MyBatis-Spring 帮助将 MyBatis 代码无缝地整合到 Spring 中。
Mybatis与Spring集成时都做了什么?
MyBatis集成Spring后,Spring侵入了Mybatis的初始化和mapper绑定,具体就是:
- Cofiguration的实例化是读取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml
- mapper对象是方法级别的,Spring通过FactoryBean巧妙地解决了这个问题
- 事务交由Spring管理
初始化相关
在基础的MyBatis中,通过SqlSessionFactoryBuilder创建SqlSessionFactory。
集成Spring后由SqlSessionFactoryBean来创建。
Cofiguration的实例化是读取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory(); }
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; return this.sqlSessionFactoryBuilder.build(targetConfiguration); } }
|
事务管理
MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中。
借助 Spring 的 DataSourceTransactionManager 实现事务管理。
SqlSeeion与事务的关系:
- 同一事务中不管调用多少次 mapper里的方法,最终都是用得同一个sqlSession,即一个事务中使用的是同一个sqlSession。
- 如果没有开启事务,调用一次mapper里的方法将会新建一个 sqlSession 来执行方法。
SqlSeeion
在MyBatis 中,使用 SqlSessionFactory 来创建 SqlSession。通过它执行映射的sql语句,提交或回滚连接,当不再需要它的时候,可以关闭 session。
使用 MyBatis-Spring 之后,我们不再需要直接使用 SqlSessionFactory 了,因为我们的bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
spring整合mybatis之后,通过动态代理的方式,使用SqlSessionTemplate持有的sqlSessionProxy属性来代理执行sql操作。方法执行完操作后,会执行finally里面的代码对sqlSeesion进行关闭。
因此,spring整合mybatis之后,由spring管理的sqlSeesion在sql方法(增删改查等操作)执行完毕后就自行关闭了sqlSession,不需要我们对其进行手动关闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession;
public SqlSessionDaoSupport() { } public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); }
} public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } public SqlSession getSqlSession() { return this.sqlSession; } protected void checkDaoConfig() { Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSession sqlSessionProxy;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
private class SqlSessionInterceptor implements InvocationHandler { @Override 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)) { sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { 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); } } } } }
|
映射器 Mapper
Mapper的注册
1 2 3 4
| @MapperScan("com.feng.blog.mybatis.*.mapper") @SpringBootApplication public class HaierDocumentCenterApplication {}
|
最终mapper接口都将被注册为MapperFactoryBean。
MapperFactoryBean分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } }
public class SqlSessionTemplate implements SqlSession, DisposableBean { @Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } }
public class Configuration { public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } }
public class MapperRegistry { public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } }
public class MapperProxyFactory<T> { public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } }
|
最终看到动态代理生成了一个新的代理实例返回了,也就是说,我们使用@Autowired注解进来一个Mapper接口,每次使用时都会由代理生成一个新的实例。
为什么在Mybatis中SqlSession是方法级的,Mapper是方法级的,在集成Spring后却可以注入到类中使用?
因为在Mybatis-Spring中所有Mapper被注册为FactoryBean,每次调用都会执行getObject(),返回新实例。