自定义ChenTransaction
注解,实现方法级别事务管理。正常则事务提交,异常则事务回滚。
思路:使用注解判断方法是否开启了事务,如果开启了事务则使用spring aop
,在方法执行前开启事务,方法正常执行完毕提交事务。如果出现异常,则回滚事务。
模拟正常的业务代码,演示未实现事务会出现的问题。
本项目spring
事务基于Spring Aop
技术实现,所以需要引入spring AOP
相关jar
包。事务需要关联数据库,同时引入mysql
驱动包进行连接。源码如下:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>handwritingprojectartifactId>
<groupId>com.njustgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>springTransactionartifactId>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.5.3version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.1_2version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.37version>
dependency>
dependencies>
project>
在spring.xml
中,主要配置自动扫描包的范围、开启自动切片代理、数据库连接池、引入JdbcTemplate来对数据库进行操作、配置事务等信息。源码如下:
spring.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:component-scan base-package="com.njust">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_learning">property>
<property name="user" value="root">property>
<property name="password" value="root">property>
bean>
<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">property>
bean>
beans>
新建ChenTransaction
类,定义注解所修饰的对象范围为ElementType.METHOD
,即用于描述方法。同时定义注解不仅被保存到class文件中,而且jvm加载class文件之后,仍然存在,使得spring
通过java
反射机制可以获取到该类。源码如下:
ChenTransaction.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 事务注解 设置传播行为
//@Target说明了Annotation所修饰的对象范围 METHOD:用于描述方法
@Target({ ElementType.METHOD })
//RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Retention(RetentionPolicy.RUNTIME)
public @interface ChenTransaction {
}
根据测试先行原则,我们先完成业务代码结构,定义UserDao类,主要实现向数据库插入一条数据的功能。源码如下:
UserDao.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void add(String name, Integer age) {
String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
int updateResult = jdbcTemplate.update(sql, name, age);
System.out.println("updateResult:" + updateResult);
}
}
定义UserService 接口,主要有add()
和del()
两个简单的方法。源码如下:
UserService .java
//user 服务层
public interface UserService {
public void add();
public void del();
}
定义UserServiceImpl
实现UserService
接口,通过spring
注入UserDao
,实现user
用户添加操作。其中在add()
方法上加入自定义注解@ChenTransaction
实现事务管理。代码中用int i = 1 / 0;
模拟业务操作过程中出现的异常,以测试自定义事务管理是否能够正常工作。
UserServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.njust.dao.UserDao;
import com.njust.service.LogService;
import com.njust.service.UserService;
//user 服务层
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private LogService logService;
@ChenTransaction
public void add() {
userDao.add("test001", 20);
int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
}
// 方法执行完毕之后,才会提交事务
public void del() {
System.out.println("del");
}
}
定义Test001
类,通过spring
容器获取UserService
对象,对其进行add()
添加操作,测试整个业务逻辑是否正常。
Test001 .java
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.njust.service.UserService;
public class Test001 {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
userService.add();
}
}
运行测试类,由于代码中有int i = 1 / 0;
,所以会出现异常,下面的控制台输出也验证了这一点。按照事务原子性的原则,当方法出现异常时,test001
不会插入数据库,但是查看数据库我们发现,里面有条test001
的数据,没有实现回滚,这和我们的预期不符。接下来我们将解决这个问题。
Console
三月 31, 2020 7:34:10 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6267c3bb: startup date [Tue Mar 31 19:34:10 CST 2020]; root of context hierarchy
三月 31, 2020 7:34:11 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring.xml]
三月 31, 2020 7:34:11 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@337d0578: defining beans [aopChenTransaction,logDao,userDao,logServiceImpl,userServiceImpl,transactionUtils,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.aop.config.internalAutoProxyCreator,dataSource,jdbcTemplate,dataSourceTransactionManager]; root of factory hierarchy
三月 31, 2020 7:34:11 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
三月 31, 2020 7:34:12 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
三月 31, 2020 7:34:13 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1b60ft0a9df5oit1lx41ct|2f490758, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1b60ft0a9df5oit1lx41ct|2f490758, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring_learning, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
Exception in thread "main" java.lang.NullPointerException
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:816)
at com.njust.transaction.TransactionUtils.rollback(TransactionUtils.java:37)
at com.njust.aop.AopChenTransaction.afterThrowing(AopChenTransaction.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:603)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy17.add(Unknown Source)
at com.njust.Test001.main(Test001.java:12)
updateResult:1
Process finished with exit code 1
使用注解判断方法是否开启了事务,如果开启了事务则使用spring aop
,在方法执行前开启事务,方法正常执行完毕提交事务。如果出现异常,则回滚事务。
定义TransactionUtils
编程事务类,实现事务开启、事务提交、事务回滚的功能。注意TransactionUtils
要设置成多例,防止出现线程安全问题。
TransactionUtils.java
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;
// 编程事务(需要手动begin 手动回滚 手都提交)
@Component
@Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子
public class TransactionUtils {
// 全局接受事务状态
private TransactionStatus transactionStatus;
// 获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务
public TransactionStatus begin() {
System.out.println("开启事务");
transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transactionStatus;
}
// 提交事务
public void commit(TransactionStatus transaction) {
System.out.println("提交事务");
dataSourceTransactionManager.commit(transaction);
}
// 回滚事务
public void rollback() {
System.out.println("回滚事务...");
dataSourceTransactionManager.rollback(transactionStatus);
}
}
定义AopChenTransaction
类,对service
包中所有方法定义环绕通知和异常通知。在环绕通知中,首先判断该方法是否加上ChenTransaction
注解,如果有则开启事务,否则直接跳过事务流程。然后执行目标代理对象方法。最后如果开启了事务则提交事务,否则直接返回。当目标代理对象方法出现异常时,则调用afterThrowing
进行事务回滚。
AopChenTransaction.java
import java.lang.reflect.Method;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import com.njust.annotation.ChenTransaction;
import com.njust.transaction.TransactionUtils;
// 自定义事务注解具体实现
@Aspect
@Component
public class AopChenTransaction {
// 一个事务实例子 针对一个事务
@Autowired
private TransactionUtils transactionUtils;
// 使用异常通知进行 回滚事务
@AfterThrowing("execution(* com.njust.service.*.*.*(..))")
public void afterThrowing() {
// 获取当前事务进行回滚
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
transactionUtils.rollback();
}
// 环绕通知 在方法之前和之后处理事情
@Around("execution(* com.njust.service.*.*.*(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable {
// 1.获取该方法上是否加上注解
ChenTransaction ChenTransaction = getMethodChenTransaction(pjp);
TransactionStatus transactionStatus = begin(ChenTransaction);
// 2.调用目标代理对象方法
pjp.proceed();
// 3.判断该方法上是否就上注解
commit(transactionStatus);
}
private TransactionStatus begin(ChenTransaction ChenTransaction) {
if (ChenTransaction == null) {
return null;
}
// 2.如果存在事务注解,开启事务
return transactionUtils.begin();
}
private void commit(TransactionStatus transactionStatus) {
if (transactionStatus != null) {
// 5.如果存在注解,提交事务
transactionUtils.commit(transactionStatus);
}
}
// 获取方法上是否存在事务注解
private ChenTransaction getMethodChenTransaction(ProceedingJoinPoint pjp)
throws NoSuchMethodException, SecurityException {
String methodName = pjp.getSignature().getName();
// 获取目标对象
Class<?> classTarget = pjp.getTarget().getClass();
// 获取目标对象类型
Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
// 获取目标对象方法
Method objMethod = classTarget.getMethod(methodName, par);
ChenTransaction ChenTransaction = objMethod.getDeclaredAnnotation(ChenTransaction.class);
return ChenTransaction;
}
}
再次运行测试代码,我们发现同样出现异常,但是控制台显示了回滚,查看数据库我们发现确实没有插入test001
,正确的实现了事务的功能。
Console
三月 31, 2020 8:01:33 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6267c3bb: startup date [Tue Mar 31 20:01:33 CST 2020]; root of context hierarchy
三月 31, 2020 8:01:33 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring.xml]
三月 31, 2020 8:01:33 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@337d0578: defining beans [aopChenTransaction,logDao,userDao,logServiceImpl,userServiceImpl,transactionUtils,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.aop.config.internalAutoProxyCreator,dataSource,jdbcTemplate,dataSourceTransactionManager]; root of factory hierarchy
三月 31, 2020 8:01:33 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
三月 31, 2020 8:01:34 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
开启事务
三月 31, 2020 8:01:35 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1b60ft0a9dg4vp61yxqyq7|365c30cc, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1b60ft0a9dg4vp61yxqyq7|365c30cc, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring_learning, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
updateResult:1
回滚事务...
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.njust.service.impl.UserServiceImpl.add(UserServiceImpl.java:77)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.njust.aop.AopChenTransaction.around(AopChenTransaction.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy17.add(Unknown Source)
at com.njust.Test001.main(Test001.java:12)
Process finished with exit code 1
自定义事务ChenTransaction
的主要实现流程如下:
ChenTransaction
注解类,使其修饰method
方法,同时保证注解不仅被保存到class
文件中,而且jvm
加载class
文件之后,仍然存在,以便被spring
反射出实例对象。spring AOP
环绕通知和异常通知实现给方法添加事务,正常运行提交事务,出现异常及时回滚。具体步骤分为:
ChenTransaction
注解,并开启事务。在业务代码中不要捕获异常,否则即使业务代码出现异常,但是没有抛出,框架会认为任务正常结束从而提交事务,而不是回滚事务。
大白话理解一下,事务主要使用Spring AOP在方法前开启事务,方法结束后提交事务,遇到异常则回滚事务。
第一次写小轮子,有问题欢迎各位读者批评指正。
点个赞再走呗!欢迎留言哦!