1. 程式人生 > >spring事物(2)-----手寫spring註解事務&&事務傳播行為

spring事物(2)-----手寫spring註解事務&&事務傳播行為

一,spring事務的註解

1.1,spring自帶的@Transactional例子

 

package com.qingruihappy1.dao;

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); } }

 

 

package com.qingruihappy1.service;

//user 服務層
public interface UserService {

    public void add();

}

 

 

package com.qingruihappy1.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.qingruihappy1.dao.UserDao;
import com.qingruihappy1.service.UserService;



//user 服務層 @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; //宣告:@Transactional 或者XML方式 //方法執行開始執行前,開啟提交事務 @Transactional public void add() { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################" + i); userDao.add("test002", 21); //注意下面的程式碼是不會回滾的,因為異常給吃掉了,所以是不會回滾的。 //因為回滾的機制在異常中的,現在吃掉異常之後就不會進入異常的方法中,所以自然而然的不會回滾的。 /* try { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################" + i); userDao.add("test002", 21); } catch (Exception e) { e.printStackTrace(); }*/ } // 方法執行完畢之後,才會提交事務 }

 

 

<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"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 開啟事物註解 -->
    <!-- 1. 資料來源物件: C3P0連線池 -->
    <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/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>


    <!-- 2. JdbcTemplate工具類例項 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3.配置事務 -->
    <bean id="dataSourceTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 開啟註解事務 注意自定義註解的時候把它註釋掉,用spring自帶的話則加上 -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
</beans>

 

 

測試:

package com.qingruihappy1.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.qingruihappy1.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();
    }

}

 

 

 

1.2,自定義註解案例

package com.qingruihappy2.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Target(value = ElementType.METHOD) 設定註解許可權<br>
 * @author qingruihappy
 *
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
// @interface 定義註解
public @interface AddAnnotation {

    // 手寫Spring事務註解
    int userId() default 0;

    String userName() default "預設名稱";

    String[]arrays();
}

 

 

package com.qingruihappy2.annotation;

import java.lang.reflect.Method;

public class User {

    @AddAnnotation(userName = "張三", userId = 18, arrays = { "1" })
    public void add() {

    }

    public void del() {

    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 怎麼樣獲取到方法上註解資訊 反射機制
        Class<?> forName = Class.forName("com.qingruihappy2.annotation.User");
        // 獲取到當前類(不包含繼承)所有的方法
        Method[] declaredMethods = forName.getDeclaredMethods();
        for (Method method : declaredMethods) {
            // 獲取該方法上是否存在註解
            System.out.println("####方法名稱" + method.getName());
            AddAnnotation addAnnotation = method.getAnnotation(AddAnnotation.class);
            if (addAnnotation == null) {
                // 該方法上沒有註解
                System.out.println("該方法上沒有加註解..");
                continue;
            }
            // 在該方法上查詢到該註解
            System.out.println("userId:" + addAnnotation.userId());
            System.out.println("userName:" + addAnnotation.userName());
            System.out.println("arrays:" + addAnnotation.arrays());
            System.out.println();
        }
    }

}

 

 

####方法名稱add
userId:18
userName:張三
arrays:[Ljava.lang.String;@18760838

####方法名稱main
該方法上沒有加註解..
####方法名稱del
該方法上沒有加註解..

 

 

1.3,手寫spring註解事務

步驟

 

package com.qingruihappy3.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoa {
    @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);
    }

}

 

 

package com.qingruihappy3.service;

//user 服務層
public interface UserService {

    public void add();

    public void del();
}

 

package com.qingruihappy3.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.qingruihappy3.annotation.ExtTransaction;
import com.qingruihappy3.dao.UserDaoa;
import com.qingruihappy3.service.UserService;

//user 服務層
@Service
public class UserServiceImpla implements UserService {
    @Autowired
    private UserDaoa userDao;

    /*
     * 宣告:@Transactional 或者XML方式 方法執行開始執行前,開啟提交事務
     */
    @ExtTransaction
    public void add() {
        userDao.add("test001", 20);
         int i = 1 / 0;
         System.out.println("################" + i);
        userDao.add("test002", 21);
        // 獲取當前事務,手動進行回滾
    }

    // 方法執行完畢之後,才會提交事務
    public void del() {
        System.out.println("del");
    }

}

 

註解:

package com.qingruihappy3.annotation;

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({ ElementType.METHOD })//只在方法上起作用
@Retention(RetentionPolicy.RUNTIME)//執行時的註解
public @interface ExtTransaction {

}

 

 工具類

package com.qingruihappy3.transaction;

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") // 每個事務都是新的例項 目的解決執行緒安全問題 多例子 prototype就是多例的。
public class TransactionUtils {

