Spring的事务配置
这里先举一个例子:A给B转账100,逻辑为 A减少100,B增加100(原本两者都是1000)
public void test() {
userService.transfer(1, 2, 100.0);
}
此时没有开启事务,数据库进行了正常的增加减少
但是我们在sevice中加入
系统会报异常
并且,数据库还进行了错误的减少,即对方没收到,但是我们的钱却少了
这时我们就需要用事务来管理这一系列SQL操作,达到数据的一致性
一 基于配置文件配置
1.导入配置文件
<context:property-placeholder location="db.properties"/>
此处location填写配置文件的名称
2.配置数据源
我使用的c3p0连接池,并给数据源配置一个id
<!-- 配置dataSouce -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
3.配置JdbcTemplate
<!-- 配置jdbcTemplate -->
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.配置事务管理器
在不同平台,操作事务的代码各不相同,因此spring提供了一个 TransactionManager 接口:
- DateSourceTransactionManager 用于 JDBC 的事务管理
-HibernateTransactionManager 用于 Hibernate 的事务管理
- JpaTransactionManager 用于 Jpa 的事务管理
这里我们使用DateSourceTransactionManager 来管理事务,
在这里我们需要为事务管理器注入一个dataSource数据源,并设置一个唯一id
![1540554427428](/1540554427428.png
5.配置事务通知
<!-- -->
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer"/>
</tx:attributes>
</tx:advice>
给事务通知设置一个id,然后transaction-manager属性为上面配置好的事务管理器,然后配置Spring事务管理的属性,添加tx:attributes标签,在之中加入<tx:method name=“方法名”/>,这里的意思是配置这些方法上的事务0属性,事务属性包括这个几个方面
可以在后面添加相应的相应的属性来配置对应方法的事务属性,方法可以使用*通配符 例如
<tx:method name="update*" />//表示给名称为update开头的方法配置事务属性
6.配置切面
<aop:config>
<aop:pointcut expression="execution(* com.dream.transaction.UserServiceImpl.*(..))" id="pc"/>
<aop:advisor advice-ref="tx" pointcut-ref="pc"/>
</aop:config>
首先配置切点,即增强应该用到哪个类的哪个方法上
然后配置通知,即我们需要执行的操作
advice-ref引用我们上面声明的事务通知,pointcut-ref用来引入切入点
注意
1.execution使用
execution是一种使用频率比较高比较主要的一种切点指示符,用来匹配方法签名,方法签名使用全限定名,包括访问修饰符(public/private/protected)、返回类型,包名、类名、方法名、参数,其中返回类型,包名,类名,方法,参数是必须的,如下面代码片段所示:
@Pointcut("execution(public String org.baeldung.dao.FooDao.findById(Long))")
这些属性可以由替代,表示任意,但是不能连续两个出现连续两个 * 既 比如 ,让访问修饰符和返回类型都为
<aop:pointcut expression="execution(* * com.dream.transaction.UserService.*(..))" id="pc"/>
程序会报错,这种语法是不对的
2.execution的方法
方法那里一般填写接口,如果填写一个实现类,则表示只有那一个类中的某些方法会被增强
7.测试
public interface UserDao {
//增加100
public void addMoney(int id,double money);
//增加100
public void jianMoney(int id,double money);
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
JdbcTemplate jdbc;
@Override
public void addMoney(int id, double money) {
jdbc.update("update people set money=money+? where id=?", money,id);
}
@Override
public void jianMoney(int id, double money) {
jdbc.update("update people set money=money-? where id=?", money,id);
}
}
public interface UserService {
public void transfer(int mId,int oId,double money);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userdao;
/* (non-Javadoc)
* @see com.dream.transaction.UserService#transfer(int, int, double)
*/
@Override
public void transfer(int mId, int oId, double money) {
//减少100
userdao.jianMoney(mId, money);
//int i=100/0;
userdao.addMoney(oId, money);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:transfer.xml")
public class Test {
@Autowired
private UserService userService;
@org.junit.Test
public void test() {
userService.transfer(1, 2, 100.0);
}
}
这里使用spring整合junit来加载配置文件,数据库表如下
1.通过以上代码实现简单的转账操作,转账分为两个操作,A转给B,A需要先减100,B加100,我们先不用事务来进行操作。
测试结果
当执行完以上测试代码后,1减少100,2增加了100,但是如果中途出现了异常,还会像上面一样吗?
加入一个100/0,看一下是否会出现扣款成功,对方钱没加的情况
控制台已经,报错
看一下数据库的信息,钱并没有增加和减少,这样就完成了事务的配置操作
二 基于注解配置
1.在配置文件中配置好jdbcTemplate,datasource和事务管理器
<?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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 导入配置文件 -->
<context:property-placeholder location="db.properties"/>
<context:component-scan base-package="com.dream.transaction"></context:component-scan>
<!-- 配置dataSouce -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置transactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
2.配置事务注解支持
<!--对标注@Transactional注解的Bean进行加工处理,以织入事物管理切面 -->
<tx:annotation-driven transaction-manager="transactionManager" />
3@Transactional其他方面介绍
- 关于@Transactional的属性
基于@Transactional注解的配置和基于xml的配置一样,它拥有一组普适性很强的默认事务属性,往往可以直接使用默认的属性.
- 事务传播行为: PROPAGATION_REQUIRED.
- 事务隔离级别: ISOLATION_DEFAULT.
- 读写事务属性:读/写事务.
- 超时时间:依赖于底层的事务系统默认值
- 回滚设置:任何运行期异常引发回滚,任何检查型异常不会引发回滚.
默认值可能适应大部分情况,但是我们依然可以可以自己设定属性,具体属性表如下:
- 在何处标注@Transactional注解
@Transactional注解可以直接用于接口定义和接口方法,类定义和类的public方法上.
但Spring建议在业务实现类上使用@Transactional注解,当然也可以添加到业务接口上,但是这样会留下一些容易被忽视的隐患,**因为注解不能被继承,**所以业务接口中标注的@Transactional注解不会被业务类实现继承.
- 在方法出使用注解
方法出添加注解会覆盖类定义处的注解,如果有写方法需要使用特殊的事务属性,则可以在类注解的基础上提供方法注解,如下:
可以为@Transactional注解配置事务属性
4.测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:transferAno.xml")
public class Test {
@Autowired
private UserService userService;
@org.junit.Test
public void test() {
userService.transfer(1, 2, 100.0);
}
}
完成转账操作
设置,异常
测试
三 事务管理
3.1事务简介
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
这里我们以取钱的例子来讲解:比如你去ATM机取1000块钱,大体有两个步骤:第一步输入密码金额,银行卡扣掉1000元钱;第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。
如何保证这两个步骤不会出现一个出现异常了,而另一个执行成功呢?事务就是用来解决这样的问题。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。
3.2事务四大特性(ACID)
①、原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作**要么全部完成,要么完全不起作用**。
②、一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
③、隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
④、持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
3.3 PlatformTransactionManager 平台事务管理器–核心API
PlatformTransactionManager 事务管理器 Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示,Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
我们进入到 PlatformTransactionManager 接口,查看源码:
①、TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 通过TransactionDefinition,获得“事务状态”,从而管理事务。
②、void commit(TransactionStatus status) 根据状态提交
③、void rollback(TransactionStatus status) 根据状态回滚
也就是说Spring事务管理的为不同的事务API提供一致的编程模型,具体的事务管理机制由对应各个平台去实现。
比如下面我们导入实现事务管理的两种平台:JDBC和Hibernate、Mybatis
然后我们再次查看PlatformTransactionManager接口,会发现它多了几个实现类,如下:
Ctrl+shift+T:打开
Ctrl+T:打开类的集成结构树:
3.4事务属性的定义
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
3.4.1传播行为(面试题)
传播行为:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。事务传播的策略!
Spring 定义了如下七中传播行为,这里以A业务和B业务之间如何传播事务为例说明:
①、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创建一个新的事务。
⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。
⑥、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。
⑦、PROPAGATION_NESTED :nested ,嵌套。如果A当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
3.4.2隔离级别
隔离级别:定义了一个事务可能受其他并发事务影响的程度。
n 并发事务引起的问题:
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致以下的问题。
①、脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
②、不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
③、幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
注意:不可重复读重点是修改,而幻读重点是新增或删除。
在 Spring 事务管理中,为我们定义了如下的隔离级别:
①、ISOLATION_DEFAULT:使用后端数据库默认的隔离级别(不同的数据隔离级别不同)
②、ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
③、ISOLATION_READ_COMMITTED**(Oracle****)**:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
④、ISOLATION_REPEATABLE_READ**(mysql****)**:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
⑤、ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。
上面定义的隔离级别,在 Spring 的 TransactionDefinition.class 中也分别用常量 -1,0,1,2,4,8表示。比如 ISOLATION_DEFAULT 的定义:
3.4.3事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,**在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束**。
3.4.4回滚规则
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的) 。但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
可以指定何种类型的异常西是否需要回滚撤销!!
3.4.5只读
这是事务的第三个特性,是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
Spring会管理事务,但是查询一般都设置成只读事务,性能会高!