Spring系列:
目录
1.1.PlatformTransactionManager事务管理器
1.2.TransactionDefinition事务定义信息
1.2.2.事务的传播行为PropagationBehavior
2.案例:声明式事务管理案例 —— 转账(xml方式与注解方式)
2.2.注解配置方式添加事务管理 @Transactional
1.Spring的事务管理机制
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
- PlatformTransactionManager:事务管理器 —— 主要用于平台相关事务的管理
- TransactionDefinition: 事务定义信息(隔离、传播、超时、只读) —— 通过配置如何进行事务管理。
- TransactionStatus:事务具体运行状态 —— 事务管理过程中,每个时间点事务的状态信息。
1.1.PlatformTransactionManager事务管理器
该接口提供三个方法:
- commit:提交事务
- rollback:回滚事务
- getTransaction:获取事务状态
Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现类:
DataSourceTransactionManager针对JdbcTemplate、MyBatis 事务控制 ,使用Connection(连接)进行事务控制 :
- 开启事务 connection.setAutoCommit(false);
- 提交事务 connection.commit();
- 回滚事务 connection.rollback();
HibernateTransactionManager针对Hibernate框架进行事务管理, 使用Session的Transaction相关操作进行事务控制 :
- 开启事务 session.beginTransaction();
- 提交事务 session.getTransaction().commit();
- 回滚事务 session.getTransaction().rollback();
1.2.TransactionDefinition事务定义信息
该接口主要提供的方法:
getIsolationLevel:获取 隔离级别
getPropagationBehavior:获取 传播行为
getTimeout:获取 超时时间(事务的有效期)
isReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写),事务管理器能够根据这个返回值进行优化。
这些事务的定义信息,都可以在配置文件中配置和定制。
1.2.1.事务的隔离级别IsolationLevel
脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
事务四大特性 ACID,原子性(Atomicity),一致性(Consistency),隔离性(isoiation),持久性(Durability)。
其中,隔离性引发问题,解决事务的隔离问题:隔离级别
Mysql 默认隔离级别 REPEATABLE_READ
Oracle 默认隔离级别 READ_COMMITTED
1.2.2.事务的传播行为PropagationBehavior
什么是事务的传播行为? 有什么作用?
事务传播行为用于解决两个被事务管理的方法互相调用问题。
业务层两个方法面临的事务问题:
1)有些时候需要处于同一个事务(删除用户删除完成之后,需要同时删除用户对应的订单,需要事务回滚,例如商场工作人员删除订单业务)
2)有些时候不能在同一个事务(取款是一个事务操作,打印凭条是另一个事务操作,例如ATM取款业务)
事务的传播行为的7种类型:
主要分为三大类:
1)PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY
支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 。
事务默认传播行为 REQUIRED。最常用的。
2)PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。
常用的事务传播行为:PROPAGATION_REQUIRES_NEW
3)PROPAGATION_NESTED
嵌套事务 ,只对DataSourceTransactionManager有效 ,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点
1.3.TransactionStatus 事务状态
事务运行过程中,每个时间点 事务状态信息
flush(),给hibernate使用,底层发出sql的
hasSavepoint():判断是否有保留点
isCompleted():判断事务是否结束
isNewTransaction():判断当前事务是否是新开的一个事务。
isRollbackOnly():判断事务是否只能回滚
setRollbackOnly():设置事务是否回滚
事务的结束:必须通过commit 确认事务提交,rollback作用标记为回滚。
数据库操作中,如果只是回滚,后面不操作,数据库在关闭连接的时候,自动发出了commit。
三个事务超级接口对象之间的关系:
1)首先用户管理事务,需要先配置TransactionManager(事务管理器)进行事务管理;
2)然后根据TransactionDefinition(事务定义信息、事务的管理方案),通过TransactionManager(事务管理器)进行事务管理;
3)最后事务运行过程中,每个时刻都可以通过获取TransactionStatus(事务状态)来了解事务的运行状态。
1.4.Spring事务管理两种方式
Spring 支持两种方式事务管理:
一:编程式的事务管理
通过TransactionTemplate手动管理事务。
在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码 (侵入性 )。
二:使用XML或注解 配置 声明式事务
Spring的声明式事务是通过AOP实现的(环绕通知)
开发中经常使用(代码侵入性最小)--推荐使用!
2.案例:声明式事务管理案例 —— 转账(xml方式与注解方式)
2.1.编写转账案例,引出事务管理问题
需求:账号转账,Tom账号取出1000元,存放到Jack账号上
数据准备:创建mysql脚本
第一步:创建表t_account
CREATE TABLE `t_account` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) NOT NULL,
`money` DOUBLE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
第二步:插入测试数据:
INSERT INTO `t_account` VALUES (1, 'Tom', 1000);
INSERT INTO `t_account` VALUES (2, 'Jack', 1100);
INSERT INTO `t_account` VALUES (3, 'Rose', 1200);
第三步:查看测试数据:
第一步:新建web项目
第二步:导入jar包(spring核心jar包,数据库驱动,c3p0连接池、测试包、jdbc、spring事务)
配置文件:applicationContext.xml、log4j.properties、db.properties
注:可以直接在pom文件添加依赖,此处省略了;
db.properties文件:
jdbcClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///itcastspring
user=root
password=root
applicationContext.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <!-- 引入外部文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置数据源 --> <!-- c3p0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 设置连接字符串 --> <property name="driverClass" value="${jdbcClass}" /> <property name="jdbcUrl" value="${jdbcUrl}" /> <property name="user" value="${user}" /> <property name="password" value="${password}" /> </bean> <!-- 配置dao,注入jdbctemplate 对象--> <bean id="AccounDaoImpl" class="cn.itcast.spring.dao.AccounDaoImpl"> <!-- 方案二:BookDao类继承JdbcDaoSupport,直接注入数据源,就拥有了jdbctempate对象 --> <property name="dataSource" ref="dataSource"/> </bean> </beans>
第三步:编写Dao层,建包 cn.itcast.spring.dao
IAccountDao接口
public interface IAccountDao { //转入 public void in(String name,Double money); //转出 public void out(String name,Double money); }
AccounDaoImpl实现类
//账户操作持久层 //技术方案:jdbctempate public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao { //转入 public void in(String name,Double money){ String sql="update t_account set money = money+ ? where name = ?"; super.getJdbcTemplate().update(sql, money,name); } //转出 public void out(String name,Double money){ String sql="update t_account set money = money- ? where name = ?"; super.getJdbcTemplate().update(sql, money,name); } }
第四步:编写service层,建包 cn.itcast.spring.service
IAccountService接口
public interface IAccountService { //转账业务 void transfer(String outName,String inName,Double money); }
AccountServiceImpl实现类
public class AccountServiceImpl implements IAccountService{ //注入dao(xml方式) private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } //转账操作的业务逻辑 public void transfer(String outName,String inName,Double money){ //先取出 accountDao.out(outName, money); //出现异常 int i = 1/0; //再转入 accountDao.in(inName, money); } }
配置applicationContext.xml文件:
<!-- 配置dao--> <bean id="accountDao" class="cn.itcast.dao.AccountDaoImpl"> <!--注入数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置service --> <bean id="accountService" class="cn.itcast.service.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean>
第五步:使用SpringTest进行测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext.xml"}) public class SpringTest { //注入测试的service @Autowired private IAccountService accountService; //需求:账号转账,Tom账号取出1000元,存放到Jack账号上 @Test public void testTransfer(){ accountService.transfer("Tom", "Jack", 1000d); System.out.println("转账成功!"); } }
但是发现问题:
事务管理问题:在Service层没有事务的情况下,如果出现异常,则会转账不成功,先操作的取出可以执行,但后操作的转入失败,导致数据异常。
扩展:如果不配置事务,那么每一个数据库的操作都是单独的一个事务。
2.2.XML配置方式添加事务管理(tx、aop元素)
【操作思路】:
1、 确定目标:需要对AccountService 的 transfer方法,配置切入点
2、 需要Advice (环绕通知),方法前开启事务,方法后提交关闭事务
3、 配置切面和切入点
第一步:导入aop相关的包(4个),引入约束名称空间(aop和tx 的名称空间)
<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">
导入jar包:
配置Advice通知:Spring为简化事务的配置,提供了<tx:advice>来配置事务管理,也可以理解为该标签是spring为你实现好了的事务的通知增强方案。
第二步:配置spring容器,applicationContext.xml文件
<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">
<!-- 引入外部属性配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.className}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="cn.itcast.spring.dao.AccountDaoImpl">
<!-- 注入数据源,才拥有jdbctemplate -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置service -->
<bean id="accountService" class="cn.itcast.spring.service.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 第二步:定义通知,通知中要处理的就是事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
//注意:如果第一步的事务管理器id设为transactionManager,transaction-manager属性可以省略,默认
<!-- 配置事务的属性定义 -->
<tx:attributes>
<!-- 配置具体的方法的事务属性
name:事务添加在哪个方法上,需要自己定义
Isolation:事务的隔离级别,默认是按数据库的隔离级别来
Propagation:事务的传播行为,默认是同一个事务
timeout="-1": 事务的超时时间,默认值使用数据库的超时时间。
read-only="false": 事务是否只读,默认可读写。
rollback-for: 遇到哪些异常就回滚,其他的都不回滚
no-rollback-for:遇到哪些异常不回滚,其他的都回滚。和上面互斥的
-->
//method定义的方法都会添加事务管理
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<!-- 支持通配符:切面中 方法名字符合下面规则的方法都会添加事务管理 -->
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入点,让通知关联切入点,即事务控制业务层的方法 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
第三步:使用SpringTest.java测试:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext.xml"}) public class SpringTest { //注入测试的service @Autowired private IAccountService accountService; //需求:账号转账,Tom账号取出1000元,存放到Jack账号上 @Test public void testTransfer(){ accountService.transfer("Tom", "Jack", 1000d); System.out.println("转账成功!"); } }
结果:配置事务后,如果出现异常,转账不成功,则取出操作和转入操作都没有实行,数据正常。
【声明式事务处理的原理图】
【注意】
如果不配置,则走默认的事务(默认事务是每个数据库操作都是一个事务,相当于没事务),所以我们开发时需要配置事务。
【补充了解】:
rollback-for属性:
注意事项:声明式事务处理对运行时异常有效,任何 RuntimeException(运行时异常) 将触发事务回滚,但是任何 checked Exception(编译时异常)将不触发事务回滚
2.2.注解配置方式添加事务管理 @Transactional
步骤:
1.在需要管理事务的方法或者类上面 添加@Transactional 注解
2.配置注解驱动事务管理(事务管理注解生效的作用)(需要配置对特定持久层框架使用的事务管理器)
第一步:编写dao层,建包 cn.itcast.spring.anntx;
接口 IAccountDao
public interface IAccountDao { //(存入)转入 public void in(String name,Double money); //(取出)转出 public void out(String name,Double money); }
实现类 AccountDaoImpl
//账户操作持久层
//技术方案:jdbctempate
/**
* @Repository("accountDao")
* 相当于容易中定义<bean id="accountDao"
class="cn.itcast.spring.anntx.dao.AccountDaoImpl"/>
*/
@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
/*
演示:注入数据源的错误方式:
//注入数据源
@Autowired
private DataSource dataSource;
注入失败,@Autowired的原理是为属性生成setter方法通过setter方法注入,
但是JdbcDaoSupport 类有setter方法方法且为最终方法,不可重写
注意:xml配置与注解注入数据源的区别:xml配置是在配置文件中注入数据源到dao,
不生成setter方法,注解注入的@Autowired会底层生成setter方法,
*/
//注入数据源的正确方式:自定义方法,在方法内调用父类方法注入数据源
@Autowired //当初始化dao的时候,会调用该方法,通过set方法的形参注入数据源
public void setSuperDataSource(DataSource dataSource){ //方法名自定义
//调用父类的setDataSource方法,注入数据源
super.setDataSource(dataSource);
}
//(存入)转入
public void in(String name,Double money){
String sql="update t_account set money = money+ ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
//(取出)转出
public void out(String name,Double money){
String sql="update t_account set money = money- ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
}
第二步:编写service层,建包 cn.itcast.spring.anntx.service
IAccountService接口
public interface IAccountService { void transfer(String outName,String inName,Double money); }
AccountServiceImpl 实现类
/**
* @Service("accountService")
* 相当于spring容器中定义:<bean id="accountService" class="cn.itcast.spring.anntx.service.AccountServiceImpl">
*/
@Service("accountService")
@Transactional //在类上添加事务管理,自动对类中所有的公有的方法添加事务
public class AccountServiceImpl implements IAccountService{
//注入dao
@Autowired
private IAccountDao accountDao;
//转账操作的业务逻辑
@Transactional //在方法上添加事务管理,事务管理只在此方法上
public void transfer(String outName,String inName,Double money){
//调用dao层
//先取出
accountDao.out(outName, money);
//异常
int d = 1/0;
//再转入
accountDao.in(inName, money);
}
@Transactional(readOnly=true) //在方法和类上同时定义了事务的属性,且属性值不同,局部会覆盖全局
public void findAccount(){
System.out.println("查询帐号的信息了");
}
}
第三步:配置事务文件 applicationContext-tx.xml
<?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"> <!-- 引入外部属性文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置数据源 --> <!-- c3p0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.className}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 配置bean注解扫描 --> <context:component-scan base-package="cn.itcast.spring.anntx"/> <!-- 事务管理 --> <!-- 配置事务管理器(DataSource事务管理器) --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务注解驱动 :识别事务的注解@tr transaction-manager:具体的平台事务管理器 --> <!-- 默认的平台事务管理器的名字叫:transactionManager,如果与自定义的事务管理器id相同,transaction-manager="transactionManager"可以省略 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
【注意】方法与类添加事务管理:
(1)在需要管理事务的方法或者类上面 添加@Transactional 注解
(2)配置事务的定义属性信息,在注解中直接配置:
【扩展1】
如果 @Transactional 标注在 类 上面, 那么将会对这个 类 里面所有的 public 方法都包装事务方法。等同于该类的每个公有方法都放上了@Transactional。
如果某 方法 需要单独的事务定义,则需要在方法上加@Transactional来覆盖类上的标注声明。
记住:方法级别的事务覆盖类级别的事务
/** * @Service("accountService") * 相当于spring容器中定义:<bean id="accountService" class="cn.itcast.spring.anntx.service.AccountServiceImpl"> */ @Service("accountService") @Transactional() //会对该类中,所有的共有的方法,自动加上事务--全局的设置,默认是可写 public class AccountServiceImpl implements IAccountService{ //注入dao @Autowired private IAccountDao accountDao; //转账操作的业务逻辑 @Transactional(readOnly=false)//在方法上添加事务 public void transfer(String outName,String inName,Double money){ //调用dao层 //先取出 accountDao.out(outName, money); int d = 1/0; //再转入 accountDao.in(inName, money); } @Transactional(readOnly=true) //使用局部覆盖全局的 public void findAccount(){ System.out.println("查询帐号的信息了"); } }
2.4.小结:xml方式和注解方式的选择
XML配置方式和注解配置方式 进行事务管理哪种用的多?
1)XML方式,集中式维护,统一放置到applicationContext.xml文件中,缺点在于配置文件中的内容太多。
2)使用@Transactional注解进行事务管理,配置太分散,使用XML进行事务管理,属性集中配置,便于管理和维护。
注意:以后的service的方法名字的命名,必须是上面规则,否则,不能被spring事务管理。!!!!
即以save开头的方法,update开头的方法,delete开头的方法,表示增删改的操作,所以事务为可写。
以find开头的方法,表示查询,所以事务为只读。
(1)xml方式小结
(2)注解方式小结