Spring事务分为声明式事务(注解或包扫描)和编程式(在代码里提交或回滚)事务,声明式事务就是在编程式事务的基础上加上AOP计数进行包装
这个工程为了实验事务的回滚,使用用了数据库,使用了jdbc模板链接数据库 ,数据库链接池配置再RootConfig里
我导入的Maven依赖以下java
<dependencies>
<!-- 引入Spring-AOP等相关Jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
<!--mysql链接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--链接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
复制代码
配置类以下,用于代替有些过期的XML配置Spring
mysql
@Configuration
@ComponentScan(basePackages = {"com.libi"})
@EnableAspectJAutoProxy
public class RootConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/sms?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
复制代码
须要加入事务的方法以下userDao是会操做数据的,在中间的间隔会抛出异常spring
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public void add() {
userDao.add("test001","1233321");
System.out.println("中间的间隔,且出现异常");
int i = 1 / 0;
userDao.add("test002","135365987");
}
}
复制代码
这时只会插入test001的语句,test002不会插入成功。
sql
这时咱们封装一个事务工具数据库
@Component
@Scope("prototype")
public class TransactionUtils {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
private TransactionStatus status;
/** 开启事务*/
public TransactionStatus begin() {
//使用默认的传播级别
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transaction;
}
/** 提交事务 须要传入这个事务状态*/
public void commit() {
dataSourceTransactionManager.commit(status);
}
/**回滚事务 须要传入这个事务状态*/
public void rollBack() {
//获取当前事务,若是有,就回滚
if (status != null) {
dataSourceTransactionManager.rollback(status);
}
}
}
复制代码
再这样使用,修改add方法编程
public void add() {
TransactionStatus begin = null;
try {
begin = transactionUtils.begin();
userDao.add("test001", "1233321");
System.out.println("中间的间隔,且出现异常");
int i = 1 / 0;
userDao.add("test002", "135365987");
transactionUtils.commit();
} catch (Exception e) {
e.printStackTrace();
transactionUtils.rollBack();
}
}
复制代码
咱们使用AOP编程把刚刚的事务工具封装一下安全
@Component
@Aspect
public class AopTransaction {
@Autowired
private TransactionUtils transactionUtils;
@Around("execution(* com.libi.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开启事务");
proceedingJoinPoint.proceed();
System.out.println("提交事务");
transactionUtils.commit();
}
@AfterThrowing("execution(* com.libi.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("回滚事务");
//获取当前事务,直接回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
复制代码
而后清空原来的方法里全部的try代码,让他回到最初的状态(不能捕获异常,否者出现异常后不能被异常通知捕获到,致使事务不生效)bash
在Spring里已经帮咱们实现类注解事务,须要在配置类里添加下面的注解来开启注解事务的支持框架
@EnableTransactionManagement
复制代码
而后注释掉咱们上次的AOP注解,使用@Transactional(rollbackFor = Exception.class)
的注解开启这个方法的事务,rollbackFor
标识须要回滚的异常类,整个方法以下工具
@Transactional(rollbackFor = Exception.class)
public void add() {
userDao.add("test001", "1233321");
System.out.println("中间的间隔,且出现异常");
int i = 1 / 0;
userDao.add("test002", "135365987");
}
复制代码
这样也能够实现这个方法的事务。固然,这个方法里也不能捕获异常,这样仍然会致使没法触发异常通知而致使事务无效
咱们就以这种效果做为模板手写事务的框架
/**
* @author libi
* 本身实现的事务注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {
}
复制代码
@Component
@Aspect
public class AopAnnotationTransaction {
@Autowired
private TransactionUtils transactionUtils;
/**这边规定扫描service下的全部方法*/
@Around("execution(* com.libi.service.*.*(..))")
//获取方法上的注解,这里把获取注解的方法单独提出来了
ExtTransaction extTransaction = getExtTransaction(proceedingJoinPoint);
TransactionStatus status = null;
if (extTransaction != null) {
//若果有事务,开启事务
System.out.println("开启事务");
status = transactionUtils.begin();
}
//调用代理目标方法
proceedingJoinPoint.proceed();
if (status != null) {
//提交事务
System.out.println("提交事务");
transactionUtils.commit();
}
}
/**事务的异常通知*/
@AfterThrowing("execution(* com.libi.service.*.*.*(..))")
public void afterThrowing() {
System.out.println("回滚事务");
transactionUtils.rollBack();
}
/**获取方法上的注解*/
private ExtTransaction getExtTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
//获取代理对象的方法
String methodName = proceedingJoinPoint.getSignature().getName();
Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
Class[] parameterTypes = ((MethodSignature) (proceedingJoinPoint.getSignature())).getParameterTypes();
Method targetMethod = targetClass.getMethod(methodName, parameterTypes);
//获取方法上的注解
return targetMethod.getAnnotation(ExtTransaction.class);
}
}
复制代码
还要注意的是,TransactionUtils类仍然须要时多例的,否则会出现线程安全问题