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類仍然須要時多例的,否則會出現線程安全問題