spring在集成mybatis 的时候,并没有使用DefaultSqlSession来一个个getmapper。而是通过@Autowired来直接获取mapper接口,调用mapper方法。那么spring帮助自动注入的mapper到底是什么呢?
其实是一种名为MapperFactoryBean的类,这个类继承了SqlSessionDaoSupport,可以直接获取到SqlSessionTemplate。使用SqlSessionTemplate来代理DefaultSqlSession进行db交互。
那么这个SqlSessionTemplate到底是何方神圣?
SqlSessionTemplate其实是Spring 为了接入mybatis提供的bean,它其实是SqlSession的一个实现类,用于替代DefaultSqlSession的存在,保存线程的SqlSession来保证线程安全性。
既然SqlSessionTemplate是一个Bean,那它默认就是单利的,里面有一个sqlSessionProxy的类变量,他其实是SqlSession的代理类:SqlSessionInterceptor。每次SqlSessionTemplate在执行方法的时候,最后都给交给SqlSessionInterceptor来代理执行,SqlSessionInterceptor每次在获取SqlSession的时候,都会使用事务管理器从threadlocal中获取,所以必然是线程安全的!对于Spring而言,每个事务都会使用同一个SqlSession,其实也就是DefaultSqlSession,用于操作相应的executor来进行db交互。
是不是看到这里有点懵逼?接下来结合源码来进一步分析。
从你依赖注入一个mapper接口到完成一次sql查询的全过程
例:TestMapper.java TestMapper.xml
通过@Autowire来获取一个TestMapper,但其实spring在启动的时候注入的是一个MapperFactoryBean(具体位置在org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions中)
下面看看MapperFactoryBean的源码
// 继承了SqlSessionDaoSupport,继承了SqlSessionDaoSupport可以用于直接获取SqlsessionTemplate
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// 真正的mapper文件
private Class<T> mapperInterface;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
...
}
再来看看通过SqlSessionDaoSupport获取到的SqlSessionTemplate里面是什么:
// 其实为defaultSqlSession的代理类SqlSessionInterceptor
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;
// jdk动态代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
...
// 内部类 SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// SqlSessionUtils.getSession(...) 是工具类, 里面使用事务同步管理器从threadlocal中获取到SqlsessionHolder, sqlsessionholder可以用于获取缓存了的DefaultSqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 用DefaultSqlSession来完成真正的操作
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
...
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
// SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 使用事务同步管理器从threadlocal中获取到SqlsessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 获取到DefaultSqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如果threadlocal中没有会生成一个
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
// DefaultSqlSession 来完成真正的操作
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 在全局配置类Configuration中,有一个map为: Map mappedStatements = new StrictMap("Mapped Statements collection"); 这是在Configuration初始化的时候,扫描到所有xml中的语句,init到这个map中去,每个sql语句都是一个 MappedStatement,map的key为mapper文件的路径+id(类似com.xxx.entity.mapper.TestMapper.selectByPrimaryKey)
MappedStatement ms = configuration.getMappedStatement(statement);
// 获取到MapperStatement之后,交给executor来进行执行具体操作
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();
}
}
Executor是在sqlsession生成的时候生成的,mybatis也给插件们(interceptor)留下了口子,用于代理executor。
如果不开启二级缓存,默认的executor为SimpleExecutor,否则为CachingExecutor。CachingExecutor为SimpleExecutor的装饰器,二级缓存是基于namespace(MappedStatement)的,cache在MappedStatement中,所以所有的sqlsession共享二级缓存,而一级缓存就存在于executor下。
二级缓存 > 一级缓存
关于缓存这块的具体内容推荐看下这篇美团技术团队的文章,写的特好(羡慕):
传送门
网上资料大多都说明了,使用$ {} 的时候会存在sql注入的问题,所以已经不推荐使用这种方式,那么到底两者在什么地方导致了不同的结果呢?结合源码来进行说明。
// org.apache.ibatis.executor.BaseExecutor#query 方法 可以看到具体执行的sql是从MappedStatement中获得
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
最后进入到org.apache.ibatis.mapping.SqlSource#getBoundSql这个接口方法中
可以看到该接口有多个实现类,大胆猜测不同的实现类最终获取到的执行sql是不同的。
很容易可以看到MappedStatement 中的 SqlSource是在org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource中生成的:
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("