Spring的事务管理
事务的定义
事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败
事务的特性
- 原子性:事务不可分割
- 一致性:事务执行前后数据完整性保持一致
- 隔离性:一个事务的执行不应该受到其他事务的干扰
- 持久性:一旦事务结束,数据就持久化到数据库
如果不考虑隔离性可能引发的安全性问题
- 读问题
- 脏读:一个事务读到另一个事务未提交的数据
- 不可重复读:一个事务读到另一个事务已经提交的update的数据,导致一个事务中多次查询结果不一致
- 虚读、幻读:一个事务读到另一个事务已经提交的insert的数据,导致一个事务中多次查询结果不一致
- 写问题
- 丢失更新
解决读问题
设置事务的隔离级别
- Read uncommitted:未提交读,任何读问题都解决不了
- Read committed:已提交读,解决脏读,但是不可重复读和虚读都有可能发生(Oracle)
- Repeatable read:重复读,解决脏读和不可重复读,但是虚读有可能发生(mysql)
- Serializable:解决所有读问题
Spring的事务管理的API
PlatformTransactionManager:平台事务管理器
平台事务管理器:接口,是Spring用于管理事务真正的对象
- DataSourceTransactionManager:底层使用JDBC管理事务
- HibernateTransactionManager:底层使用Hibernate管理事务
TransactionDefinition:事务定义信息
事务定义:用于定义事务的相关信息,隔离级别、超时信息、传播行为、是否只读
TransactionStatus:事务的状态
事务状态:用于记录在事务管理过程中,事务的状态的对象
事务管理的API的关系
Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态的信息记录到事务状态的对象中
事务的传播行为
事务的传播行为用来解决业务层方法相互调用的问题
Spring中提供了七种事务传播行为,大体分为三类
- 保证同一个事务中
- PROPAGATION_REQUIRED 支持当前事务,如果不存在就新建一个(默认值)
- PROPAGATION_SUPPORTS 支持当前事务,如果不存在就不使用事务
- PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
- 保证没有在同一个事务中
- PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
- PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
- PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
- 如果当前事务存在,则嵌套事务执行
PROPAGATION_NESTED
Spring的事务管理
搭建转账环境
依赖添加
- Spring的四个核心依赖
- Spring的四个AOP依赖
- mysql数据库的jar包
- Spring-JDBC的jar包
- mybatis自身jar包和Spring支持mybatis框架的jar包
- c3p0的两个jar包
- Spring的测试包
- Junit的单元测试包
- get和set方法的辅助jar包
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance -->
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--spring整合mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/mchange-commons-java -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
创建数据库,账号,姓名,金钱
创建实体类
配置文件
-
mybatis-config.xml
- 配置打印sql语句日志
- 配置别名
<?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="logImpl" value="STDOUT_LOGGING"/> </settings> <typeAliases> <typeAlias type="com.aop.pojo.User" alias="User"></typeAlias> </typeAliases> </configuration>
-
application-context.xml
- 配置组件扫描
- AOP注解开发
- c3p0注入bean管理
- mybatis注入bean管理
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.aop"></context:component-scan> <!--开启AOP的注解开发--> <aop:aspectj-autoproxy/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test7?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false"></property> <property name="user" value="root"></property> <property name="password" value="cqrjxk39"></property> </bean> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="mapperLocations" value="classpath:com/aop/dao/*.xml"></property> <property name="configLocation" value="classpath:mybatis-cfg.xml"></property> </bean> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.aop.dao"></property> <property name="sqlSessionFactoryBeanName" value="sessionFactory"></property> </bean> </beans>
使用mybatis框架编写dao层,一个修改功能,一个查询功能
service层编写
为测试类提供转账业务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public void deal(String fromkid, double fromMoney, String tokid, double toMoney) {
User fromUser = new User();
User toUser = new User();
fromUser.setKid(fromkid);
fromUser.setMoney(fromMoney);
toUser.setKid(tokid);
toUser.setMoney(toMoney);
userDao.update(fromUser);
userDao.update(toUser);
}
}
测试类
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
public class TestDemo1 {
@Autowired
private UserDao userDao;
@Autowired
private UserService userService;
@Test
public void test1(){
System.out.println("转账前");
findMoney();
userService.deal("11111",-100,"22222",100);
System.out.println("转账后");
findMoney();
}
public void findMoney(){
User fromUser = userDao.find("11111");
User toUser = userDao.find("22222");
System.out.println("转账人:"+fromUser+"\t收账人:"+toUser);
}
}
产生的问题
如果在转账的过程中代码出现了异常情况,就会造成金钱丢失
Spring的事务管理
编程式事务
第一步:配置平台事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步:配置事务管理的模板
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
第三步:注入事务模板到业务层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
//注入事务模板
@Autowired
private TransactionTemplate transactionTemplate;
public void deal(final String fromkid, final String tokid, final double Money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
User fromUser = new User();
User toUser = new User();
fromUser.setKid(fromkid);
fromUser.setMoney(-Money);
toUser.setKid(tokid);
toUser.setMoney(Money);
userDao.update(fromUser);
int i = 1/0;
userDao.update(toUser);
}
});
}
}
声明式事务管理
通过AOP进行声明式事务管理
XML方式的声明式事务管理
第一步:引入AOP的开发环境(上面已引入AOP的jar包),引入tx的jar包
<!--https://mvnrepository.com/artifact/org.springframework/spring-tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
第二步:配置事务管理器
第三步:配置事务的增强规则
(在这里还可以配只读read-only,配超时timeout,timeout的值为-1表示没有过期时间)
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"></tx:method>
<tx:method name="update*" propagation="REQUIRED"></tx:method>
<tx:method name="delete*" propagation="REQUIRED"></tx:method>
<tx:method name="find*" read-only="true"></tx:method>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
第四步:配置切点
<aop:config>
<aop:pointcut id="pointCut1" expression="execution(* com.aop.service.impl.UserServiceImpl.deal(..))"></aop:pointcut>
<!--advisor是一个切入点和一个通知的组合,aspect是多个切入点和多个通知的组合-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut1"></aop:advisor>
</aop:config>
第五步:测试(Spring的AOP一章有)
注解方式的声明事务管理
第一步:引入AOP开发环境
第二步:配置事务管理器
第三步:开启注解事务
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
第四步:在业务层的类上添加注解@Transactional
该注解有两个重要的属性,isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,可以设置隔离级别和传播行为。