ssm-spring集成mybatis事务
事务
MyBatis-Spring库的引入,无需创建新的MyBatis事务管理器,就能使MyBatis接入到Spring事。 引入的方式既可以是注解,也可以是aop。未配置事务实例
首先来看看未配置事务时,执行一组先增加后删除(删除异常)的数据库语句场景。数据库连接配置延用之前的,这里不再介绍。- 编写DAO
- mapper配置文件中编写对应sql:
- 分别编写测试方法测试正确的增加和删除: 增加:
- 模拟异常事务 现在模拟场景:新增后立即删除。如果新增成功但是删除失败,此时我们的需求期望时整个过程需要回滚。现在先将删除sql故意写错来模拟,看看异常现象:
public interface StudentMapper {
void add(Map<Object, Object> student);
void delete(String name);
}
声明了新增和删除两个接口。
<?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="com.zx.demo.mybatis.spring.mapper.StudentMapper">
<insert id="add">
INSERT INTO student (`name`,sex) VALUES (#{name},#{sex});
</insert>
<delete id="delete">
DELETE FROM student WHERE `name` = #{name};
</delete>
</mapper>
@Test
public void testTransaction() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
StudentMapper mapper = (StudentMapper) context.getBean("exampleMapper");
Map<Object, Object> student = new HashMap<>();
student.put("name", "曹操");
student.put("sex", "男");
mapper.add(student);
}
运行后确定数据库中有写入一条新的数据:删除:
@Test
public void testTransaction() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
StudentMapper mapper = (StudentMapper) context.getBean("exampleMapper");
mapper.delete("曹操");
}
运行后正确的将刚才新增的“曹操”记录删除:<delete id="delete">
DELETE FROM student WHERE `name` = #{name}s;
</delete>
@Test
public void testTransaction() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
StudentMapper mapper = (StudentMapper) context.getBean("exampleMapper");
Map<Object, Object> student = new HashMap<>();
student.put("name", "曹操");
student.put("sex", "男");
mapper.add(student);
mapper.delete("曹操");
}
运行时发现,新增成功但是删除时抛出了语法错误的异常:我们看看数据库结果:
虽然整个过程的第二步删除失败了,但数据还是写入到了数据库,这显然与需求不符,下面我们来看看Mybatis-Spring是如何管理事务的。
Mybatis-Spring事务
基于上面的需求,我们现在来看看通过事务,将写入和删除一致性实现。按照官方的事务配置说明,这里介绍注解式事务实现。- 将数据库操作从test中抽离到业务接口
public class StudentServiceImpl implements StudentService { private StudentMapper mapper;
public StudentMapper getMapper() { return mapper; } public void setMapper(StudentMapper mapper) { this.mapper = mapper; } @Override public void handle() throws Exception { Map<Object, Object> student = new HashMap<>(); student.put("name", "曹操"); student.put("sex", "男"); mapper.add(student); mapper.delete("曹操"); }
}
<!--业务实现类,内部通过mapper接口实现了数据业务操作-->
<bean id="studentServiceImpl" class="com.zx.demo.mybatis.spring.service.StudentServiceImpl">
<property name="mapper" ref="exampleMapper"/>
</bean>
<!--配置Spring数据源事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--使用上面声明的数据源-->
<constructor-arg ref="dataSource"/>
</bean>
注意:提供给transactionManager的数据源一定和创建sqlSessionFactory使用同一个,否则无法生效。
<!--aop注入-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--这里一定是注入到mapper对应的业务接口或其实现方法上,否则不生效,也不要用在@Test方法上-->
<!--<aop:pointcut id="p" expression="execution( com.zx.demo.mybatis.spring.service.StudentServiceImpl.(..))"/>-->
<aop:pointcut id="p" expression="execution( com.zx.demo.mybatis.spring.service.StudentService.(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"/>
</aop:config>
注意:aop切入点一定是注入到mapper对应的业务接口或其实现方法上,否则不生效,也不要用在@Test方法上
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置数据源,可以是实现了javax.sql.DataSource接口的任意数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxxx"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</bean>
<!--1、SqlSessionFactoryBean:实现了接口org.springframework.beans.factory.FactoryBean-->
<!--2、SqlSessionFactory:通过SqlSessionFactoryBean对象的getObject()方法来构建-->
<!--3、dataSource和mapperLocations:getObject()方法中创建SqlSessionFactory对象的属性-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:com/zx/demo/mybatis/**/.xml"/>
</bean>
<!--1、MapperFactoryBean:实现了接口org.springframework.beans.factory.FactoryBean-->
<!--2、StudentMapper:通过MapperFactoryBean对象的getObject()方法来构建-->
<!--3、mapperInterface和sqlSessionFactory:getObject()方法中创建StudentMapper接口代理实现对象的属性-->
<bean id="exampleMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.zx.demo.mybatis.spring.mapper.StudentMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!--业务实现类,内部通过mapper接口实现了数据业务操作-->
<bean id="studentServiceImpl" class="com.zx.demo.mybatis.spring.service.StudentServiceImpl">
<property name="mapper" ref="exampleMapper"/>
</bean>
<!--开启Spring数据源事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--使用上面声明的数据源-->
<constructor-arg ref="dataSource"/>
</bean>
<!--aop注入-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--这里一定是注入到mapper对应的业务接口或其实现方法上,否则不生效,也不要用在@Test方法上-->
<!--<aop:pointcut id="p" expression="execution(* com.zx.demo.mybatis.spring.service.StudentServiceImpl.(..))"/>-->
<aop:pointcut id="p" expression="execution( com.zx.demo.mybatis.spring.service.StudentService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"/>
</aop:config>
</beans>
@Test
public void testTransaction() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
StudentService studentService = (StudentService) context.getBean("studentServiceImpl");
studentService.handle();
}
删除语句依旧报语法错误,我们到数据库中看是否插入成功:
可以看出,并没有写入“曹操”这条数据,说明事务生效。