接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122104494?spm=1001.2014.3001.5502
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder
来创建 SqlSessionFactory
的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean
来创建。
要创建工厂 bean,将下面的代码放到 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
bean>
需要注意的是 SqlSessionFactoryBean
实现了 Spring 的 FactoryBean
接口。 这意味着由 Spring 最终创建的 bean 并不是 SqlSessionFactoryBean
本身,而是工厂类(SqlSessionFactoryBean
)的 getObject() 方法的返回结果。这种情况下,Spring 将会在应用启动时为你创建 SqlSessionFactory
,并使用 sqlSessionFactory
这个名字存储起来。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
等效的 Java 代码如下:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
}
通常,在 MyBatis-Spring 中,你不需要直接使用 SqlSessionFactoryBean
或对应的 SqlSessionFactory
。 相反,session 的工厂 bean 将会被注入到 MapperFactoryBean
或其它继承于 SqlSessionDaoSupport
的 DAO(Data Access Object,数据访问对象)中。如下:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="dao.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
SqlSessionFactory
有一个唯一的必要属性:用于 JDBC 的 DataSource
。这可以是任意的 DataSource
对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation
,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是
或
元素。【mybatis-config.xml】
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(
),数据源(
)和 MyBatis 的事务管理器(
)都会被忽略。 SqlSessionFactoryBean
会创建它自有的 MyBatis 环境配置(Environment
),并按要求设置自定义环境的值。
如果 MyBatis 在映射器类对应的路径下找不到与之相对应的映射器 XML 文件,那么也需要配置文件。这时有两种解决办法:第一种是手动在 MyBatis 的 XML 配置文件中的
部分中指定 XML 文件的类路径;第二种是设置工厂 bean 的 mapperLocations
属性。
mapperLocations
属性接受多个资源位置。这个属性可以用来指定 MyBatis 的映射器 XML 配置文件的位置。属性的值是一个 Ant 风格的字符串,可以指定加载一个目录中的所有文件,或者从一个目录开始递归搜索所有目录。比如:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
bean>
这会从类路径下加载所有在 sample.config.mappers
包和它的子包中的 MyBatis 映射器 XML 配置文件。
在容器管理事务的时候,你可能需要的一个属性是 transactionFactoryClass
。
如果你使用了多个数据库,那么需要设置 databaseIdProvider
属性:
<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties">
<props>
<prop key="SQL Server">sqlserverprop>
<prop key="DB2">db2prop>
<prop key="Oracle">oracleprop>
<prop key="MySQL">mysqlprop>
props>
property>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
<property name="databaseIdProvider" ref="databaseIdProvider"/>
bean>
一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager
来实现事务管理。
一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional
注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession
对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。
事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。
要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager
对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
bean>
或者是:
@Configuration
public class DataSourceConfig {
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
传入的 DataSource
可以是任何能够与 Spring 兼容的 JDBC DataSource
。包括连接池和通过 JNDI 查找获得的 DataSource
。
注意:为事务管理器指定的 DataSource
必须和用来创建 SqlSessionFactoryBean
的是同一个数据源,否则事务管理器就无法工作了。
还有要注意 spring 中的事务管理,下面是使用 @Transactional
注解进行事务管理设置,需要 配置下面的内容:
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
bean>
您也可以通过 @Configuration
类中的 @EnableTransactionManagement
注解使 bean 实例具有事务性。
关于 spring 中事务管理请看博主的另外一篇文章:https://blog.csdn.net/qq_43605444/article/details/122086818?spm=1001.2014.3001.5502
如果你正使用一个 JEE 容器而且想让 Spring 参与到容器管理事务(Container managed transactions,CMT)的过程中,那么 Spring 应该被设置为使用 JtaTransactionManager
或由容器指定的一个子类作为事务管理器。最简单的方式是使用 Spring 的事务命名空间或使用 JtaTransactionManagerFactoryBean
:
<tx:jta-transaction-manager />
@Configuration
public class DataSourceConfig {
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManagerFactoryBean().getObject();
}
}
在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个 SqlSession
。 如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。
注意,如果你想使用由容器管理的事务,而不想使用 Spring 的事务管理,你就不能配置任何的 Spring 事务管理器。并必须配置 SqlSessionFactoryBean
以使用基本的 MyBatis 的 ManagedTransactionFactory
:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
property>
bean>
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setTransactionFactory(new ManagedTransactionFactory());
return factoryBean.getObject();
}
}
MyBatis 的 SqlSession
提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession
或映射器。也就是说,Spring 总是为你处理了事务。
你不能在 Spring 管理的 SqlSession
上调用 SqlSession.commit()
,SqlSession.rollback()
或 SqlSession.close()
方法。如果这样做了,就会抛出 UnsupportedOperationException
异常。在使用注入的映射器时,这些方法也不会暴露出来。
无论 JDBC 连接是否设置为自动提交,调用 SqlSession
数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
下面的代码展示了如何使用 PlatformTransactionManager
手工管理事务。
public class UserService {
private final PlatformTransactionManager transactionManager;
public UserService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void createUser() {
TransactionStatus txStatus =
transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userMapper.insertUser(user);
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
}
}
在使用 TransactionTemplate
的时候,可以省略对 commit
和 rollback
方法的调用。
public class UserService {
private final PlatformTransactionManager transactionManager;
public UserService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void createUser() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
userMapper.insertUser(user);
return null;
});
}
}
注意:虽然这段代码使用的是一个映射器,但换成 SqlSession 也是可以工作的。
首先是准备一个简单的数据库:
create tables account (id int primary key auto_increment, username varchar(25), money int);
insert into account values(null,"张三",1000);
insert into account values(null,"李四",1000);
查看数据库数据:
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 1000 |
+----+----------+-------+
2 rows in set (0.00 sec)
创建对应的 pojo 实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private Integer id;
private String username;
private Integer money;
}
创建对应的 mapper 接口:
public interface AccountMapper {
@Update("update account set money = money - #{money} where id = #{id}")
void deAccount(@Param("id") Integer id, @Param("money") Integer money);
@Update("update account set money = money + #{money} where id = #{id}")
void upAccount(@Param("id") Integer id, @Param("money") Integer money);
}
创建一个服务类:
@Component
@Transactional
public class AccountService {
@Autowired
private AccountMapper accountMapper;
// 模拟借钱示例
public void rent() {
// 张三减少 100
accountMapper.deAccount(1,100);
// 模拟异常
int a = 1/0;
// 李四增加 100
accountMapper.upAccount(2,100);
}
}
在 spring 配置文件中配置如下内容:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="service"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
bean>
<bean id="accountMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="dao.AccountMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
beans>
最后编写简单的测试代码:
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans1.xml");
AccountService service = context.getBean(AccountService.class);
// 模拟借钱场景
service.rent();
}
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 1000 |
+----+----------+-------+
2 rows in set (0.00 sec)
从运行结果可以看到,事务管理配置已经成功配置,执行前的数据库中的数据和异常发生后回滚事务的数据库数据一致。
最后附上 mybatis-spring 官网地址:http://mybatis.org/spring/zh/sqlsession.html