一、SqlSessionTemplate的入場
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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("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("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
// 根據isDynamic 來生成不同的SqlSource 那這裏面到底做了什麼?
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
// TextSqlNode 類
// 構造方法,傳入代碼中編寫的sql語句
public TextSqlNode(String text) {
this(text, null);
}
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
// 是否爲動態語句
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
...
// 到這裏可以明顯看出SqlSource的實現類到底是 DynamicSqlSource 還是 RawSqlSource 是根據語句中是否有${}來判斷的
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
知道了SqlSource 的來源,那麼再看看這兩種不同的實現類在getBoundSql方法,也就是獲取最終執行sql的時候到底有什麼不同。
RawSqlSource
// 不帶有${} 的sql語句使用的sqlsource實現類
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 這個sqlSource 其實是StaticSqlSource 生成過程中會將sql中的#{...} 格式化爲 ? 的
// 例:select * from test where id = #{id}
// ====> select * from test where id = ?
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 裝飾器模式 交給StaticSqlSource.getBoundSql
// 直接返回 new BoundSql(configuration, sql, parameterMappings, parameterObject);
return sqlSource.getBoundSql(parameterObject);
}
}
DynamicSqlSource
// 帶有${} 的sql語句使用的sqlsource實現類
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 在這裏會將初始sql和param屬性替換,組成一個完整的執行sql語句。並放到context的sqlBuilder屬性中。
// 例:select * from test where id = ${id}
// ====> select * from test where id = 1
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 將上面處理完成後的sql語句再進行StaticSqlSource的處理,依然對#{}的屬性進行處理
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
無論是DynamicSqlSource還是RawSqlSource ,在最後都會交給PreparedStatement來處理。