spring编程式事务

Posted 猿人课堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring编程式事务相关的知识,希望对你有一定的参考价值。

事务是spring框架中一个核心的模块,事务的ACID特性想必对于学习java的同学来说都不陌生,对于spring,实现事务的底层原理其实很简单,就是通过AOP代理进行实现,而实现spring的AOP更底层的,就是使用反射机制完成的;


举例来说,当你的方法上标注了@Service之后,spring怎么知道你这个类会被纳入到整个bean的容器中进行管理呢?如果在这个类下面的某个方法上面加了@Transactionnal注解,spring怎么知道要为你的这个方法开启一个事务管理器呢?说到底,反射在这里就派上用场了,总体来说,就是通过反射,匹配类上的注解,再去匹配方法上的注解,只要找到了相应的注解,就开启相应的特性,就是这么简单,


关于spring事务,常见的有两种,一种是编程式事务,比如java代码使用Jdbc进行数据库操作而没有引入其他框架时,就需要通过手动编写事务进行处理,另一种就是我们熟知的使用spring框架,帮助我们管理事务,比如像我们在xml配置文件中,通过配置扫描包和事务管理器,就可以对相关的类进行事务管理了,下面来演示一下采用编程式事务来学习一下spring的事务特性,了解了编程式事务,xml的事务就不是难事了,


项目结构非常简单,就是几个包和一个spring的配置文件,

spring编程式事务


1、pom依赖,主要是spring相关的依赖,


<properties>
<spring.version>5.1.2.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>



2、spring的基本配置文件,这里为了模拟原生的事务,直接配置的是 JdbcTemplate


       <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.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/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.congge"></context:component-scan>
<!-- 开启事务注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:config proxy-target-class="true"></aop:config>
<!--数据库连接池配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="user" value="root" />
<property name="password" value="root" />
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
</beans>




3、自定义注解,这个自定义注解其实可以理解成spring的@Transactional这个注解,为了模拟的更真实,就使用自定义注解,也就是说,当我们的方法上面标注了这个自定义的注解之后,事务就开启了,


/**
* 自定义事务注解
*/
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ExtTransaction {



}




4、自定义事务管理器,在aop拦截器里面,我们将使用这个自定义的事务管理器对我们的方法操作进行事务提交等相关处理,


/**
* 自定义事务管理器
* @author asus
*
*/
@Component
public class SelfTransactionManager {


private TransactionStatus transactionStatus;
//获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
/**
* 手动开启事务
*
* @return
*/
public TransactionStatus begin() {
transactionStatus = dataSourceTransactionManager.getTransaction(
new DefaultTransactionAttribute());
return transactionStatus;
}


/**
* 提交事务
*
* @param transactionStatus
*/
public void commit(TransactionStatus transactionStatus) {
dataSourceTransactionManager.commit(transactionStatus);
}


/**
* 回滚事务
*/
public void rollBack() {
System.out.println("开始回滚");
dataSourceTransactionManager.rollback(transactionStatus);
}
}



