欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳細(xì)了解MyBatis的異常處理機(jī)制

 更新時間:2023年06月06日 09:04:19   作者:半夏之沫  
本文將對MyBatis的異常體系以及異常使用進(jìn)行學(xué)習(xí),MyBatis版本是3.5.6,作為一款成熟的ORM框架,MyBatis有自己一套成熟的異常處理體系,,需要的朋友可以參考下

前言

作為一款成熟的ORM框架,MyBatis有自己一套成熟的異常處理體系。MyBatis的異常體系,有如下幾個關(guān)鍵角色。

  • PersistenceException。繼承于RuntimeException(直接繼承于**IbatisException),是MyBatis各個功能模塊的異常的父類,所以MyBatis**中使用的異常都是運行時異常;
  • ExceptionFactory。MyBatis中根據(jù)異常上下文創(chuàng)建PersistenceException的工廠類,配合ErrorContext使用;
  • ErrorContext。MyBatis異常處理的靈魂,是一個和線程綁定的全局異常上下文,在打印異常信息時,能夠反映出異常存在于哪個映射文件中,是做什么操作時引發(fā)的異常以及發(fā)生異常的SQL信息等。

正文

一. MyBatis異常體系說明

MyBatis框架自定義了一個異常基類,叫做PersistenceException,UML圖如下所示。

MyBatis各個功能模塊自定義的異常均繼承于PersistenceException,部分異常類UML圖如下所示。

異常的拋出策略遵循如下原則。

  • 優(yōu)先基于邏輯判斷的方式拋出異常。在每個功能模塊中,會優(yōu)先對非法條件或場景進(jìn)行判斷校驗,如果校驗不通過,則拋出功能模塊對應(yīng)的自定義異常;
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (!StatementType.CALLABLE.equals(ms.getStatementType())
        && void.class.equals(ms.getResultMaps().get(0).getType())) {
        throw new BindingException("method " + command.getName()
                                   + " needs either a @ResultMap annotation, a @ResultType annotation,"
                                   + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
        sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
}
  • 所有底層異常統(tǒng)一封裝為MyBatis的自定義異常。比如初始化日志打印器時的各種反射相關(guān)異常,獲取數(shù)據(jù)庫連接時的各種數(shù)據(jù)庫連接池相關(guān)異常,與數(shù)據(jù)庫交互時的各種SQL異常等,均會被MyBatis統(tǒng)一封裝為各個功能模塊自定義的異常類型,然后向上拋出;
public static Log getLog(String logger) {
    try {
        // 運行時異常,校驗異常和Error均可能會發(fā)生
        return logConstructor.newInstance(logger);
    } catch (Throwable t) {
        // 捕獲到的Throwable統(tǒng)一封裝為自定義的LogException
        throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
    }
}
  • 在能夠處理自定義異常的地方精確捕獲異常。在能夠明確下層會拋出哪種異常并且當(dāng)前能夠處理這種異常的情況下,通過try-catch精確的捕獲異常。
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
        return getNullableResult(rs, columnName);
    } catch (Exception e) {
        throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
}

上述getResult() 方法會拋出SQLException,下面是調(diào)用getResult() 方法時的兩種不同處理策略。

// 能明確下層會拋出哪種異常且能夠處理這種異常的情況
Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
                                       List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
    boolean foundValues = false;
    for (ResultMapping constructorMapping : constructorMappings) {
        final Class<?> parameterType = constructorMapping.getJavaType();
        final String column = constructorMapping.getColumn();
        final Object value;
        try {
            if (constructorMapping.getNestedQueryId() != null) {
                value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
            } else if (constructorMapping.getNestedResultMapId() != null) {
                final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
                value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
            } else {
                final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
                value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
            }
        } catch (ResultMapException | SQLException e) {
            // 精確的捕獲ResultMapException和SQLException
            throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
        }
        constructorArgTypes.add(parameterType);
        constructorArgs.add(value);
        foundValues = value != null || foundValues;
    }
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
// 不能明確下層會拋出哪種異?;蛘弋?dāng)前不能夠處理這種異常的情況
@Override
public Object getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
    TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
    return handler.getResult(rs, columnName);
}

