** 这篇博客主要是来分析MyBatis与Spring集成后Spring帮我们做了哪些事情,以及集成后使用MyBatis有什么变化。**
首先来看看集成包下有什么东西吧。
第一个模块annotation:这里做了一个注解(MapperScan),用于扫描mapper。以及mapper扫描注册器(MapperScannerRegistrar),此扫描注册器实现了ImportBeanDefinitionRegistrar接口,在Spring容器启动时会运行所有实现了这个接口的实现类,而这个注册器内部会注册一系列MyBatis的信息。
第二个模块batch:这里没有深入研究这个包,估计是为了批量操作使用的?
第三个模块config:主要是为了解析处理读取到的配置信息。
第四个模块mapper:这里就是主要处理mapper的地方了,与编程式的MyBatis不同,与Spring集成后,每个mapper本来的生命周期为method级别,在集成后变成了application级别,主要原因是Spring将扫描到的每一个mapper都存入IOC容器,并且是单例的。
我们进入ClassPathMapperScanner的doScan方法DEBUG来看一下。
这里可以看到,所有的mapper都会被放入IOC容器,并且scope=singleton,生命周期提升至容器级别,所以我们在用的时候只需要使用注解@Autowired即可使用Mapper。
第五个模块support:只有一个模板辅助类,是MapperFactoryBean的父类,用于方便创建一个SqlSession(集成后的SqlSession变成了SqlSessionTemplate,下面会介绍)
第六个模块transaction:在集成后,事务的管理交给了Spring来做。
最后一个就是SqlSession的变化了,先来DEBUG看看集成后的mapper是个什么东西吧。
这里我随意启动了一个Service层,debug了一个mapper的属性,可以看到,它还是一个熟悉的MapperProxy代理的,但不同点在于MapperProxy中的sqlSession,这里再也不是DefaultsqlSession了,而是一个SqlSessionTemplate,那这个到底是什么呢?
1234567891011121314
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()); }
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());
}
这个是SqlSessionTemplate主要的构造方法,可以看到,SqlSessionTemplate里还有一个SqlSession。
1
private final SqlSession sqlSessionProxy;
private final SqlSession sqlSessionProxy;
而这个SqlSession又用动态代理代理了一下,主要代理类为SqlSessionInterceptor,其为SqlSessionTemplate的内部类。
1234567891011121314151617
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)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } }
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)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
}
}
这里删除了一些东西,保留了关键代码,可以看出来每次运行SqlSessionTemplate的方法都会新创建一个SqlSession,看看getSqlSession这个方法是如何创建SqlSession的。
12345678910111213141516171819202122
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; }
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;
}
这个方法在SqlSessionUtils中,用了静态导包方法直接写方法名调用。可以看到,这里会先从TransactionSynchronizationManager中获取SqlSession(下面会提到一级缓存),如果取不到就用sessionFactory新开启一个SqlSession,而开启的这个SqlSession还是我们之前熟悉的DefaultSqlSession,回到上面内部类的invoke方法。
123456
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); }
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);
}
由method.invoke(sqlSession, args)可以看出,其实真正执行查询方法的还是DefaultSqlSession,下面又判断了一下事务,然后自动commit。
我们来总结一下,这个SqlSessionTemplate到底在集成后有了什么作用?为什么要一个这样的SqlSession?
首先理一理大致的流程,Spring容器在启动后会扫描所有的Mapper信息,然后还是用MapperProxy给每一个Mapper接口做代理,不同的是这次的MapperProxy中的SqlSession是SqlSessionTemplate代理的,也就是说,每次执行mapper接口的方法,都会先执行MapperProxy的invoke方法,然后去执行SqlSessionTemplate的invoke方法,然后SqlSessionTemplate内部就会新new一个DefaultSqlSession,实际上底层还是使用的DefaultSqlSession的方法。
那为什么要这样一个SqlSession呢?
可以从invoke方法看出,在集成后,设计者将为每一次查询都开启一个新的SqlSession,在集成后的scope为method级别,也就是每调用一次mapper接口的方法,都会创建一个SqlSession,然后会将此SqlSession判断事务,因为是Spring管理事务,所以这里就可以用SqlSessionTemplate这样一个类去很方便的可以管理事务,也方便了Spring管理事务。
介绍到这里,可以抛出一个问题了,我们知道,一级缓存是SqlSession级别的,同一个SqlSession在查询同一条语句时会使用缓存,但集成后将SqlSession设计成每调用一次方法开启一个新的SqlSession。
那么一级缓存是不是失效的呢?
其实也不一定,在一般情况下一级缓存是没有用了,但如果开启了事务,就不一定是新创建SqlSession了,其实上面也有提到,在SqlSesionUtils类中创建SqlSession方法里。
12345678910111213141516171819202122
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; }
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;
}
会尝试先从TransactionSynchronizationManager取SqlSession,而这个类存放了一个ThreadLocal变量。
1
private static final ThreadLocalSetTransactionSynchronization synchronizations = new NamedThreadLocal("Transaction synchronizations");
private static final ThreadLocalSetTransactionSynchronization synchronizations = new NamedThreadLocal("Transaction synchronizations");
在上面的getSqlSession方法中,倒二行有一个registerSessionHolder,里面会判断是否有开启事务,如有开启事务则将当前的SqlSession保存至一个holder到TransactionSynchronizationManager的ThreadLocal中,也就是保存到线程级别的变量中,然后在同一个Transaction且同一个线程中如果有再次调用mapper的方法,第二次进入getSqlSession方法后会从TransactionSynchronizationManager中取出刚刚存放的线程变量ThreadLocal的holder,再从holder取出SqlSession,这样,两次查询就还是用的同一个SqlSession,一级缓存还是会生效的(一级缓存默认开启)。但在没有事务的情况下,每一个mapper方法的调用都会创建一个新的mapper。
这里写一个测试类来验证以上的想法。
123456789101112131415161718192021222324252627282930313233343536373839404142
@RestController@RequestMapping("")public class LoginControlller { @Autowired UserMapper mapper; @RequestMapping(value = "/test") @Transactional public String test(){ long one = System.currentTimeMillis(); long id = 3; mapper.selectByPrimaryKey(id); long two = System.currentTimeMillis(); mapper.selectByPrimaryKey(id); long three = System.currentTimeMillis(); mapper.selectByPrimaryKey(id); long four = System.currentTimeMillis(); System.out.println("第1次查询耗时:" + (two - one)); System.out.println("第2次查询耗时:" + (three - two)); System.out.println("第3次查询耗时:" + (four - three)); return "test"; } @RequestMapping(value = "/test2") public String test2(){ long one = System.currentTimeMillis(); long a = 3; mapper.selectByPrimaryKey(a); long two = System.currentTimeMillis(); mapper.selectByPrimaryKey(a); long three = System.currentTimeMillis(); mapper.selectByPrimaryKey(a); long four = System.currentTimeMillis(); System.out.println("第1次查询耗时:" + (two - one)); System.out.println("第2次查询耗时:" + (three - two)); System.out.println("第3次查询耗时:" + (four - three)); return "test2"; }}
@RestController
@RequestMapping(“”)
public class LoginControlller {
@Autowired
UserMapper mapper;
@RequestMapping(value = "/test")
@Transactional
public String test(){
long one = System.currentTimeMillis();
long id = 3;
mapper.selectByPrimaryKey(id);
long two = System.currentTimeMillis();
mapper.selectByPrimaryKey(id);
long three = System.currentTimeMillis();
mapper.selectByPrimaryKey(id);
long four = System.currentTimeMillis();
System.out.println("第1次查询耗时:" + (two - one));
System.out.println("第2次查询耗时:" + (three - two));
System.out.println("第3次查询耗时:" + (four - three));
return "test";
}
@RequestMapping(value = "/test2")
public String test2(){
long one = System.currentTimeMillis();
long a = 3;
mapper.selectByPrimaryKey(a);
long two = System.currentTimeMillis();
mapper.selectByPrimaryKey(a);
long three = System.currentTimeMillis();
mapper.selectByPrimaryKey(a);
long four = System.currentTimeMillis();
System.out.println("第1次查询耗时:" + (two - one));
System.out.println("第2次查询耗时:" + (three - two));
System.out.println("第3次查询耗时:" + (four - three));
return "test2";
}
}
/test映射test方法,此方法开启了事务,/test2映射test2方法,此方法没有开启事务。
首先访问/test,控制台打印。
从日志可以看出来,这里只创建了一次SqlSession,并且将此SqlSession注册进了transaction synchronization中,也就是上面提到的那个地方,也可以从下面看出,只执行了一次SQL语句的查询,以至于查询耗时变快,感觉存在缓存也许是因为此时三个查询都是同一个SqlSession,一级缓存生效了。所以可以得出结论,被事务管理的方法中,调用mapper的方法,不会new多次SqlSession,同一个线程同一个mapper都只会使用同一个SqlSession,所以此时的一级缓存是生效的。
第二个测试,访问/test2 (没有注解上@Transactional)
第三次查询耗时:0,控制台有点短截取不到下面的内容了。这里关键信息我框了出来,可以看出第一次查询使用了SQL语句进行查询,然后关闭一个没有事务的SqlSession,归还JDBC连接给数据库,在第二次查询之前,又创建了一个新的SqlSession,然后又进行相同的SQL语句的查询,接着又关闭了没有事务的SqlSession,并且归还了JDBC连接给数据库。以至于为什么查询耗时还是越来越短,感觉到缓存的存在,这是因为数据库上也是有缓存的。所以到这里可以得出结论,没有被事务管理的方法例如/test2,在每次mapper查询的时候,都会新创建SqlSession,以至于一级缓存失效,但底层数据库缓存还是有在的。我在写这篇文章时曾一度以为MyBatis与Spring集成后一级缓存是失效的,在查阅资料以及动手验证才知道底层还有这么个原理,在测试的时候没有开启事务也能让查询耗时变短,曾一度以为是MapperProxy中的methodCache属性在作怪,感兴趣的读者可以去这个类看一看,这里的methodCache只是缓存了Mapper的method信息,例如command的type(INSERT、UPDATE等等),查询时还是照样要去数据库查询,误解了以为是这里使查询耗时变快,后来仔细看了一下这里并不缓存查询结果…