Spring 与 MyBatis 的整合
本文讨论 Spring 与 MyBatis 的整合。
在 beans.xml 中我们定义了两个 bean: SqlSessionFactoryBean、SqlSessionTemplate。
1、SqlSessionFactoryBean
a、SqlSessionFactoryBean 是 FactoryBean,它在 Spring 容器中通过 getObject() (单例模式)返回 SqlSessionFactory bean。源码如下:
1 public SqlSessionFactory getObject() throws Exception { 2 if (this.sqlSessionFactory == null) { 3 afterPropertiesSet(); 4 } 5 6 return this.sqlSessionFactory; 7 }
可以看出,SqlSessionFactoryBean 对 sqlSessionFactory 是懒加载。
b、afterPropertiesSet() 源码如下:
1 public void afterPropertiesSet() throws Exception { 2 notNull(dataSource, "Property 'dataSource' is required"); 3 notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); 4 5 this.sqlSessionFactory = buildSqlSessionFactory(); 6 }
afterPropertiesSet() 先检查 dataSource、sqlSessionFactoryBuilder 是否为 null。
dataSource 在 beans.xml 中设置,sqlSessionFactoryBuilder 在新建 sqlSessionFactoryBean 时会自动创建。
c、protected SqlSessionFactory buildSqlSessionFactory() throws Exception;
这个方法设置了 transactionFactory (默认为 SpringManagedTransactionFactory),生成 environment,放入 configuration 中。
然后对 mapperLocations(Resource[]) 进行解析,放入 configuration 的 mappedStatements 中。
最后调用 sqlSessionBuilder.build(configuration) 返回 sqlSessionFactory。
d、在 beans.xml 中给 SqlSessionFactoryBean 设置了 dataSource、mapperLocations 两个参数。
2、SqlSessionTemplate 构造方法接收一个 SqlSessionFactory 参数,最终会调用以下的构造方法:
1 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, 2 PersistenceExceptionTranslator exceptionTranslator) { 3 4 notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); 5 notNull(executorType, "Property 'executorType' is required"); 6 7 this.sqlSessionFactory = sqlSessionFactory; 8 this.executorType = executorType; 9 this.exceptionTranslator = exceptionTranslator; 10 this.sqlSessionProxy = (SqlSession) newProxyInstance( 11 SqlSessionFactory.class.getClassLoader(), 12 new Class[] { SqlSession.class }, 13 new SqlSessionInterceptor()); 14 }
sqlSessionFactory 默认 executorType 为 Simple,exceptionTranslator 为 MyBatisExceptionTranslator。
SqlSessionTemplate 可以加入到 Spring 的事务中,且线程安全,而 DefaultSqlSession 没有这两个特性。
3、DAO 层对数据库的访问最后都被 SqlSessionInterceptor 拦截。
这里用到了 JDK 反射功能。
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取sqlSession,其中包含了statement id final 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) { Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { // 关闭session closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } }
可以看出,对 sqlSession 的获取和关闭、事务管理、异常管理都由这个内部类 SqlSessionInterceptor 负责;业务层只需专注 SQL 的编写,无需再关注对资源、事务及异常的管理。
代理的目的是加入透明的对资源、事务、异常的管理。
最终委托给 DefaultSqlSession 去访问数据库:从 configuration 获取 MappedStatement,由 Executor(默认 CachingExecutor)完成对 DB 的访问。