Spring中的事务
1.事务回顾
事务:是逻辑上一组操作,要么全都成功,要么全都失败. 事务特性:ACID
原子性:事务不可分割 一致性:事务执行的前后,数据完整性保持一致. 隔离性:一个事务执行的时候,不应该受到其他事务的打扰 持久性:一旦结束,数据就永久的保存到数据库.
如果不考虑隔离性: 脏读:一个事务读到另一个事务未提交数据 不可重复读:一个事务读到另一个事务已经提交数据(update)导致一个事务多次查询结果不一致 虚读:一个事务读到另一个事务已经提交数据(insert)导致一个事务多次查询结果不一致
事务的隔离级别: 未提交读:以上情况都有可能发生。 已提交读:避免脏读,但不可重复读,虚读是有可能发生。 可重复读:避免脏读,不可重复读,但是虚读有可能发生。 串行的:避免以上所有情况.
2.Spring中的事务
Spring中事务管理 分层开发:事务处在Service层.
Spring提供事务管理API
PlatformTransactionManager:平台事务管理器.
getTransaction(TransactionDefinition definition)
rollback(TransactionStatus status)
commit(TransactionStatus status)
TransactionDefinition:事务定义
ISOLation_XXX:事务隔离级别.
PROPAGATION_XXX:事务的传播行为.
TransactionStatus:事务状态 是否有保存点 是否是一个新的事务 事务是否已经提交 关系:PlatformTransactionManager通过TransactionDefinition设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由TransactionStatus记录。
API详解: PlatformTransactionManager:接口. Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现
使用Spring JDBC或iBatis 进行持久化数据时使用(重点) org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Hibernate进行持久化数据时使用 org.springframework.orm.hibernate.HibernateTransactionManager
使用JPA进行持久化时使用
org.springframework.orm.jpa.JpaTransactionManager
当持久化机制是Jdo时使用
org.springframework.jdo.JdoTransactionManager
使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用 org.springframework.transaction.jta.JtaTransactionManager
TransactionDefinition: ISOLATION_DEFAULT:默认级别.
Mysql --> repeatable_read | Oracle -->> read_commited
级别如下:
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
3.Sping中事务的传播行为
事务的传播行为:(不是JDBC事务管理,用来解决实际开发的问题.) 传播行为:解决业务层之间的调用的事务的关系.
PROPAGATION_REQUIRED: 支持当前事务,如果不存在 就新建一个
A,B 如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务.(A,B是在一个事务中。)
PROPAGATION_SUPPORTS: 支持当前事务,如果不存在,就不使用事务
A,B 如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务.
PROPAGATION_MANDATORY: 支持当前事务,如果不存在,抛出异常
A,B 如果A有事务,B使用A的事务,如果A没有事务,抛出异常.
PROPAGATION_REQUIRES_NEW: 如果有事务存在,挂起当前事务,创建一个新的事务
A,B 如果A有事务,B将A的事务挂起,重新创建一个新的事务.(A,B不在一个事务中.事务互不影响.)
PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果有事务存在,挂起当前事务
A,B 非事务的方式运行,A有事务,就会挂起当前的事务.
PROPAGATION_NEVER: 以非事务方式运行,如果有事务存在,抛出异常 PROPAGATION_NESTED: 如果当前事务存在,则嵌套事务执行
基于SavePoint技术.
A,B A有事务,A执行之后,将A事务执行之后的内容保存到SavePoint.B事务有异常的话,用户需要自己设置事务提交还是回滚.
常用:(重点) PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_NESTED
4.Spring的事务管理
Spring的事务管理分成两类 编程式事务管理: 手动编写代码完成事务管理.(了解) 声明式事务管理: 不需要手动编写代码,配置.
5.Spring的事务案例
模拟两个人转账的问题,当我们没有加入事务操作的时候,假如转出操作执行了,转出操作和转入操作之间抛出了异常,大家都知道,程序遇到异常的时候会中止,所以出现的结果就是这个人转出钱了,另一个人没收到钱,这个就出现了很大的bug,因此我们引入了事务的操作
5.1手动编码的方式完成事务管理(了解)
缺点:代码量增加,代码有侵入性.
(1)创建数据库和money表
(2)创建db.properties文件
db.driverClassName = com.mysql.cj.jdbc.Driver
db.url = jdbc:mysql://localhost:3306/javacoffee?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
db.username=root
db.password=1234
(3)导入依赖,这里我就直接都复制出来了,包括下文所需的包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
(4)创建mapper 和 实现类 并定义转出钱和增加钱的方法
package com.coffee.mapper;
public interface Money {
public void setMoney(String money,Integer id);
public void getMoney(String money,Integer id);
}
package com.coffee.mapper.impl;
import com.coffee.mapper.Money;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class MoneyImpl extends JdbcDaoSupport implements Money {
@Override
public void setMoney(String money,Integer id) {
String sql = "update money set money = money - ? where id = ?";
getJdbcTemplate().update(sql,money,id);
}
@Override
public void getMoney(String money,Integer id) {
String sql = "update money set money = money + ? where id = ?";
getJdbcTemplate().update(sql,money,id);
}
}
(5)创建service 以及它的实现类
package com.coffee.service;
import java.sql.SQLException;
public interface MoneyService {
public void money(String money,Integer id1,Integer id2) throws SQLException;
}
在实现类里面,使用了事务模板对象
package com.coffee.service.impl;
import com.coffee.mapper.impl.MoneyImpl;
import com.coffee.service.MoneyService;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
public class MoneyServiceImpl implements MoneyService {
private MoneyImpl moneyImpl;
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void setMoneyImpl(MoneyImpl moneyImpl) {
this.moneyImpl = moneyImpl;
}
@Override
public void money(String money, Integer id1, Integer id2) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
moneyImpl.setMoney(money,id1);
// 这里可以写一个异常来看看事务回滚是否执行了
// int sb= 1/0;
moneyImpl.getMoney(money,id2);
}
});
}
}
(6)在applicationContext.xml文件里完成依赖注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans">
<!-- 扫描文件,获取文件中存储的数据库属性-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 注入四个属性,获取道数据库连接对象datasource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="url" value="${db.url}"></property>
<property name="driverClassName" value="${db.driverClassName}"></property>
</bean>
<!-- 事务核心管理器,封装的所有的事务操作,十分重要-->
<bean id="dataSourceTransactionManager" 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="dataSourceTransactionManager"></property>
</bean>
<!-- mapper层,使用了JdbcTemplate来进行修改的操作,因此需要传入datasource-->
<bean id="moneyImpl" class="com.coffee.mapper.impl.MoneyImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- service层,将mapper层中获取的对象进行依赖注入,以及事务模板对象-->
<bean id="moneyService" class="com.coffee.service.impl.MoneyServiceImpl">
<property name="moneyImpl" ref="moneyImpl"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
(7)测试类
import com.coffee.service.MoneyService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class MyTest {
@Test
public void test(){
ClassPathXmlApplicationContext classPathXmlApplicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
MoneyService money2 = (MoneyService)classPathXmlApplicationContext.getBean("moneyService");
try {
//三个参数分别为 转账的钱,转出的账户的id,转入的账户的id
money2.money("100",1,2);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
两个人本来都有1000块钱,账户id为1的给账户id为2的转100块钱
结果如下
当转出之后立马出现了异常,就会执行事务回滚,可以在两个方法执行中间假如一个异常
执行后控制台输出语句:
数据库为:
可以看到两个人的账户余额并没有改变
5.7Xml配置(aop)的方式完成事务管理
修改AccountServiceImpl
package com.coffee.service.impl;
import com.coffee.mapper.impl.MoneyImpl;
import com.coffee.service.MoneyService;
public class MoneyServiceImpl implements MoneyService {
private MoneyImpl moneyImpl;
public void setMoneyImpl(MoneyImpl moneyImpl) {
this.moneyImpl = moneyImpl;
}
@Override
public void money(String money, Integer id1, Integer id2) {
{
moneyImpl.setMoney(money,id1);
// 这里可以写一个异常来看看事务回滚是否执行了
int sb= 1/0;
moneyImpl.getMoney(money,id2);
}
}
}
修改applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans">
<!-- 扫描文件,获取文件中存储的数据库属性-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 注入四个属性,获取道数据库连接对象datasource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="url" value="${db.url}"></property>
<property name="driverClassName" value="${db.driverClassName}"></property>
</bean>
<!-- 事务核心管理器,封装的所有的事务操作,十分重要-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知-->
<!-- <!– 企业中配置CRUD方法一般使用方法名+通配符*的形式配置通知,此时类中的方法名要和配置的方法名一致 –>-->
<!-- <!– 以方法为单位,指定方法应用什么事务属性-->
<!-- isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。-->
<!-- propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。-->
<!-- read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。-->
<!-- timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。-->
<!-- rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。-->
<!-- no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。-->
<!-- –>-->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="money" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="false"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 事务模板对象-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
<!-- mapper层,使用了JdbcTemplate来进行修改的操作,因此需要传入datasource-->
<!-- 配置织入-->
<aop:config>
<!-- 配置切入点-->
<aop:pointcut id="pc" expression="execution(* com.coffee.service.impl.MoneyServiceImpl.money(..))"/>
<!-- 配置切面-->
<!-- 配置切面 : 通知+切点 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
<bean id="moneyImpl" class="com.coffee.mapper.impl.MoneyImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- service层,将mapper层中获取的对象进行依赖注入,以及事务模板对象-->
<bean id="moneyService" class="com.coffee.service.impl.MoneyServiceImpl">
<property name="moneyImpl" ref="moneyImpl"></property>
</bean>
</beans>
5.8注解配置(aop)的方式完成事务管理
修改applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans">
<!-- 扫描文件,获取文件中存储的数据库属性-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 注入四个属性,获取道数据库连接对象datasource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="url" value="${db.url}"></property>
<property name="driverClassName" value="${db.driverClassName}"></property>
</bean>
<!-- 事务核心管理器,封装的所有的事务操作,十分重要-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务-->
<tx:annotation-driven/>
<bean id="moneyImpl" class="com.coffee.mapper.impl.MoneyImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- service层,将mapper层中获取的对象进行依赖注入,以及事务模板对象-->
<bean id="moneyService" class="com.coffee.service.impl.MoneyServiceImpl">
<property name="moneyImpl" ref="moneyImpl"></property>
</bean>
</beans>
修改AccountServiceImpl
package com.coffee.service.impl;
import com.coffee.mapper.impl.MoneyImpl;
import com.coffee.service.MoneyService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class MoneyServiceImpl implements MoneyService {
private MoneyImpl moneyImpl;
public void setMoneyImpl(MoneyImpl moneyImpl) {
this.moneyImpl = moneyImpl;
}
@Override
//注解可以直接在类上表示,但是如果该方法与类名上的配置不同,也可以单独在这个方法上配置注解
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly = false)
public void money(String money, Integer id1, Integer id2) {
{
moneyImpl.setMoney(money,id1);
// 这里可以写一个异常来看看事务回滚是否执行了
int sb= 1/0;
moneyImpl.getMoney(money,id2);
}
}
}