(0) Spring和MyBatis集成实例
通过MapperFactoryBean工厂类进行单个配置
1.创建Mapper接口有两种方式,可以通过注解@Mapper也可以通过XML配置文件实现
- 通过注解@Mapper实现Dao接口
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{id}")
public User getUser(int id);
public void insertUser(User user);
public void updateUserScore(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
- 通过XML配置文件实现Dao接口
public interface UserMapper {
public User getUser(int id);
public void insertUser(User user);
public void updateUserScore(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.mapping.UserMapper">
<cache/>
<select id="getUser" parameterType="int" resultType="mybatis.User" useCache="true">
select * from user where id=#{id}
</select>
<insert id="insertUser" parameterType="mybatis.User">
INSERT INTO user (name,password,score) VALUES (#{name},#{password},#{score})
</insert>
<update id="updateUserScore" parameterType="mybatis.User">
UPDATE user set score = #{score} where id=#{id}
</update>
</mapper>
2.创建Dao接口完成后,将spring的dataSource数据库连接注入到sqlSessionFactoryBean中生成sqlSession实例
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:/config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mybatis.mapping.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
3.通过sqlSession获取Dao层Mapper实例有两种方法,一种是通过MapperFactoryBean创建单个Mapper实例,另一种是通过自动扫描,获取工程内所有的Mapper实例;
- 将生成的SqlSessionFactory注入到MapperFactoryBean用于生成单个userMapper实例
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mybatis.mapping.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
- 将生成的SqlSessionFactory注入到MapperScannerConfigurer中,自动扫描工程获取全部Mapper实例
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="mybatis.mapping"/>
</bean>
(1) Spring和MyBatis集成原理
1.通过MapperFactory进行的单个Mapper实例获取
- 将Mapper接口和sqlSessionFactory注入到MapperFactoryBean中
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mybatis.mapping.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
- 通过MapperFactoryBean的getObject()方法获取Mapper实例,通过getMapper()方法实现
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
2.通过MapperScannerConfigurer进行Mapper实例扫描获取
在容器ApplicationContext实例化SqlSessionFactory
- SqlSessionFactoryBean类实现了InitializingBean接口,所以会在SqlSessionFactoryBean工程类实例创建完成后执行afterPropertiesSet方法,在afterPropertiesSet方法中会执行buildSqlSessionFactory方法生成一个sqlSessionFactory对象,用于getObject()方法返回SqlSessionFactory;SqlSessionFactrory的创建是通过解析XML文件,同时注入Spring的DataSource创建Conguration实例;
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//实际就是将扫描到的接口包装成MapperFactoryBean的实现类
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//注入sqlSessionFactory对象,这个也很重要
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
return beanDefinitions;
}
在容器ApplicationContext实例化MapperScannerConfigurer扫描配置类
- MapperScannerConfigurer对象的初始化过程,这个对象实现了BeanDefinitionRegistryPostProcessor接口,实现了该接口后在ApplicationContext初始化扫描applicationContext.xml配置文件注册BeanDefinition后执行postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if(this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
- 在postProcessBeanDefinitionRegistry方法中初始化一个对象ClassPathMapperScanner,并讲执行scan—>doScan方法,扫描指定包下的所有Mapper类(@Mapper定义或者XML配置)注册到spring容器中,包装为MapperFactoryBean
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//实际就是将扫描到的接口包装成MapperFactoryBean的实现类
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//注入sqlSessionFactory对象,这个也很重要
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
return beanDefinitions;
}
- 每一个Dao层Mapper类都在Spring容器内实例化一MapperFactoryBean,MapperFactoryBean继承自SqlSessionDaoSupport,需要通过setSqlSessionFactory()方法注入SqlSessionFactory用来创建会话SqlSessionTemplate类型的SqlSession
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if(!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
- 每个Mapper类都有一个单例的SqlSessionTemplate类型的sqlSession用来管理线程不安全的SqlSession,底层通过ThreadLocal实现;SqlSessionTemplate执行具体的数据库操作是通过内部的代理类SqlSessionProxy实现
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property \'sqlSessionFactory\' is required");
Assert.notNull(executorType, "Property \'executorType\' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
- SqlSessionProxy是通过JDK动态代理实现的类,在执行具体的数据库操作之前会尝试获取与该线程绑定的sqlSession,如果没有绑定则从新创建,如果已经绑定则直接使用;
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 t = method.invoke(sqlSession, args);
if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = t;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
DataAccessException 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;
}
}
- 在动态代理类内如果Spring开启了事务则通过TransactionSynchronizationManager内部的ThreadLocal实现保证了非线程安全的sqlSession的线程安全实现;如果Spring未开启事务则TransactionSynchronizationManager内部为空,每次都会创建一个SqlSession实例,增删查改执行完成后进行事务提交;
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
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");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
- 创建完成MapperFactoryBean后会在容器实例化时候调用getObject()方法进行实例化,对Mapper类会通过JDK动态代理生成实现类实例放入容器中;调用实现类实例相关方法时,会通过sqlSessionTemplate执行,会先获取到线程绑定的sqlSession后,再通过SqlSession执行相应的数据库操作;
3.MyBatis与Spring集成后的事务问题
MyBatis与Spring集成后默认将事务托管给Spring,如果Spring未开启事务管理则MyBatis也不会进行事务管理;MyBatis不进行事务的原理是每次增删查改数据库操作都创建一个sqlSession实例,数据库操作完成后会立即进行提交,这样就变成了无事务操作;
-
MyBatis与Spring集成后的事务问题主要变现在是否将线程绑定的Connection注入到TransactionSynchronizetionManager中
-
MyBatis与Spring集成并且Spring开启事务使用@Transaction,会在事务代理类中将线程绑定的Connection连接注入TransactionSynchronizetionManager中,根据TransactionSynchronizetionManager可以获取与线程绑定的sqlSession
-
MyBatis与Spring集成并且Spring未开启事务,则从TransactionSynchronizetionManager不能获取与线程绑定的sqlSession,每次执行数据库操作时都会通过sessionFactory.openSession()方法冲重新创建sqlSession;
-
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
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");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
总结
-
MyBatis与Spring集成的难点在于将线程不安全的sqlSession转变成线程安全的,Spring的解决方案是通过对每个Mapper类创建线程安全的sqlSessionTemplate作为数据库操作的入口,任何通过sqlSessionTemplate执行数据库操作的线程都会尝试获取与线程绑定的sqlSession,因此每个线程执行多步数据库操作使用的是同一个sqlSession,这样就保证了线程安全;
-
MyBatis管理事务时,每个SqlSession绑定一个Executor,每个Executor绑定一个connection,当执行sqlSession.close()关闭事务的时,会调用connection.close()将数据库连接放回连接池;
-
SqlSession内绑定着一个Conection用来具体的数据库操作,connection数据库连接是有状态的,因此SqlSession是有状态的,线程不安全的
-
在MyBatis与Spring集成中会为每个Mapper类创建一个sqlSessionTemplate实例,当多个线层对同一个Mapper类进行操作时,是通过同一个sqlSessionTemplate实例实现的,因此sqlSessionTemplate是线程安全的;LZ感觉其实可以整个上下文创建一个单例的sqlSessionTemplate实例再注入到每个Mapper类中,这样多个线程多个Mapper共有一个线程安全的sqlSessionTemplate,没有必要每个Mapper类创建一个;
-
通过SqlSession执行数据库操作并且需要保证其线程安全的原因是为了事务操作,如果没有事务操作,直接通过connection进行数据库操作是最有效率的;
-
MyBatis与Spring集成后将事务操作完全托付给Spring,如果Spring未开启事务管理则默认每次增删查改操作都会创建一个新的sqlSession;
-
MyBatis与Spring集成后的整体逻辑过程是:
-
在容器ApplicationContext实例化SqlSessionFactory,注入spring的dataSource实例
-
在容器ApplicationContext注册MapperScannerConfigurer扫描配置类后会扫描特定包下的所有Mapper类,并将Mapper类包装成MapperFactoryBean注册到容器中(BeanDefinitionName);
- 在创建MapperFactoryBean时会在其中创建会话SqlSessionTemplate类
-
在容器applicationContext实例化时会通过getObject()方法创建Mapper类的JDK动态代理类的实例放入容器
-
调用时注入Mapper类的JDK动态代理类的实例,调用其相关方法,在动态代理实现类中方法的执行是通过sqlSessionTemplate实现的;
-
sqlSessionTemplate执行数据库操作是通过代理类,首先获取线程绑定sqlSession,再通过这个sqlSession执行的;
-