我猜大概50%的Java程序员(别问我怎么知道的,反正我就是,太失败了!!!)如今仅仅局限于一个@Transactional注解或者在XML中配置事务相关的东西,而后除了事务级别以外,其余的事务知识多是空白的。为了更加全面地学习,因此我就汇总一下Spring事务的知识点,有什么不对或者补充的,你们记得留言告诉我哈。前端
关于事务的由来,我就不举例子了,不少人第一反应就是去银行存钱(然而我是用花呗的)的操做了。事务的四大特性ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)java
(1)**read uncommited:**是最低的事务隔离级别,它容许另一个事务能够看到这个事务未提交的数据。 (2)**read commited:**保证一个事物提交后才能被另一个事务读取。另一个事务不能读取该事物未提交的数据。 (3)**repeatable read:**这种事务隔离级别能够防止脏读,不可重复读。可是可能会出现幻象读。它除了保证一个事务不能被另一个事务读取未提交的数据以外还避免了如下状况产生(不可重复读)。 (4)**serializable:**这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读以外,还避免了幻象读程序员
说明: a.脏读:指当一个事务正字访问数据,而且对数据进行了修改,而这种数据尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。由于这个数据尚未提交那么另一个事务读取到的这个数据咱们称之为脏数据。依据脏数据所作的操做肯能是不正确的。 b.不可重复读:指在一个事务内,屡次读同一数据。在这个事务尚未执行结束,另一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,因为第二个事务的修改第一个事务两次读到的数据多是不同的,这样就发生了在一个事物内两次连续读到的数据是不同的,这种状况被称为是不可重复读。 c.幻象读:一个事务前后读取一个范围的记录,但两次读取的纪录数不一样,咱们称之为幻象读(两次执行同一条 select 语句会出现不一样的结果,第二次读会增长一数据行,并无说这两次执行是在同一个事务中)spring
@Transactional注解估计你们都了解,那么咱们先跟踪一下它的源码,发现了PlatformTransactionManager这个接口类,具体的接口方法以下:sql
public interface PlatformTransactionManager()...{
// 由TransactionDefinition获得TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
void commit(TransactionStatus status) throws TransactionException;
// 回滚
void rollback(TransactionStatus status) throws TransactionException;
}
复制代码
它就是定义了咱们平时操做事务的三大步骤。具体实现由它的子类来实现,也就是以下图所示的关系:数据库
(1)编程式事务管理对基于 POJO 的应用来讲是惟一选择。咱们须要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。(学过Java都会的吧,我就不啰嗦这个了。) (2)基于 TransactionProxyFactoryBean的声明式事务管理 (3)基于 @Transactional 的声明式事务管理 (4)基于Aspectj AOP配置事务apache
具体实现以下:编程
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
//一、注入事务管理器对象
@Autowired
private PlatformTransactionManager txManager;
//二、开启事务
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
//三、提交
txManager.commit(status);
4、回滚
txManager.rollback(status);
复制代码
使用场景: 在springboot项目开发中,涉及到调用第三方接口,请求第三方接口成功但返回相关交易失败的话,须要删除插入表的某条数据,或更新别表中的表状态同时记录日志等,将第三方请求的实际完成状况返回给前端。安全
配置文件:applicationContext.xmlspringboot
<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 引用外部文件 db.properties读取数据库配置-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- schemaLocation后面两个命名空间是扫描该包必须有的 -->
<!-- 扫描com.sunline包以及全部子包,为全部加了注解的类建立bean -->
<context:component-scan base-package="com.sunline">
</context:component-scan>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="${driverClassName}">
</property>
<property name="url"
value="${url}">
</property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.hbm2ddl.auto">true</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/sunline/entity/Account.hbm.xml</value>
</list>
</property>
</bean>
<!-- 配置Hibernate事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== -->
<!-- 配置业务层的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountBizTwo" />
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 注入事务的属性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事务的传播行为
* ISOTATION :事务的隔离级别
* readOnly :只读
* -EXCEPTION :发生哪些异常回滚事务
* +EXCEPTION :发生哪些异常不回滚事务
-->
<prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>
</beans>
复制代码
这个主要是使用xml配置事务,其实跟如今的@Transactional 的效果同样。 更多详细的配置能够参考文章:blog.csdn.net/linhaiyun_y…
这个最简单,就暂时不细讲。
1)、建立工具类,用于开启事务,提交事务,会滚事务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
//注入spring容器中
@Component
//建立为多例对象,放置多线程安全问题
@Scope("prototype")
public class AopAspectUtil {
@Autowired
private DataSourceTransactionManager manager; //注入spring的事务管理器
//事务拦截器
private TransactionStatus transaction;
public TransactionStatus begin() {
transaction = manager.getTransaction(new DefaultTransactionAttribute());//设置为默认事务隔离级别
//返回事务拦截器
return transaction;
}
public void commit() {
// 提交事务
manager.commit(transaction);
}
public void rollback() {
//回滚事务
manager.rollback(transaction);
}
}
复制代码
2)、定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})//设置注解使用范围
@Retention(RetentionPolicy.RUNTIME)//注解不只被保存到class文件中,jvm加载class文件以后,仍然存在
public @interface MyTransactional {
}
复制代码
3)、自定义通知
使用注意,在针对事务管理方面,方法中不要去try{}catch(){},使用try,,catch后aop不能获取到异常信息,会致使出现异常后不能进行回滚,若是确实须要try,,,catch 能够再finally中加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();由此段代码进行事务的回滚
import com.zbin.aop.mytransation.TransactionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Component
@Aspect
public class AopTransaction {
@Autowired
private TransactionUtils transactionUtils;
@Around("execution(* cn.xbmchina.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//调用方法以前执行
System.out.println("开启事务");
transactionUtils.begin();
proceedingJoinPoint.proceed();
//调用方法以后执行
System.out.println("提交事务");
transactionUtils.commit();
}
@AfterThrowing("execution(* cn.xbmchina.aop.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("异常通知 ");
//获取当前事务进行回滚
//TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
transactionUtils.rollBack();
}
}
复制代码
4)、测试
@MyTransactional
public void add() {
userDao.add("test001", 20);
int i = 1 / 0;
System.out.println("---------------------");
userDao.add("test002", 20);
}
复制代码
如下部分摘自「唐大麦」:blog.csdn.net/soonfly/art… 事务传播行为(propagation behavior)指的就是当一个事务方法被另外一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,仍是为本身开启一个新事务运行,这就是由methodB的事务传播行为决定的。
看完仍是以为有点懵,那就一个个地为各位简单介绍一下呗。
若是存在一个事务,则支持当前事务。若是没有事务则开启一个新的事务。 能够把事务想像成一个胶囊,在这个场景下方法B用的是方法A产生的胶囊(事务)。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
复制代码
单独调用methodB方法时,由于当前上下文不存在事务,因此会开启一个新的事务。 调用methodA方法时,由于当前上下文不存在事务,因此会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,所以就加入到当前事务中来。
若是存在一个事务,支持当前事务。若是没有事务,则非事务的执行。可是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少量不一样。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
复制代码
单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
若是已经存在一个事务,支持当前事务。若是没有一个活动的事务,则抛出异常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
复制代码
当单独调用methodB时,由于当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
使用PROPAGATION_REQUIRES_NEW,须要使用 JtaTransactionManager做为事务管理器。 它会开启一个新的事务。若是一个事务已经存在,则先将这个存在的事务挂起。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
复制代码
当调用methodA();时,至关于以下代码
main(){
TransactionManager tm = null;
try{
//得到一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//从新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback();//回滚第二个事务
} finally {
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
} catch(RunTimeException ex) {
ts1.rollback();//回滚第一个事务
} finally {
//释放资源
}
}
复制代码
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码能够看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。若是methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所作的结果依然被提交。而除了 methodB以外的其它代码致使的结果却被回滚了
PROPAGATION_NOT_SUPPORTED 老是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也须要使用JtaTransactionManager做为事务管理器。
老是非事务地执行,若是存在一个活动事务,则抛出异常。
示例:
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.NEWSTED)
methodB(){
……
}
复制代码
若是单独调用methodB方法,则按REQUIRED属性执行。若是调用methodA方法,至关于下面的效果:
main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try{
methodB();
} catch(RuntimeException ex) {
con.rollback(savepoint);
} finally {
//释放资源
}
doSomeThingB();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
//释放资源
}
}
复制代码
当methodB方法调用以前,调用setSavepoint方法,保存当前的状态到savepoint。若是methodB方法调用失败,则恢复到以前保存的状态。可是须要注意的是,这时的事务并无进行提交,若是后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的全部操做。嵌套事务一个很是重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所作的动做。而内层事务操做失败并不会引发外层事务的回滚。
特别地: PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 彻底是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 若是外部事务 commit, 嵌套事务也会被 commit, 这个规则一样适用于 roll back.
以上都是一个数据源的状况下的事务处理,那你有没有想过若是多个数据源的状况下,这个事务如何获得保证呢?还请留意下次更新【Spring多数据源事务】
更多文章可关注公众号**【爱编码】,回复2019**有相关资料哦。