    // 全域性接受事務狀態 TransactionStatus宣告為全域性的原因就是讓每個事務都建立一個例項,防止出現執行緒安全的問題
    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);
    }

}

 

切面類:

package com.qingruihappy3.aop;

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.qingruihappy3.annotation.ExtTransaction;
import com.qingruihappy3.transaction.TransactionUtils;



//  自定義事務註解具體實現
@Aspect
@Component
public class AopExtTransaction {
    // 一個事務例項子 針對一個事務
    @Autowired
    private TransactionUtils transactionUtils;

    // 使用異常通知進行 回滾事務
    @AfterThrowing("execution(* com.qingruihappy3.service.*.*.*(..))")
    public void afterThrowing() {
        // 獲取當前事務進行回滾
        // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        transactionUtils.rollback();
    }

    // 環繞通知 在方法之前和之後處理事情
    @Around("execution(* com.qingruihappy3.service.*.*.*(..))")
    public void around(ProceedingJoinPoint pjp) throws Throwable {

        // 1.獲取該方法上是否加上註解
        ExtTransaction extTransaction = getMethodExtTransaction(pjp);
        TransactionStatus transactionStatus = begin(extTransaction);
        // 2.呼叫目標代理物件方法
        pjp.proceed();
        // 3.判斷該方法上是否就上註解
        commit(transactionStatus);
    }

    private TransactionStatus begin(ExtTransaction extTransaction) {
        if (extTransaction == null) {
            return null;
        }
        // 2.如果存在事務註解,開啟事務
        return transactionUtils.begin();
    }

    private void commit(TransactionStatus transactionStatus) {
        if (transactionStatus != null) {
            // 5.如果存在註解,提交事務
            transactionUtils.commit(transactionStatus);
        }

    }

    // 獲取方法上是否存在事務註解
    private ExtTransaction getMethodExtTransaction(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);
        ExtTransaction extTransaction = objMethod.getAnnotation(ExtTransaction.class);
        return extTransaction;
    }

}

 

 

 

package com.qingruihappy3;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.qingruihappy3.service.UserService;


public class Test001 {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = (UserService) applicationContext.getBean("userServiceImpla");
        userService.add();
    }

}

 

 

二:事務的傳播行為

我們通過一張圖引入

 這個怎麼實現呢?

我們來看這7中傳播行為

Propagation(key屬性確定代理應該給哪個方法增加事務行為。這樣的屬性最重要的部份是傳播行為。)有以下選項可供使用:

PROPAGATION_REQUIRED--支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。

PROPAGATION_SUPPORTS--支援當前事務,如果當前沒有事務,就以非事務方式執行。

PROPAGATION_MANDATORY--支援當前事務,如果當前沒有事務,就丟擲異常。 

PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。 

PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 

PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則丟擲異常。

 

我們通過程式碼來看:

package com.qingruihappy4.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class LogDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void add(String name) {
        String sql = "INSERT INTO t_log(log_name) VALUES(?);";
        int updateResult = jdbcTemplate.update(sql, name);
        System.out.println("##LogDao##updateResult:" + updateResult);
    }

}

 

 

package com.qingruihappy4.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoc {
    @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);
    }

}

 

 

package com.qingruihappy4.service;

public interface LogService {

    public void addLog();

}
package com.qingruihappy4.service;

//user 服務層
public interface UserService {

    public void add();

    public void del();
}
package com.qingruihappy4.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.qingruihappy4.dao.LogDao;
import com.qingruihappy4.service.LogService;



@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW)關鍵就是這一行程式碼
    public void addLog() {
        logDao.add("addLog" + System.currentTimeMillis());
    }

}
package com.qingruihappy4.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.qingruihappy4.dao.UserDaoc;
import com.qingruihappy4.service.LogService;
import com.qingruihappy4.service.UserService;



//user 服務層
@Service
public class UserServiceImplc implements UserService {
    @Autowired
    private UserDaoc userDao;
    @Autowired
    private LogService logService;
    
    @Transactional
    public void add() {
        // 呼叫介面的時候 介面失敗 需要回滾,但是日誌記錄不需要回滾。
        logService.addLog(); // 後面程式發生錯誤,不能影響到我的回滾### 正常當addLog方法執行完畢,就應該提交事務
        userDao.add("test001", 20);
        int i = 1 / 0;
        System.out.println("################");
        userDao.add("test002", 21);

    }
    // 方法執行完畢之後,才會提交事務

    public void del() {
        System.out.println("del");
    }

}
package com.qingruihappy4;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.qingruihappy4.service.UserService;


public class Test001 {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = (UserService) applicationContext.getBean("userServiceImplc");
        userService.add();
    }

}

 

 

 關鍵就是上面標紅的程式碼。定義一個兩套事務互不影響的。