我们先来看看Mybatis的几个核心类SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession之间的关系;
大致流程是这个样子的,mybatis通过SqlSessionFactoryBuilder类的build方法和配置文件生成SqlSessionFactory对象,而SqlSession对象是通过SqlSessionFactory的open方法获取到的,这里我们说下这三个对象的生命周期,对于SqlSessionFactoryBuilder来说,他是为了创建SqlSessionFactory而存在的,SqlSessionFactory创建完成后就可以丢弃不要了,而SqlSessionFactory是创建SqlSession的基础,只要程序在运行,则SqlSessionFactory必须存在,所以SqlSessionFactory可以做成单例存在程序运行期间,而SqlSession由于是线程不安全的,所以存在方法体内或者一次请求的对象更合适;
生成SqlSession的代码如下:
public void getSqlSession(){
Reader reader = null;
try {
reader = Resources.getResourceAsReader("com/xxx/config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession =sessionFactory.openSession();
} catch (IOException e) {
e.printStackTrace();
}
}
而springboot整合mybatis时做了一些改变,SqlSessionFactoryBean代替了SqlSessionFactoryBuilder的用,SqlSessionTemplate代替了Sqlsession,看下面的代码我们通过注解的方式自定义了SqlSessionFactory和SqlSessionTemplate,接下来我们从注入看起,看看spring是如何整合mybatis的;
package com.spring.mybatis.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@Configuration
@EnableTransactionManagement //开启注解事物管理
@MapperScan(basePackages = "com.spring.mybatis.mapper" ,sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfig {
@Autowired
@Qualifier("dataSource")
private DataSource dataSource;
//配置sqlSessionFactory
//自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Bean(name = "sqlSessionFactory")
@Primary
public SqlSessionFactory getSqlSessionFactory() throws IOException {
SqlSessionFactory sessionFactory = null;
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
/*
Resource[] resources = new Resource[1];
Resource方式只能一个一个列出mapper文件
Resource resource = new ClassPathResource("mapping/DemoMapper.xml");
resources[0] = resource;*/
//PathMatchingResourcePatternResolver方式可以使用正则匹配
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//设置mapper文件的路径Resource
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapping/*.xml"));
//给entity取别名,这样在mapper文件中就可以使用缩写,默认别名是类名,不区分大小写
sessionFactoryBean.setTypeAliasesPackage("com.spring.mybatis.model");
//设置数据源DataSource
sessionFactoryBean.setDataSource(this.dataSource);
Properties mybatisProperties = new Properties();
mybatisProperties.setProperty("dialect", "mysql");
//设置Properties属性
sessionFactoryBean.setConfigurationProperties(mybatisProperties);
try {
/* 通过sessionFactoryBean获取sessionFactory配置文件,其实底层就是通过解析配置文件生成Configuration对象
再通过SqlSessionFactoryBuild的build方法生成sessionFactory,return this.sqlSessionFactoryBuilder.build(configuration);
有兴趣的可以自己通过debug跟一下,最后生成的是DefaultSqlSessionFactory对象*/
sessionFactory = sessionFactoryBean.getObject();
sessionFactory.getConfiguration().setCacheEnabled(true);
sessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);
return sessionFactory;
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}
}
分析万SqlSessionFactory对象的生成,我们再来看一下SqlSessionTemplate对象;
//配置sqlSessionTemplate
@Bean(name = "sqlSessionTemplate")
@Primary
public SqlSessionTemplate getSqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
//设置sqlSessionFactory对象
this.sqlSessionFactory = sqlSessionFactory;
//executorType=SIMPLE
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//sqlSessionProxy是一个SqlSession动态代理对象
//newProxyInstance方法有三个参数,第一个参数是生成代理对象的类加载器,第二个参数是
//生成那个对象的代理对象,接口显示,第三个参数是是指明生成代理对象要做的事情
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
可能很多读者可能已经猜出来了这里生成SqlSession的动态代理的用处,我们前面说过,SqlSession是线程不安全的,我们之前都是要用的时候通过SqlSessionFactory open一个SqlSession,用完就关闭,但是我们现在用的SqlSessionTemplate是一个单例的,他里面也只要只有一个SqlSession对象,那么我们怎么保证的线程安全呢,这就是要用到SqlSession动态代理的用处了,我们可以看看SqlSessionInterceptor做了什么:
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
我们主要看的是生成SqlSession这一行,这个方法可以从spring事物的上下文获取事物范围内的SqlSession:
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
//从当前线程的threadLocal 中获取sqlSessionHolder
//根据SqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder
//当SqlSessionFactory创建一个SqlSession时,就会在事物管理器中添加一对映射,
//key=SqlSessionFactory,value=SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//获取新的sqlSession 对象。这里由sessionFacory产生的defaultSqlSession
session = sessionFactory.openSession(executorType);
//判断当前是否存在事务,将sqlSession 绑定到sqlSessionHolder 中,并放到threadLoacl 当中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
这里可以看出SqlSession是视情况是否创建新的SqlSession然后执行,结果很清晰了,通过代理SqlSession的好处就是每次请求时都能保证SqlSession是线程安全的;
在mybatis中,是用MapperProxy动态代理我们的dao的,也就是说当我们调用dao的方法时,其实是调用的MapperProxy代理对象,接下来我们看看怎么获取MapperProxy对象吧:
先看看SqlSessionTemplate的getMapper方法:
通过debug很容易看出来,生成了一个mapperProxy对象代理我们的dao,我这里dao是DemoMapper,当我们调用DemoMapper接口的方法时,会调用的invoke方法,下面是mapperProxy类的一部分:
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
未完待续。。。。