第一节 事务的隔离级别【回顾】
- 为什么有事务的隔离级别?
- 解决多线程访问数据库的问题。
- 一组业务操作,要么全部成功,要么全部不成功。
特性:ACID
- 原子性:整体 【原子性是指事务包含的所有操作要么全部成功,要么全部失败】
- 一致性:数据 【一个事务执行之前和执行之后都必须处于一致性状态】
- 隔离性:并发 【对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。】
- 持久性:结果 【持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的】
隔离问题
-
脏读:一个事务读到另一个事务未提交的内容【读取未提交内容】
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其它级别好多少。 -
不可重复读:一个事务读到另一个事务已提交的内容(insert)【读取提交内容】
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 -
虚读(幻读):一个事务读到另一个事务已提交的内容(update)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。 -
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
隔离级别–解决问题
- read uncommittd,读未提交。存在3个问题。
- read committed,读已提交。解决:脏读。存在2个问题。
- repeatable read ,可重复读。解决:脏读、不可重复读。存在1个问题。
- serializable,串行化。单事务。没有问题。
mysql 事务操作–简单
- ABCD(4个操作) 一个事务
Connection conn = null;
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);//不要自动提交事务
A
B
C
D
//3 提交事务
conn.commit();
} catche(){
//4 回滚事务
conn.rollback();
}
mysql 事务操作–Savepoint
- 需求:AB(必须),CD(可选)
Connection conn = null;
Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();//设置保存点
C
D
//3 提交事务
conn.commit();
} catche(){
if(savepoint != null){
//CD异常
//回滚到CD之前
conn.rollback(savepoint);//如果保存点有值,回滚到记录的保存点,CD之前
//提交了AB
conn.commit();
} else{
//如果保存点没有值,直接回滚到A之前
//AB异常
//回滚AB
conn.rollback();
}
}
第二节 Spring事务管理介绍
2.1 Spring提供的事务jar包
2.2 Jar包中的三个顶级接口
- PlatformTransactionManager:
平台事务管理器,spring要管理事务,进行事务配置时,必须要使用事务管理器。 - TransactionDefinition:
事务详情(事务定义、事务属性),spring用于确定事务具体详情,
例如:隔离级别、是否只读、超时时间等。
进行事务配置时,必须要配置详情。spring将配置项封装到该对象实例。 - TransactionStatus:
事务状态,spring用于记录当前事务的运行状态。例如:是否有保存点,事务是否完成。
spring底层根据状态进行相应操作。
2.3 PlatformTransactionManager 事务管理器
导入以下jar包
- 不使用hibernate可以不导,只是了解一下
之前常用的两个事务管理器
以后操作数据库将使用mybatis的事务管理器
2.4 TransactionDefinition 事务定义
- 传播行为:在两个业务之间如何共享事务
PROPAGATION_REQUIRED required,必须 【默认值】【掌握】 | 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将创建一个新的事务。 |
PROPAGATION_SUPPORTS supports ,支持 | 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将以非事务执行。 |
PROPAGATION_MANDATORY mandatory ,强制 | 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将抛异常。 |
PROPAGATION_REQUIRES_NEW requires_new ,必须新的【掌握】 | 如果A有事务,将A的事务挂起,B创建一个新的事务。 如果A没有事务,B创建一个新的事务。(不管A有没有事务,B都创建一个新事务) |
PROPAGATION_NOT_SUPPORTED not_supported ,不支持 | 如果A有事务,将A的事务挂起,B将以非事务执行。 如果A没有事务,B将以非事务执行。(不管A有没有事务,B都以非事务执行) |
PROPAGATION_NEVER never,从不 | 如果A有事务,B将抛异常。 如果A没有事务,B将以非事务执行 |
PROPAGATION_NESTED nested ,嵌套【掌握】 | A和B底层采用保存点机制,形成嵌套事务。 |
2.5 TransactionStatus 事务状态
第三节 转账案例:使用Spring管理事务
3.1 环境搭建
第一步:创建数据库表
create database spring_day05;
use spring_day05;
create table account(
id int primary key auto_increment,
username varchar(50),
money int
);
insert into account(username,money) values('jack','10000');
insert into account(username,money) values('rose','10000');
第二步:导入相关jar包
- 核心:4+1(core、context、beans、expression)+logging(日志)
- aop : 4 (aop联盟、spring aop、aspectj.weaver规范、spring aspect)
- 数据库:2 (jdbc/tx事务)
- 驱动:mysql
- 连接池:c3p0
第三步:Dao层代码编写
- AccountDao接口代码
package com.it.dao;
/**
* @author shuyy
* @date 2020/9/15
*/
public interface AccountDao {
//支出
void out(String outer,Integer money);
//收入
void in(String inner,Integer money);
}
- AccountDaoImpl实现类代码
package com.it.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* @ClassName AccountDaoImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
@Override
public void out(String outer, Integer money) {
//支出
getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
}
@Override
public void in(String inner, Integer money) {
//收入
getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner);
}
}
第四步:Service层代码编写
- AccountService接口编写
package com.it.service;
public interface AccountService {
//(支出,收入,金额)
void transfer(String outer,String inner,Integer money);
}
- AccountServiceImpl实现类编写
package com.it.service;
import com.it.dao.AccountDao;
/**
* @ClassName AccountServiceImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;//由spring注入,至少要提供set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, Integer money) {
//支出
accountDao.out(outer,money);
//收入
accountDao.in(inner, money);
}
}
第五步:Spring的配置
- 读取db.properties数据配置 -> 配置c3p0数据源 -> 配置dao -> 配置service
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--读取db.properties数据-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0数据源
注意:dbcp和c3p0的数据库连接的参数的属性名不一样,注意区别
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
第六步:测试与效果
@Test
public void test1(){
//转账测试
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//获取Service
AccountService accountService = (AccountService) context.getBean("accountService");
//jack转1000给rose
accountService.transfer("jack","rose",1000);
}
3.2 手动管理事务【了解】
- spring底层使用 TransactionTemplate 事务模板进行操作
- 操作步骤:
- service 需要获得 TransactionTemplate
- spring 配置模板,并注入给service
- 模板需要注入事务管理器
- 配置事务管理器:DataSourceTransactionManager ,需要注入DataSource
- 了解底层即可,因为以后都是通过aop来配置事务
1. 修改Service
- 配置spring事务模板TransactionTemplate
package com.it.service;
import com.it.dao.AccountDao;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @ClassName AccountServiceImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;//由spring注入,至少要提供set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//spring配置事务模板【提供set方法,由spring注入】
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(String outer, String inner, Integer money) {
//通过事务模板执行业务
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//支出
accountDao.out(outer,money);
int i = 1/0;//如果这里有错误就会回滚
//收入
accountDao.in(inner, money);
}
});
}
}
2. 修改spring的配置文件
- 逆推,要用事务模板就需要配置事务模板,配置事务模板还要配置事务管理器,配置事务管理器里面还要注入数据源dateSource
- 手动管理事务比较麻烦【了解即可】
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--读取db.properties数据-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0数据源
注意:dbcp和c3p0的数据库连接的参数的属性名不一样,注意区别
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务模板-->
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
<!--事务管理器-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl" id="accountService">
<property name="accountDao" ref="accountDao"></property>
<!--事务模板-->
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
3. 测试与效果
-
有异常,数据会回滚,数据不变
-
注释异常,再运行一次,测试手动配置事务后,能否运行成功
3.3 工厂bean生成代理:半自动
- Spring提供管理事务的代理工厂bean TransactionProxyFactoryBean
1. service无需配置事务模板
- AccountServiceImpl实现类同最开始的环境配置
2. 修改spring的配置文件
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--读取db.properties数据-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0数据源
注意:dbcp和c3p0的数据库连接的参数的属性名不一样,注意区别
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置工厂代理-->
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" id="transactionProxyFactoryBean">
<!--接口-->
<property name="proxyInterfaces" value="com.it.service.AccountService"></property>
<!--目标对象-->
<property name="target" ref="accountService"></property>
<!--切面对象:spring做,无需写-->
<!--事务管理器-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
<!--transactionAttributes:配置事务的属性或详情(必须配置,不配置报错)
key:写方法名
value:写事务配置
格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception(后面3个可不配置,可选配置)
传播行为 隔离级别 是否只读 异常回滚 异常提交
-->
<property name="transactionAttributes">
<props>
<!--配置默认的传播行为和隔离级别-->
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
</props>
</property>
</bean>
</beans>
3. 测试与效果
- 有异常,回滚,数据不变
- 注释异常,成功转账
- readOnly的使用(只读,不能修改),不设置异常也会报错,因为里面涉及修改数据,而readOnly是只读(数据不变)
- +Exception的使用(有异常也提交)
- 先设置异常
- 复制异常的类型,设置有异常也提交,再运行一下
- 有异常,但是数据会提交(jack支付了,rose没收到)
3.4 使用AOP配置事务【掌握】
- 实现类同上
1. spring的配置
<?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:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--读取db.properties数据-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0数据源
注意:dbcp和c3p0的数据库连接的参数的属性名不一样,注意区别
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="txManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--使用spring的aop标签来配置-->
<!--1.配置通知事务管理器-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--配置事务详情:传播行为、隔离级别-->
<tx:attributes>
<!--传播行为、隔离级别有默认,可以不配置,但是方法(tx:attributes)一定要配置,否则报错-->
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--可以抽取为公共切入点,其它可以引用它-->
<!--<aop:pointcut id="myPointCut" expression="execution(* com.it.service..*.*(..))"/>-->
<!--2.事务与切入点关联--><!--这里的切入点表达式表示service包及其子包下的所有类和任意参数的方法都是切入点-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.it.service..*.*(..))"></aop:advisor>
</aop:config>
</beans>
- 注意要添加文件头
2. 测试与结果
- 由于之前测试次数过多,将数据库数据重置一下
- 同样先测试有异常,数据库数据不变
- 注释异常,事务成功配置
- readOnly的设置(true只读)
- 有异常也提交(no-rollback-for)
3.5 使用注解配置事务
- 使用注解配置事务,注解可以写在一个类上也可以写在一个方法上
- 每一个使用事务的类都要加上注解,不如在配置文件中来得一目了然
1.spring的配置
<?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:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--读取db.properties数据-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0数据源
注意:dbcp和c3p0的数据库连接的参数的属性名不一样,注意区别
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--注解配置事务-->
<!--1.配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="txManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.开启事务注解驱动-->
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
</beans>
2. 测试与效果
- 先重置数据库数据,都为10000
- 使用注解配置事务(@Transactional)
- @Transactional注解(readOnly与异常提交条件等都在其中)
- 默认可以不写传播行为与隔离级别,设置异常,回滚,数据不变
- 注释异常,成功配置