總之就是突出一個能處理絕不放過,不能處理絕不逞強(qiáng)

二. ErrorContext

我們使用MyBatis操作數(shù)據(jù)庫時,如果在映射文件中寫了一條錯誤的SQL,此時運行程序,會得到如下報錯信息。

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'book b' at line 4
### The error may exist in com/mybatis/learn/dao/BookMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT             b.id, b.b_name, b.b_price         FROMM             book b
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'book b' at line 4
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
	......
Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'book b' at line 4
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	......

通過上述的異常信息,我們清晰的知道了錯誤發(fā)生在哪個映射文件,錯誤與哪個對象有關(guān),錯誤是在進(jìn)行什么操作時發(fā)生,錯誤相關(guān)的SQL語句信息,錯誤詳細(xì)的堆棧信息

MyBatis之所以能夠在異常發(fā)生時打印出上述的完備的異常信息,就是基于ErrorContext,下面對ErrorContext的實現(xiàn)原理和工作機(jī)制進(jìn)行分析。

MyBatisErrorContext實現(xiàn)成了線程綁定的單例模式,在ErrorContext中有一個靜態(tài)字段LOCAL,用于存儲每個線程的ErrorContext,同時還提供了instance() 方法用于每個線程獲取ErrorContext,相關(guān)字段和方法如下所示。

public class ErrorContext {
    private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);
    ......
    private ErrorContext() {
    }
    public static ErrorContext instance() {
        return LOCAL.get();
    }
    ......
}

上述代碼可以等效于如下代碼。

public class ErrorContext {
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    ......
    private ErrorContext() {
    }
    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }
    ......
}

也就是每個線程在使用MyBatis的過程中,隨時可以通過ErrorContextinstance() 方法拿到當(dāng)前線程綁定的ErrorContext

ErrorContext有如下幾個字段,用于存儲MyBatis執(zhí)行過程中的關(guān)鍵信息,如下所示。

public class ErrorContext {
    ......
    // 用于暫存ErrorContext
    private ErrorContext stored;
    // 保存當(dāng)前操作的映射文件
    private String resource;
    // 保存當(dāng)前的行為
    private String activity;
    // 保存當(dāng)前操作的對象
    // 比如保存當(dāng)前的MappedStatement的id
    private String object;
    // 保存當(dāng)前的異常信息
    private String message;
    // 保存當(dāng)前執(zhí)行的SQL
    private String sql;
    // 保存異常
    private Throwable cause;
    ......
}

下面以一條錯誤的SQL執(zhí)行全過程,演示ErrorContext的完整工作機(jī)制。

已知,MyBatis中,我們通過映射接口執(zhí)行SQL語句,流程如下。

首先在BaseExecutor中會記錄resource,如下所示。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
                         ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
		throws SQLException {
    // 在這里記錄resource
    ErrorContext.instance()
        .resource(ms.getResource())
        .activity("executing a query")
        .object(ms.getId());
    ......
    List<E> list;
    ......
    return list;
}

在上述方法中記錄了resourcecom/mybatis/learn/dao/BookMapper.xml,雖然也記錄了activityobject,但是這兩個值會在后續(xù)流程節(jié)點被覆蓋。

繼續(xù)往下執(zhí)行,會在BaseStatementHandlerprepare() 方法中記錄sql,如下所示。

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) 
    throws SQLException {
    // 在這里記錄sql
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        statement = instantiateStatement(connection);
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

繼續(xù)往下執(zhí)行,會在DefaultParameterHandlersetParameters() 方法中記錄activityobject,如下所示。

@Override
public void setParameters(PreparedStatement ps) {
    // 在這里記錄activity和object
    ErrorContext.instance()
        .activity("setting parameters")
        .object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        ......
    }
}

