Spring集成MyBatis
使用
- 配置数据源
<!--配置dataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</bean>
- 配置SqlSessionFactoryBean
<!--配置sqlSessionFactory, 这里使用了SqlSessionFacotryBean,它继承了FactoryBean接口,可以让mybatis自定义生成我们的sqlSessionFactory
-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="myBatis_Spring.entity"/>
</bean>
- 扫描mapper接口,并为每个mapper接口代理对象
<!--扫描所有mapper,并生成代理对象-->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="myBatis_Spring.mapper"/>
</bean>
对象说明
FactoryBean: 工厂Bean用于自定义生成Bean对象。当在ioc 中配置FactoryBean 的实例时,最终通过bean id 对应的是FactoryBean.getObject()实例,而非FactoryBean 实例本身
SqlSessionFactoryBean: 生成SqlSessionFactory 实例,该为单例对像,作用于整个应用生命周期。
MapperScannerConfigurer: 扫描我们写的mapper接口,并在spring容器初始化时为我们创建一个单例的Mapper代理对象。
线程安全问题
和原生mybatis不同的是,创建出来的mapper是一个单例对象,按理说会存在线程安全问题。但是spring帮我们解决了这一点,当spring容器初始化的时候,会生成mapper的一个代理对象。每当mapper调用它的任何一个方法,都会经过代理对象的invoke方法。在这个invoke方法中,每次都会创建一个新的SqlSession,当这个方法调用完成后SqlSession方法也会随之销毁或者释放。
/*
SqlSessionTemplate > SqlSessionInterceptor > invoke()
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取SqlSession对象
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;
....
....
一级缓存还会生效吗
如果每次执行一个查询都会创建一个新的SqlSession,那么一级缓存不是不起作用了吗?
确实如此,但是当我们Service层方法加了@Transactional后,在这个有事务的方法内,第一次查询数据库,会把新建的SqlSession对象存入ThreadLoacl中,下一次进行查询时会先判断这个ThreadLocal中是否有对应的SqlSession对象。
/*
SqlSessionUtils > getSqlSession()
*/
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);
//从ThreadLoacl中获取SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//如果ThreadLoacl中没有的话就创建一个新的SqlSession
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}