手寫源碼(一):自己實現Spring事務

NO IMAGE

手寫Spring事務

Spring事務分為聲明式事務(註解或包掃描)和編程式(在代碼裡提交或回滾)事務,聲明式事務就是在編程式事務的基礎上加上AOP計數進行包裝
這個工程為了實驗事務的回滾,使用用了數據庫,使用了jdbc模板連接數據庫 ,數據庫連接池配置再RootConfig裡
我導入的Maven依賴如下

    <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

@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是會操作數據的,在中間的間隔會拋出異常

 @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不會插入成功。

編程式事務

這時我們封裝一個事務工具

@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代碼,讓他回到最初的狀態(不能捕獲異常,否者出現異常後不能被異常通知捕獲到,導致事務不生效

註解式事務

在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 {
}
  • 封裝手動事務(使用原來的TransactionUtils類)
  • 使用AOP掃描規定包下的註解
    • 在AOP上封裝找到註解並且加上註解的操作
@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類仍然需要時多例的,不然會出現線程安全問題

事務傳播行為

  • 什麼是傳播行為(Propagation) :事務的傳播行為產生在調用事務中,也就是說當小個事務嵌套在大事務裡時,會發生怎樣的行為
  • 傳播行為的種類
    • PROPAGATION_REQUIRED—如果當前有事務,就用當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。(如果大的方法有事務,那麼需要事務的小方法就加入到這個事務裡去,如果大方法沒有事務,就創建事務
    • PROPAGATION_SUPPORTS–支持當前事務,如果當前沒有事務,就以非事務方式執行。//(如果外層方法沒有事務,就會以非事務進行執行。這樣相當於默認沒有事務
    • PROPAGATION_MANDATORY–支持當前事務,如果當前沒有事務,就拋出異常。
    • PROPAGATION_REQUIRES_NEW–新建事務,如果當前存在事務,把當前事務掛起(互不影響,運行到小事務時暫停大事務)。
    • PROPAGATION_NOT_SUPPORTED–以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
    • — 如果當前有事務,就是以非事務進行執行
    • PROPAGATION_NEVER–以非事務方式執行,如果當前存在事務,則拋出異常。

相關文章

團長大人的學習筆記——自定義Java註解

手寫源碼(四):自己實現Mybatis

手寫源碼(三):自己實現SpringMVC

手寫源碼(二):自己實現SpringIOC