繼續(xù)往下執(zhí)行,就會在PreparedStatementHandlerquery() 方法中真正的通過PreparedStatement操作數(shù)據(jù)庫,如下所示。

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
    	throws SQLException {
    // 這里是JDBC里的PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    // 由于之前故意將SQL寫錯所以這里會報錯
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

由于之前故意在映射文件中將SQL寫錯,所以在PreparedStatementHandlerquery() 方法中通過PreparedStatement操作數(shù)據(jù)庫時,會拋出SQLSyntaxErrorException,該異常會一路往外拋,最終在DefaultSqlSessionselectList() 方法中被捕獲,如下所示。

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

捕獲到SQLSyntaxErrorException后,會通過ExceptionFactorywrapException() 方法創(chuàng)建PersistenceException,如下所示。

public static RuntimeException wrapException(String message, Exception e) {
    // 先記錄message和cause到ErrorContext中
    // 然后通過ErrorContext的toString()方法組裝異常詳細(xì)信息
    // 最后基于異常詳細(xì)信息和異常創(chuàng)建PersistenceException
    return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
}

在創(chuàng)建PersistenceException時,會先把ErrorContextmessagecause豐富上,此時ErrorContext的所有字段已經(jīng)完成賦值,然后會通過ErrorContexttoString() 方法組裝得到異常的詳細(xì)信息,最后基于異常詳細(xì)信息和異常創(chuàng)建PersistenceException。我們看到的異常的詳細(xì)打印信息,就是在ErrorContexttoString() 方法中拼接的,下面看一下其實現(xiàn)。

@Override
public String toString() {
    StringBuilder description = new StringBuilder();
    // 拼接message
    if (this.message != null) {
        description.append(LINE_SEPARATOR);
        description.append("### ");
        description.append(this.message);
    }
    // 拼接resource
    if (resource != null) {
        description.append(LINE_SEPARATOR);
        description.append("### The error may exist in ");
        description.append(resource);
    }
    // 拼接object
    if (object != null) {
        description.append(LINE_SEPARATOR);
        description.append("### The error may involve ");
        description.append(object);
    }
    // 拼接activity
    if (activity != null) {
        description.append(LINE_SEPARATOR);
        description.append("### The error occurred while ");
        description.append(activity);
    }
    // 拼接sql
    if (sql != null) {
        description.append(LINE_SEPARATOR);
        description.append("### SQL: ");
        description.append(sql
                   .replace('\n', ' ')
                   .replace('\r', ' ')
                   .replace('\t', ' ')
                   .trim());
    }
    // 拼接cause
    if (cause != null) {
        description.append(LINE_SEPARATOR);
        description.append("### Cause: ");
        description.append(cause.toString());
    }
    return description.toString();
}

最后,一次數(shù)據(jù)庫操作結(jié)束時,無論操作是否成功,都需要對ErrorContext進(jìn)行初始化,在DefaultSqlSessionselectList() 方法的finally代碼塊中,會調(diào)用到ErrorContextreset() 方法來初始化ErrorContext,如下所示。

public ErrorContext reset() {
    resource = null;
    activity = null;
    object = null;
    message = null;
    sql = null;
    cause = null;
    // 防止內(nèi)存泄漏
    LOCAL.remove();
    return this;
}

至此,一次數(shù)據(jù)庫操作中,ErrorContext的使命就完成了。

總結(jié)

其實可以發(fā)現(xiàn),MyBatis的異常使用中,也沒有嚴(yán)格遵循異常規(guī)約,甚至某些地方還明目張膽的觸犯異常規(guī)約,但是其實也不妨礙MyBatis的強(qiáng)大。

MyBatis的異常體系,總結(jié)如下。

  • 所有異常都是運行時異常;
  • 優(yōu)先基于邏輯判斷的方式拋出異常;
  • 所有底層異常統(tǒng)一封裝為MyBatis的自定義異常;
  • 能處理絕不放過,不能處理絕不逞強(qiáng)

此外,MyBatis自己基于ErrorContext實現(xiàn)了一套全局異常處理機(jī)制,使得MyBatis在異常發(fā)生時,能夠打印盡可能詳細(xì)的異常信息,這里給出一個完整的作用流程圖。

以上就是詳細(xì)了解MyBatis的異常處理機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于MyBatis 異常處理機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • metershpere實現(xiàn)調(diào)用自定義jar包中的方法

    metershpere實現(xiàn)調(diào)用自定義jar包中的方法

    在MeterSphere接口測試中,面對多層循環(huán)邏輯和邏輯判斷等復(fù)雜情況,直接編寫測試用例往往顯得混亂不便,本文介紹了一個簡化這一過程的方法:首先使用IDEA創(chuàng)建Maven工程,編寫所需邏輯并生成jar包;然后在MeterSphere中上傳此jar包
    2024-10-10
  • Spring請求傳遞參數(shù)的解決方案

    Spring請求傳遞參數(shù)的解決方案

    訪問不同的路徑,就是發(fā)送不同的請求.在發(fā)送請求時,可能會帶?些參數(shù),所以我們在學(xué)習(xí)Spring的請求時,主要是學(xué)習(xí)如何傳遞參數(shù)到后端以及后端如何接收,下面給大家講解?Spring請求傳遞參數(shù)詳解,一起看看吧
    2024-01-01
  • Java獲取漢字拼音的全拼和首拼實現(xiàn)代碼分享

    Java獲取漢字拼音的全拼和首拼實現(xiàn)代碼分享

    這篇文章主要介紹了Java獲取漢字拼音的全拼和首拼實現(xiàn)代碼分享,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下
    2015-06-06
  • java 獲取當(dāng)前函數(shù)名的實現(xiàn)代碼

    java 獲取當(dāng)前函數(shù)名的實現(xiàn)代碼

    以下是對使用java獲取當(dāng)前函數(shù)名的實現(xiàn)代碼進(jìn)行了介紹。需要的朋友可以過來參考下
    2013-08-08
  • 基于jstl 標(biāo)簽的使用介紹

    基于jstl 標(biāo)簽的使用介紹

    本篇文章小編為大家介紹,基于jstl 標(biāo)簽的使用介紹,需要的朋友參考下
    2013-04-04
  • Java 爬蟲如何爬取需要登錄的網(wǎng)站

    Java 爬蟲如何爬取需要登錄的網(wǎng)站

    這篇文章主要介紹了Java 爬蟲如何爬取需要登錄的網(wǎng)站,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • 詳解Mybatis中的PooledDataSource

    詳解Mybatis中的PooledDataSource

    這篇文章主要介紹了詳解Mybatis中的PooledDataSource,PooledDataSource使用了數(shù)據(jù)庫連接池可以實現(xiàn)數(shù)據(jù)庫連接池的重復(fù)利用,還能控制連接數(shù)據(jù)庫的連接上限
    2022-06-06
  • Mybatis-Plus邏輯刪除的用法詳解

    Mybatis-Plus邏輯刪除的用法詳解

    這篇文章主要為大家詳細(xì)介紹了Mybatis-Plus 邏輯刪除的用法,文中有詳細(xì)的代碼示例,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-07-07
  • Java并發(fā)編程中的volatile關(guān)鍵字詳解

    Java并發(fā)編程中的volatile關(guān)鍵字詳解

    這篇文章主要介紹了Java并發(fā)編程中的volatile關(guān)鍵字詳解,volatile?用于保證我們某個變量的可見性,使其一直存放在主存中,不被移動到某個線程的私有工作內(nèi)存中,需要的朋友可以參考下
    2023-08-08
  • 你必須得會的SpringBoot全局統(tǒng)一處理異常詳解

    你必須得會的SpringBoot全局統(tǒng)一處理異常詳解

    程序在運行的過程中,不可避免會產(chǎn)生各種各樣的錯誤,這個時候就需要進(jìn)行異常處理,本文主要為大家介紹了SpringBoot實現(xiàn)全局統(tǒng)一處理異常的方法,需要的可以參考一下
    2023-06-06

最新評論