Spring编程式事务

本文介绍了Spring框架中事务的核心模块,阐述其通过AOP代理和反射机制实现事务的底层原理。还提及Spring事务常见的两种类型,重点演示编程式事务,包括项目结构、pom依赖、配置文件、自定义注解、事务管理器、AOP拦截工具类等,最后通过测试验证自定义事务生效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

项目结构非常简单,就是几个包和一个spring的配置文件,
在这里插入图片描述

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

	<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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2、spring的基本配置文件,这里为了模拟原生的事务,直接配置的是 JdbcTemplate

<?xml version="1.0" encoding="UTF-8"?>

<!-- 自动扫描 -->
<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" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 3、自定义注解,这个自定义注解其实可以理解成spring的@Transactional这个注解,为了模拟的更真实,就使用自定义注解,也就是说,当我们的方法上面标注了这个自定义的注解之后,事务就开启了,

/**

  • 自定义事务注解
    */

@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ExtTransaction {

}
1
2
3
4
5
6
7
8
9
10
11
12
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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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());
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
	
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}

}
1
2
3
4
5
6
7
8
9
10
运行main函数,看看控制台的输出结果,首先我们来看一下正常的执行结果,

在这里插入图片描述

再看看数据库的结果,成功插入了两条结果,
在这里插入图片描述

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

在这里插入图片描述

再来执行一下,可以看到,异常被我们的自定义事务捕获到了,
在这里插入图片描述

再看看数据库,数据并没有插入,说明我们的自定义事务生效了,
在这里插入图片描述

以上便是整个手动编写spring编程式事务过程,感谢观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值