5、配置自定义AOP拦截工具类,在这个类里面,模拟一旦spring 容器启动,只要调用了某个方法,而这个方法正好是添加了我们的自定义注解,就会通过这个AOP拦截到,从而在方法执行时进行事务处理,这个类里面,我们还可以做很多其他的事情,比如前置通知,后置通知,异常通知,返回通知等,也正是这些特性的存在,我们可以使用AOP进行例如统一的日志管理,权限拦截与校验,性能检查等操作,方便我们队一些参数进行优化管理,而在这里的事务我们主要使用的是AOP的环绕通知,一会儿可以通过日志看出效果,


    /**
* 自定义AOP管理类
*
* @author asus
*/
@Aspect
@Component
public class SelfAopTransaction {


@Autowired
private SelfTransactionManager selfTransactionManager;
@Around("execution(* com.congge.service.*.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {


ExtTransaction extTransaction = getExtTransaction(pjp);
TransactionStatus transactionStatus = null;
// 开始事务
if (extTransaction != null) { // 说明该方法上存在自定义的事务注解
System.out.println("开始事务.....");
transactionStatus = selfTransactionManager.begin();
}


// 调用目标代理对象方法
Object rs = pjp.proceed();
// 提交事务
if (transactionStatus != null) {
selfTransactionManager.commit(transactionStatus);
System.out.println("提交事务.....");
}
return rs;
}


public ExtTransaction getExtTransaction(ProceedingJoinPoint pjp) {
// 获取方法名称
String methodName = pjp.getSignature().getName();
// 获取目标对象
Class<?> classTarget = pjp.getTarget().getClass();
// 获取目标对象类型

Class<?>[] par = ((MethodSignature) pjp.getSignature()).

getParameterTypes();

Method objMethod;
try {
objMethod = classTarget.getMethod(methodName, par);

ExtTransaction extTransaction = objMethod.

getDeclaredAnnotation(ExtTransaction.class);

if (extTransaction != null) {
return extTransaction;
}
return null;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}


// 方法开始前
@Before("execution(* com.congge.service.*.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
// 获取方法签名和参数集合

//System.out.println(joinPoint.getSignature().getName() + "-----"

+ joinPoint.getArgs());

System.out.println("我是方法的前置通知 :" + "num1");
}


// 方法结束之后
@After("execution(* com.congge.service.*.*.*(..))")
public void afterMethod(JoinPoint joinPoint) {
// 获取方法签名和参数集合

//System.out.println(joinPoint.getSignature().getName() + "-----" +

joinPoint.getArgs());

System.out.println("我是方法的后置通知");
}


// 方法返回值之后

@AfterReturning(value = "execution(* com.congge.service.*.*.*(..))",

returning = "result")

public void returnMethod(JoinPoint joinPoint, Object result) {
System.out.println("方法执行后的返回结果是:" + result);
}


// 方法抛出异常时

@AfterThrowing(value = "execution(* com.congge.service.*.*.*(..))",

throwing = "e")

public void afterException(JoinPoint joinPoint, Exception e) {
System.out.println("我是目标方法发生异常才执行的通知:" + e.getMessage());
}


}

6、下面写一个接口和方法,


    @Service
public class UserServiceImpl implements UserService {


@Autowired
private UserDao userDao;
/*@Transactional
public void add(){
userDao.add("user3",20);
int d = 1/0;
System.out.println("插入;额一条数据 &&&&&&&");
userDao.add("user4", 26);
}*/
@ExtTransaction
public int add() {
userDao.add("congge", 18);
//int d = 1/0;
System.out.println("插入;额一条数据 &&&&&&&");
int rs = userDao.add("acongge", 188);
return rs;
}


}



7、UserDao 里面具体执行数据库插入操作,



@Repository
public class UserDao {


@Autowired
private JdbcTemplate jdbcTemplate;
public int add(String name, int age) {
String sql = "INSERT INTO user(name, age) VALUES(?,?);";
int updateResult = jdbcTemplate.update(sql, name, age);
System.out.println("updateResult:" + updateResult);
return updateResult;
}


}



8、下面是一个测试方法,


public class Test1 {


public static void main(String[] args) {


ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("application-context.xml");
UserServiceImpl userService = (UserServiceImpl)
context.getBean("userServiceImpl");
userService.add();
}


}



运行main函数,看看控制台的输出结果,首先我们来看一下正常的执行结果,

spring编程式事务

再看看数据库的结果,成功插入了两条结果,

spring编程式事务


下面我们来模拟一下异常情况,我们先把数据库的两条数据删掉,

spring编程式事务



再来执行一下,可以看到,异常被我们的自定义事务捕获到了,

spring编程式事务


再看看数据库,数据并没有插入,说明我们的自定义事务生效了,


以上便是整个手动编写spring编程式事务过程



版权声明:本文为CSDN博主「神秘的葱」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhangcongyi420/article/details/89415613



以上是关于spring编程式事务的主要内容,如果未能解决你的问题,请参考以下文章

Spring事务管理2----编程式事务管理

spring事务的开启方式(编程式和声明式)

Spring事务管理的实现方式:编程式事务与声明式事务

Spring事务管理的实现方式:编程式事务与声明式事务

一文解析Spring编程式和声明式事务实例讲解

Spring的编程式事务和声明式事务