Mybatis是目前主流的Java ORM框架之一。
mybatis-spring包则是为了让Mybatis更好得整合进Spring的衍生产品。
本文就从Mybatis和mybatis-spring源码着手,以目前较为流行的用法,探究Mybatis的工做原理以及mybatis-spring是如何作到“迎合”Spring的。spring
首先在pom.xml文件中引入Mybatis包和mybatis-spring包(若是是SpringBoot,引入mybatis-spring-boot-starter便可):sql
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.1</version> </dependency>
而后在Spring的配置xml文件中声明如下bean:数据库
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="mapperLocations" > <list> <value>classpath*:xxx/*.xml</value> </list> </property> <property name="dataSource" ref="myDataSource" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" /> <property name="basePackage" value="com.xxx.xxx.mapper" /> </bean>
下面咱们研究每一个配置的做用,进而了解mybatis-spring的工做方式。缓存
一个FactoryBean,负责建立SqlSessionFactory,而SqlSessionFactory是建立SqlSession的工厂类。安全
它在初始化时会解析基本配置和XML映射文件,而后所有封装到一个Configuration对象中。建立出的SqlSessionFactory是DefaultSqlSessionFactory对象,持有这个Configuration对象。session
通常来讲一个应用只须要建立一个SqlSessionFactory。mybatis
这里重点关注下XML映射文件的解析,确切的说应该是解析的结果如何处理(毕竟解析的过程太复杂)。多线程
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
经常使用的几个节点:app
光解析完还不够,还得和映射接口关联起来。XML文件的mapper节点会有namespace属性,它的值就是映射接口的全类名。根据全类名获取到Class对象,而后Configuration对象中的MapperRegistry属性负责注册该类,就是将类对象和由它初始化的MapperProxyFactory对象组成键值对放入knownMappers属性。后面建立映射接口的实现类对象时会用到。框架
总结下SqlSessionFactoryBean的做用,就是建立一个SqlSessionFactory类型的单例,持有全部的配置信息和解析结果。
实现了BeanDefinitionRegistryPostProcessor,负责扫描指定包下的映射接口并向容器中注册对应的bean。
注册过程当中有一些细节须要提一下,注册的bean的beanClass并非映射接口自己,而统一是MapperFactoryBean。同时MapperScannerConfigurer建立时传入的sqlSessionFactoryBeanName所表明的SqlSessionFactory会设置到这些bean中去。
一个FactoryBean,负责建立对应映射接口的实现类对象,这个实现类负责完成映射接口的方法和XML定义的SQL语句的映射关系。
Mybatis经过SqlSession接口执行SQL语句,因此MapperFactoryBean会在初始化时经过持有的SqlSessionFactory对象建立一个SqlSessionTemplate(它实现了SqlSession)对象。这个SqlSessionTemplate是mybatis-spring的核心,它给常规的SqlSession赋予了更多的功能,特别是迎合Spring的功能,后面会详细描述。
咱们来看一下MapperFactoryBean是如何建立映射接口的实现类对象的。
既然是FactoryBean,就是经过getObject建立须要的bean对象。跟踪方法调用,发现最终委托给了Configuration对象中MapperRegistry属性。上面简述XML解析过程时已知,MapperRegistry对象的knownMappers属性保存了映射接口的类对象和一个MapperProxyFactory对象组成的键值对。
MapperProxyFactory就是一个代理工厂类,它建立实现类对象的方式就是建立以映射接口为实现接口、MapperProxy为InvocationHandler的JDK动态代理。代理的逻辑都在MapperProxy#invoke方法中:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
能够看到,咱们想要实现的方法(即排除Object方法和接口的默认方法),都委托给了对应的MapperMethod去实现。方法第一次调用时,新建MapperMethod,而后放入缓存。MapperMethod包含了两个内部类属性:
获取MapperMethod后就是调用它的execute方法:
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
方法根据SQL命令类型的不一样进行不一样的操做,同样的地方是都会先把方法参数转化为SQL参数形式,而后执行传进execute方法的SqlSession对象(即MapperFactoryBean对象持有的SqlSessionTemplate对象)的对应的方法。
总结下MapperScannerConfigurer和MapperFactoryBean的做用:MapperScannerConfigurer负责把配置路径下的映射接口注册为Spring容器的MapperFactoryBean类型的bean。这个工厂bean经过代理方式建立对应映射接口的实现类对象。实现类拦截映射接口的自定义方法,让SqlSessionTemplate去处理方法对应的SQL解析成的MappedStatement。
实现了SqlSession,但和SqlSession默认实现类DefaultSqlSession不一样的是,它是线程安全的,这意味着一个SqlSessionTemplate实例能够在多个Dao之间共享;它和Spring的事务管理紧密关联,能够实现多线程下各个事务之间的相互隔离;另外,它会把Mybatis返回的异常转化为Spring的DataAccessException。下面咱们来探究它是如何作到这几点的。
SqlSessionTemplate在初始化时会经过JDK动态代理的方式建立一个实现SqlSession、以SqlSessionInterceptor为InvocationHandler的代理对象,SqlSessionTemplate的大多数方法调用都转发给这个代理。拦截的逻辑在SqlSessionInterceptor#invoke中:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
首先获取真正用来工做的SqlSession,SqlSessionUtils#getSqlSession:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
这里包含了与Spring事务关联的逻辑。先尝试从事务同步管理类中获取传入的SqlSessionFactory对象在当前线程绑定的SqlSessionHolder对象,若是存在就直接返回SqlSessionHolder对象持有的SqlSession对象,不然就用SqlSessionFactory建立一个新的SqlSession,调用DefaultSqlSessionFactory#openSessionFromDataSource,level默认是null,autoCommit默认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); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } 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(); } }
能够看到最终建立了一个DefaultSqlSession对象,这里须要注意的一点是,这里建立了Transaction和Executor,在继续往底层探索时会再说起到。
建立完以后,会根据当前线程是否存在Spring事务而选择是否封装成SqlSessionHolder放入事务同步管理类,这样以来,同线程同事务下对映射接口的调用,实际工做的都是同一个SqlSession。
咱们回到SqlSessionInterceptor,获取到实际工做的DefaultSqlSession会去执行当前拦截的方法(具体咱们稍后探究),若是抛出Mybatis的PersistenceException异常,初始化时设置的PersistenceExceptionTranslator对象(默认是MyBatisExceptionTranslator对象)会对异常进行转化为DataAccessException。
总结下SqlSessionTemplate的做用,它经过动态代理对方法进行拦截,而后根据当前Spring事务状态获取或建立SqlSession来进行实际的工做。
咱们如今知道SqlSessionTemplate最终仍是依赖一个DefaultSqlSession对象去处理映射接口方法对应的MappedStatement。下面咱们以selectList方法为例探究具体的处理过程:
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(); } }
首先从configuration中获取到MappedStatement对象,而后让Executor对象调用query方法。
Executor是Mybatis的执行器,负责SQL语句的生成和查询缓存的维护。
前面在建立DefaultSqlSession的时候,会先让configuration建立一个Executor,根据配置的ExecutorType选择具体的Executor实现,默认是SimpleExecutor,而后若是配置缓存开启(默认开启),则还要封装成CachingExecutor。
CachingExecutor的query方法会先从MappedStatement对象动态生成sql语句,和参数一块儿封装在BoundSql对象中;再根据sql、参数和返回映射等信息建立一个缓存键;而后检查XML里有没有配置二级缓存,有的话就用缓存键去查找,不然就执行它代理的Executor对象的query方法,先用缓存键去一级缓存也叫本地缓存中去查找,若是没有的话就执行doQuery方法。不一样Executor实现的doQuery有所不一样,但核心都是建立一个StatementHandler,而后经过它对底层JDBC Statement进行操做,最后对查询的结果集进行转化。
限于篇幅,就不继续探究StatementHandler及更底层的操做了,就再看下Mybatis是怎么管理数据库链接的。
先回顾下这个Transaction对象是怎么来的:前面建立实际工做的DefaultSqlSession时会让TransactionFactory对象建立一个Transactio对象做为Executor对象的属性。而这个TransactionFactory对象,如何没有指定的话,默认是SpringManagedTransactionFactory对象。它接受一个DataSource建立SpringManagedTransaction,能够看到这里把事务隔离级别和是否自动提交两个参数都忽略了,那是由于mybatis-spring把事务都交给Spring去管理了。
Executor在执行doQuery方法,建立JDBC Statement对象时须要先获取到数据库链接:
protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
继续看到SpringManagedTransaction,它的Connection是经过DataSourceUtils调用getConnection方法获取的,核心逻辑在doGetConnection方法中:
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } return conHolder.getConnection(); } // Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource"); Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { try { // Use same Connection for further JDBC actions within the transaction. // Thread-bound object will get removed by synchronization at transaction completion. ConnectionHolder holderToUse = conHolder; if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } else { holderToUse.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } catch (RuntimeException ex) { // Unexpected exception from external delegation call -> close Connection and rethrow. releaseConnection(con, dataSource); throw ex; } } return con; }
能够看到,Spring的事务管理器不只保存了事务环境下当前线程的SqlSession,还以dataSource为键保存了Connection。若是从事务管理器没有获取到,就须要经过从SpringManagedTransaction传递过来的dataSource获取Connection对象,获取到以后判断当前是否在事务环境,是的话就把Connection对象封装成ConnectionHolder保存在事务管理器中,这样的话就能保证一个事务中的数据库链接是同一个。