mybatis的擴展是經過攔截器Interceptor來實現的,本質上就是JDK的動態代理,因此它只能對接口進行攔截,mybatis能夠對如下四個接口類型進行攔截,也就是說會對這4種對象進行代理,全部的代理攔截都是經過 InterceptorChain.pluginAll(Object target) 來實現的。html
Executor: 執行器,執行SQL語句(全部的sql都經過它來執行),而且對事務、緩存等提供統一接口。(在這一層上作攔截的權限會更大)
StatementHandler: 對statement進行預處理,而且提供統一的原子的增、刪、改、查接口。(若是要在SQL執行前進行攔截的話,攔截這裏就能夠了)
ResultSetHandler:對返回結果ResultSet進行處理。
PameterHandler:對參數進行賦值。java
SqlSession的建立過程:sql
mybatis中的SQL都是經過DefaultSqlSession去執行的,其建立過程以下:緩存
// SqlSessionFactoryBuilder => DefaultSqlSessionFactory => DefaultSqlSession SqlSession sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession();
2.一、建立SqlSession時,SqlSessionFactroy會解析mybatis.xml配置文件中的plugins標籤,並將Interceptor屬性定義的Interceptor放到interceptorChain中;session
// SqlSessionFactoryBuilder.java public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); // 解析mybatis.xml配置文件,並建立DefaultSqlSessionFactory return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
// XMLConfigBuilder.java public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } // 解析mybatis.xml中的各個標籤 private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // 解析plugins標籤,並把Interceptor放到interceptorChain中 private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } } // Configuration,mybatis文件的抽象類 public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
2.二、DefaultSqlSessionFactory.openSession()時使用JDK動態代理生成@Signature註解指定的被代理類(包含代理的方法以及方法參數) mybatis
// DefaultSqlSessionFactory.java public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 使用Configuration建立Executor final Executor executor = configuration.newExecutor(tx, execType, autoCommit); return new DefaultSqlSession(configuration, executor); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
// Configuration.java public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); } // 使用JDK動態代理生成Executor的interceptorChain executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
2.三、InterceptorChain生成的具體過程app
// InterceptorChain.java public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
// Interceptor的具體實現類(即咱們業務上要實現的功能) @Override public Object plugin(Object arg0) { return Plugin.wrap(arg0, this); }
// Plugin.java public static Object wrap(Object target, Interceptor interceptor) { // getSignatureMap獲取Interceptor類上的@Intercepts(@Signature)內容 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 生成目標類target(Executor.class)的代理類,實現咱們須要的plugin功能 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } // 解析實現Interceptor接口的類上定義的@Intercepts(@Signature)內容,獲取須要攔截的類和方法。 // 例如:@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { // issue #251 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { // sig.type()即Executor.class Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; }
3.一、實現自定義的Interceptoride
// 自定義攔截器 @Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MyTestInterceptor implements Interceptor { private static final String MSG = "octopus route table info is not exit!"; @Override public Object intercept(Invocation arg0) throws Throwable { Object obj = null; try { obj = arg0.proceed(); } catch (Throwable e) { if (e.getCause() instanceof MySQLSyntaxErrorException) { MySQLSyntaxErrorException ex = (MySQLSyntaxErrorException) e.getCause(); System.out.println("====" + ex.getErrorCode()); System.out.println("====" + ex.getSQLState()); System.out.println("====" + ex.getMessage()); System.out.println("====" + ex.getCause()); if (MSG.equals(ex.getMessage())) { throw new RouteTableNoExistException(); } } } return obj; } @Override public Object plugin(Object arg0) { return Plugin.wrap(arg0, this); } @Override public void setProperties(Properties arg0) { System.out.println("env value: " + arg0.getProperty("names")); } }
3.二、在mybatis.xml中配置pluginsfetch
<configuration> <plugins> <plugin interceptor="com.pinganfu.interceptor.MyTestInterceptor" /> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="MANAGED"> <property name="closeConnection" value="false" /> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${jdbcUrl}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mappers/TBATMapper.xml" /> </mappers> </configuration>
3.三、獲取SqlSessionui
Properties pro = new Properties(); try { pro.load(Resources.getResourceAsStream("jdbc.properties")); // 加載mybatis.xml中的plugins InputStream in = Resources.getResourceAsStream("mybatis.xml"); sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession(); } catch (IOException e) { e.printStackTrace(); }
mybatis經過DefaultSqlSession執行時,會將發生的全部異常統一包裝成PersistenceException再拋出,咱們能夠經過PersistenceException.getCause()獲取具體的異常。
// DefaultSqlSession.java public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { // 對執行發生的全部Exception進行wrap以後再拋出 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
// ExceptionFactory.java public static RuntimeException wrapException(String message, Exception e) { // 將Exception進行統一包裝成PersistenceException return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e); }
Ref:
https://www.cnblogs.com/kevin-yuan/p/7219003.html
https://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html