(六)Spring-事务式声明
(六)Spring-事务式声明
一、回顾事务
1.1 事务特性:
- ACID
- 把一组业务当成一个业务来做,要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性问题。不能马虎!
- 确保完整性和一致性!
1.2 事务ACID原则:
- 原子性 A
- 一致性 C
- 隔离性 I
- 多个业务可能操作同一个资源,如一条记录,防止数据损坏即确保完整性和一致性。
- 持久性 D
二、spring中的事务管理
2.1 声明式事务:AOP
1 编写service层业务逻辑,包含事务
-
service层实现,通过容器注入属性mapper
-
在业务逻辑中使用mapper即dao层
-
编写事务,
doTx()方法先添加后修改user,中间制造错误
,如下:
package com.happy.service.user.impl;
import com.happy.mapper.user.UserMapper;
import com.happy.pojo.user.User;
import com.happy.service.user.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userMapper")
UserMapper userMapper;
@Override
public List<User> getUserList() {
List<User> userList = userMapper.getUserList();
return userList;
}
@Override
public User getUserById(int id) {
User user = userMapper.getUserById(id);
return user;
}
@Override
public int addUser(User user) {
int result = userMapper.addUser(user);
return result;
}
@Override
public int deleteUser(User user) {
int result = userMapper.deleteUser(user);
return result;
}
@Override
public int updateUser(User user) {
int result = userMapper.updateUser(user);
return result;
}
@Override
public void doTx(){
List<User> userList = userMapper.getUserList();
System.out.println(userList);
System.out.println("==========执行一组业务,先增后删==============");
User user = new User(521, "gao518", "1987518");
/*1 增加*/
System.out.println("1.增加用户");
userMapper.addUser(user);
/*制造报错*/
int error=1/0;
/*2 删除*/
User user2 = new User(520, "gao518518", null);
System.out.println("2.删除用户");
userMapper.updateUser(user2);
userList = userMapper.getUserList();
System.out.println(userList);
}
}
2 编写mapper接口和mapper.xml文件
package com.happy.mapper.user;
import com.happy.pojo.user.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
User getUserById(@Param("id") int id);
/*增*/
int addUser(User user);
/*删*/
int deleteUser(User user);
/*改*/
int updateUser(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.happy.mapper.user.UserMapper">
<!-- <select id="getUserList" resultType="user">-->
<!-- select * from user;-->
<!-- </select>-->
<select id="getUserById" resultType="user">
select *
from user
where id = #{id};
</select>
<insert id="addUser" parameterType="user">
insert into user(`id`, `name`, `pwd`)
values (#{id}, #{name}, #{pwd})
</insert>
<delete id="deleteUser" parameterType="user">
<if test="id!=null">
delete from user where id=#{id};
</if>
</delete>
<update id="updateUser" parameterType="user">
<if test="id!=null">
update user
<set>
<if test="name!=null">
name=#{name},
</if>
<if test="pwd!=null">
pwd=#{pwd},
</if>
</set>
where id=#{id};
</if>
</update>
</mapper>
3 开启Spring的事务处理功能
在相关的spring xml中如spring-mybatis.xml,要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager
对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
4 配置切面(通知)和切点
-
配置切面
- 结合aop实现事务的织入
- 配置事务的类即:定义事务切面和通知即事务增强方法
经过实测,方法名必须和严格一致或使用通配符,如method name="do*" propagation="REQUIRED"
- 配置事务的传播特性
-
配置切点
- 即配置通知的作用点
- 作用点一般定义在service层,请注意
- service层,作用在接口和实现类均可。即可以是com.happy.service.user.UserService.,也可以是com.happy.service.user.impl.UserServiceImpl.
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://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/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="db.properties"/>
<!--注册bean-->
<!--1.注册一个datasource,使用spring的datasource替换mybatis的配置,c3p0,dbcp,druid-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--2.注册一个sqlSessionFactory 用于生成sqlSession-->
<!-- String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--<property name="mapperLocations" value="classpath:com/happy/mapper/user/UserMapper.xml"></property>-->
</bean>
<!--3.注册一个MapperFactoryBean 用于获取mapper-->
<!--需要关联UserMapper 相当于原来的sqlSession.getMapper(UserMapper.class)-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.happy.mapper.user.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!--spring开启事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
<!-- <constructor-arg name="dataSource" ref="dataSource" />-->
</bean>
<!--结合aop实现事务的织入-->
<!--1.配置事务的类即:定义事务切面和通知即事务增强方法-->
<!--2.配置事务的传播特性-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="addUser" propagation="REQUIRED"/>
<tx:method name="deleteUser" propagation="REQUIRED"/>
<tx:method name="updateUser" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED"/>
<!--get方法即查询不需要事务-->
<tx:method name="getUserList" read-only="true"/>
<tx:method name="getUserById" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--定义aop事务管理的切点-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.happy.service.*.*.*(..))"/>
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="txPointCut"></aop:advisor>
</aop:config>
<!--<tx:jta-transaction-manager />-->
</beans>
5 模拟controller调用service层方法,测试事务
- 注意:
必须从容器获取service层实例context.getBean("userService", UserService.class)
;,不能自己用构造器new
package com.happy.service.user;
import com.happy.pojo.user.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class UserServiceTest {
@Test
public void testGetUserList() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
UserService userService = context.getBean("userService", UserService.class);
List<User> userList = userService.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void testGetUserById() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
UserService userService = context.getBean("userService", UserService.class);
User user = userService.getUserById(3);
System.out.println(user);
}
@Test
public void testTransaction() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
UserService userService = context.getBean("userService", UserService.class);
//注意:必须是被容器管理的对象,执行相关方法,才能被AOP拦截。
userService.doTx();
}
}
2.2 编程式事务:需要在代码中,进行事务的管理。
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);
}
}
-
需要在原有业务逻辑上,通过手动代码实现事务,即try-catch+rollback
-
要改变代码,现在一般不用了
2.3 小结
思考:
为什么需要事务
- 如果不配置事务,可能存在数据提交不一致的情况。如银行转账绝对不允许。
- 如果我们不在spring中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中是否重要,涉及到数据的一致性和完整性问题,不容马虎。
- 工作的时候,都建议配置上声明式事务,都是死的。