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的时候到底有什么不同。
无论是DynamicSqlSource还是RawSqlSource ,在最后都会交给PreparedStatement来处理。