天天看點

Spring使用純注解配置事務管理并實作簡單的增删查改以及模拟轉賬功能

在之前的文章,筆者已經使用XML配置實作了對MySQL的事務管理使用Spring的事務管理器配置資料庫的事務,Spring也支援使用注解進行配置進而實作對事務的管理,此次便詳細說明如何使用注解配置實作Spring事務的管理。

1.首先建立實驗測試環境

1.1建立資料庫以及資料表Account。

create database if not exists springdemo;
use springdemo;
create table if not exists Account(id int(4) auto_increment primary key,name varchar(16) not null,password varchar(16) not null,age int(3) ,createtime datetime default now(),money int(8));


           

1.2在idea中建立maven工程。

Spring使用純注解配置事務管理并實作簡單的增删查改以及模拟轉賬功能

1.3在pom.xml中引入必須的依賴檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>springdemo_tx_annotaiton</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.8.RELEASE</spring.version>
    </properties>
    <dependencies>
<!--        引入Spring依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 引入MySQL依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <!-- 引入c3p0依賴-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
<!--        引入測試依賴-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>
           

1.4建立資料庫連接配接配置檔案db.properties

jdbc.url=jdbc:mysql://localhost:3306/springdemo?serverTimezone=UTC
jdbc.name=root
jdbc.password=root
jdbc.driver=com.mysql.cj.jdbc.Driver
           

2.開始書寫dao,entity,service包下的代碼。

2.1建立實體類

package entity;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

import java.util.Date;

/**
 * Classname:Account
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-01 17:33
 * @Version: 1.0
 **/
@Repository("account")
@Scope("prototype")
public class Account {
    //使用者主鍵ID
    private int id;
    //使用者名
    private String name;
    //使用者密碼
    private String password;
    //使用者年齡
    private int age;
    //使用者建立時間
    private Date createTime ;
    //使用者的餘額
    private int money;

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", createTime=" + createTime +
                ", monney=" + money +
                '}';
    }
}

           

2.2建立用和通路資料庫的持久層接口以及其實作類

package dao;

import entity.Account;

import java.util.List;

/**
 * Classname:springdemo3
 *
 * @description:{description}
 * @author: 陌意随影
 * @Date: 2020-08-01 17:32
 */
public interface AccountDao {
    /**
     * @Description :儲存使用者
     * @Date 11:51 2020/8/9 0009
     * @Param * @param account :
     * @return boolean
     **/
    public boolean saveAccount(Account account);
    /**
     * @Description :更新使用者
     * @Date 11:51 2020/8/9 0009
     * @Param * @param newAccount :
     * @return boolean
     **/
    public boolean updateAccount(Account newAccount);
    /**
     * @Description :通過ID删除使用者
     * @Date 11:52 2020/8/9 0009
     * @Param * @param id :
     * @return boolean
     **/
    public boolean deleteAccountById(int id);
    /**
     * @Description :通過ID查詢使用者
     * @Date 11:52 2020/8/9 0009
     * @Param * @param id :
     * @return entity.Account
     **/
    public Account findAccountById(int id);
    /**
     * @Description :查找所有使用者
     * @Date 11:52 2020/8/9 0009
     * @Param * @param  :
     * @return java.util.List<entity.Account>
     **/
    public List<Account> findAll();
}

           
package dao;

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

import java.util.List;

/**
 * Classname:AccountDaoImpl
 *
 * @description:AccountDao的實作類
 * @author: 陌意随影
 * @Date: 2020-08-01 17:32
 * @Version: 1.0
 **/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    //用于進行MySQL增删查改,自動注入
    @Autowired
    JdbcTemplate jdbcTemplate = null;
   /**
    * @Description :儲存使用者
    * @Date 12:17 2020/8/9 0009
    * @Param * @param newAccount :
    * @return boolean
    **/
    public boolean saveAccount(Account newAccount) {
            return jdbcTemplate.update("insert into account(name,password,age,createTIme,money) values(?,?,?,?,?)",
                    newAccount.getName(),newAccount.getPassword(),
                    newAccount.getAge(),newAccount.getCreateTime(),newAccount.getMoney())== 1;
    }
  /**
   * @Description :更新使用者
   * @Date 12:18 2020/8/9 0009
   * @Param * @param newAccount :
   * @return boolean
   **/
    public boolean updateAccount(Account newAccount) {
            return jdbcTemplate.update("update account set name=?,password=?,age=?,createTime=?,money=? where id=?",
                    newAccount.getName(),newAccount.getPassword(),
                    newAccount.getAge(),newAccount.getCreateTime(),newAccount.getMoney(),newAccount.getId())==1;
    }
  /**
   * @Description :通過ID删除使用者
   * @Date 12:18 2020/8/9 0009
   * @Param * @param id :
   * @return boolean
   **/
    public boolean deleteAccountById(int id) {
            return jdbcTemplate.update("delete from account where id=?",id) == 1;
    }
/**
 * @Description :通過ID查詢使用者
 * @Date 11:02 2020/8/9 0009
 * @Param * @param id :
 * @return entity.Account
 **/
    public Account findAccountById(int id) {
            List<Account> list = jdbcTemplate.query("select* from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
            if (list.isEmpty()){
                return  null;
            }
            if (list.size()==1){
                return list.get(0);
            }
            return  null;

    }
/**
 * @Description :查詢所有喲使用者
 * @Date 11:01 2020/8/9 0009
 * @Param * @param  :
 * @return java.util.List<entity.Account>
 **/
    public List<Account> findAll() {
            return jdbcTemplate.query("select* from account",new BeanPropertyRowMapper<Account>(Account.class));
    }

}

           

用于通路資料庫的JdbcTemplate需要自動注入,等會再在配置類中配置這個JdbcTemplate用于注入。

2.3書寫業務邏輯接口以及其實作類

package service;

import entity.Account;

import java.util.List;

/**
 * Classname:springdemo3
 *
 * @description:{description}
 * @author: 陌意随影
 * @Date: 2020-08-01 17:35
 */
public interface AccountService {
        /**
         * @Description :儲存使用者
         * @Date 11:51 2020/8/9 0009
         * @Param * @param account :
         * @return boolean
         **/
        public boolean saveAccount(Account account);
        /**
         * @Description :更新使用者
         * @Date 11:51 2020/8/9 0009
         * @Param * @param newAccount :
         * @return boolean
         **/
        public boolean updateAccount(Account newAccount);
        /**
         * @Description :通過ID删除使用者
         * @Date 11:52 2020/8/9 0009
         * @Param * @param id :
         * @return boolean
         **/
        public boolean deleteAccountById(int id);
        /**
         * @Description :通過ID查詢使用者
         * @Date 11:52 2020/8/9 0009
         * @Param * @param id :
         * @return entity.Account
         **/
        public Account findAccountById(int id);
        /**
         * @Description :查找所有使用者
         * @Date 11:52 2020/8/9 0009
         * @Param * @param  :
         * @return java.util.List<entity.Account>
         **/
        public List<Account> findAll();
   /**
    * @Description :從使用者ID為sourceId的使用者向使用者ID為targetId的使用者轉賬money
    * @Date 11:55 2020/8/9 0009
    * @Param * @param sourceId
    * @param targetId
    * @param money :
    * @return boolean
    **/
    public boolean tranferMoney(int sourceId,int targetId,int money);
}

```java
package service;
import dao.AccountDao;
import entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
/**
 * Classname:AccountServiceImpl
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-01 17:35
 * @Version: 1.0
 **/
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
public class AccountServiceImpl implements AccountService {
    //使用者資料可通路的Dao接口
    @Autowired
    private AccountDao accountDao;
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = false)
    public boolean saveAccount(Account account) {
        return accountDao.saveAccount(account);
    }
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = false)
    public boolean updateAccount(Account newAccount) {

        return accountDao.updateAccount(newAccount);
    }
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = false)
    public boolean deleteAccountById(int id) {
        return accountDao.deleteAccountById(id);
    }
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = true)
    public Account findAccountById(int id) {
        return accountDao.findAccountById(id);
    }
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = true)
    public List<Account> findAll() {
        return accountDao.findAll();
    }
    /**
     * @Description :從使用者ID為sourceId的使用者向使用者ID為targetId的使用者轉賬money
     * @Date 11:55 2020/8/9 0009
     * @Param * @param sourceId
     * @param targetId
     * @param money :
     * @return boolean
     **/
    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = false)
    public boolean tranferMoney(int sourceId, int targetId, int money) {
           //擷取sourceId對應的使用者
            Account sourceAccount = this.findAccountById(sourceId);
        //擷取targetId對應的使用者
            Account targetAccount = this.findAccountById(targetId);
            //轉賬失敗
            if (sourceAccount == null || targetAccount == null) {
                return  false;
            }
            //轉賬者扣去轉賬的金額
            sourceAccount.setMoney(sourceAccount.getMoney() -money);
            //目标對象加上擷取的轉賬金額
            targetAccount.setMoney(targetAccount.getMoney()+money);
            //更新轉賬者的賬戶
        boolean b = this.updateAccount(sourceAccount);
        //模拟異常
        int i = 1/0;
        //更新轉賬目标者的賬戶
        boolean b1 = this.updateAccount(targetAccount);
            return  b == true && b1 == true;

    }
}

           

AccountDao會自動被注入,我們要實作事務管理就是要在業務邏輯中進行事務管理,是以需要在AccountServiceImpl中添加@Transactional注解。

[email protected] 基于 Spring 的動态代理的機制

[email protected] 實作原理:

(1)事務開始時,通過AOP機制,生成一個代理connection對象,

并将其放入 DataSource 執行個體的某個與 DataSourceTransactionManager 相關的某處容器中。 在接下來的整個事務中,客戶代碼都應該使用該 connection 連接配接資料庫, 執行所有資料庫指令。 如果不使用該 connection 連接配接資料庫執行的資料庫指令,那麼在本事務復原的時候得不到復原,進而無法實作事務的管理。物接 connection 邏輯上建立一個會話session,DataSource 與 TransactionManager 配置相同的資料源.

(2) 事務結束時,復原在第1步驟中得到的代理 connection 對象上執行的資料庫指令, 然後關閉該代理 connection 對象。事務結束後,復原操作不會對已執行完畢的SQL操作指令起作用.

3.2事務的兩種開啟方式:

(1)顯示開啟 start transaction | begin,通過 commit | rollback 結束事務 (2)關閉資料庫中自動送出 autocommit set autocommit = 0, MySQL 預設開啟自動送出;通過手動送出或執行復原操作來結束事務.

3.3事務的隔離級别:是指若幹個并發的事務之間的隔離程度

3.3.1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):讀取未送出資料(會出現髒讀, 不可重複讀) 基本不使用.

3.3.2. @Transactional(isolation = Isolation.READ_COMMITTED):讀取已送出資料(會出現不可重複讀和幻讀).

3.3.3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重複讀(會出現幻讀).

3.3.4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化.

3.4事務傳播行為:如果在開始目前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為.

3.4.1. TransactionDefinition.PROPAGATION_REQUIRED:

如果目前存在事務,則加入該事務;如果目前沒有事務,則建立一個新的事務。這是預設值。

3.4.2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:

建立一個新的事務,如果目前存在事務,則把目前事務挂起。

3.4.3. TransactionDefinition.PROPAGATION_SUPPORTS:

如果目前存在事務,則加入該事務;如果目前沒有事務,則以非事務的方式繼續運作。

3.4.4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:

以非事務方式運作,如果目前存在事務,則把目前事務挂起。

3.4.5. TransactionDefinition.PROPAGATION_NEVER:

以非事務方式運作,如果目前存在事務,則抛出異常。

3.4.6. TransactionDefinition.PROPAGATION_MANDATORY:

如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。

3.4.7. TransactionDefinition.PROPAGATION_NESTED:

如果目前存在事務,則建立一個事務作為目前事務的嵌套事務來運作;

如果目前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。

3.5Transactional屬性配置

Spring使用純注解配置事務管理并實作簡單的增删查改以及模拟轉賬功能

3.5.1. value :主要用來指定不同的事務管理器;

主要用來滿足在同一個系統中,存在不同的事務管理器。

比如在Spring中,聲明了兩種事務管理器txManager1, txManager2.然後,

使用者可以根據這個參數來根據需要指定特定的txManager.

3.5.2. value 适用場景:在一個系統中,需要通路多個資料源或者多個資料庫,

則必然會配置多個事務管理器的

3.5.3. REQUIRED_NEW:内部的事務獨立運作,在各自的作用域中,可以獨立的復原或者送出;

而外部的事務将不受内部事務的復原狀态影響。

3.5.4. ESTED 的事務,基于單一的事務來管理,提供了多個儲存點。

這種多個儲存點的機制允許内部事務的變更觸發外部事務的復原。

而外部事務在混滾之後,仍能繼續進行事務處理,即使部分操作已經被混滾。

由于這個設定基于 JDBC 的儲存點,是以隻能工作在 JDB C的機制。

3.5.5. rollbackFor:讓受檢查異常復原;即讓本來不應該復原的進行復原操作。

3.5.6. noRollbackFor:忽略非檢查異常;即讓本來應該復原的不進行復原操作。

4.書寫配置檔案類

Spring使用純注解配置事務管理并實作簡單的增删查改以及模拟轉賬功能
package config;

import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Classname:SpringConfig
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-09 21:50
 * @Version: 1.0
 **/

@Configuration             //聲明是配置檔案類
@ComponentScan({"dao","entity","service"})//指定要掃描的包
@EnableTransactionManagement  //開啟事務管理
@PropertySource("classpath:db.properties")//引入資料庫配置的資源檔案
@Import({JdbcConfig.class,TransactionConfig.class})  //引入指定的配置類
public class SpringConfig {

}

           
package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
 * Classname:JdbcConfig
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-09 21:53
 * @Version: 1.0
 **/
public class JdbcConfig {
    //自動從資料庫配置檔案db.properties中擷取jdbc.driver對應的值來複制給driver
   @Value("${jdbc.driver}")
    private String driver;
    //自動從資料庫配置檔案db.properties中擷取jdbc.name對應的值來複制給userName
    @Value("${jdbc.name}")
    private String userName;
    //自動從資料庫配置檔案db.properties中擷取jdbc.password對應的值來複制給password
    @Value("${jdbc.password}")
    private String password;
    //自動從資料庫配置檔案db.properties中擷取jdbc.url對應的值來複制給url
    @Value("${jdbc.url}")
   private  String url;
    /**
     * @Description :使用Bean注解将建立的DataSource存入到Spring容器中友善使用
     * @Date 22:22 2020/8/10 0010
     * @Param * @param  :
     * @return javax.sql.DataSource
     **/
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        //建立一個c3p0資料庫連接配接池資料源
        ComboPooledDataSource  comboPooledDataSource = new ComboPooledDataSource();
        try {
            //設定資料庫連接配接驅動
            comboPooledDataSource.setDriverClass(driver);
            //設定資料庫使用者名
            comboPooledDataSource.setUser(userName);
            //設定資料庫連接配接密碼
            comboPooledDataSource.setPassword(password);
            //設定資料庫的連接配接URL
            comboPooledDataSource.setJdbcUrl(url);
            return comboPooledDataSource;
        } catch (Exception e) {
           throw new RuntimeException("資料庫配置出錯!");
        }
    }
    /**
     * @Description :使用Bean注解将建立的JdbcTemplate存入到Spring容器中友善使用
     * @Date 22:22 2020/8/10 0010
     * @Param * @param  DataSource:
     * @return javax.sql.DataSource
     **/
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource) ;
    }
}

           
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * Classname:TransactionConfig
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-09 22:11
 * @Version: 1.0
 **/
public class TransactionConfig {
    /**
     * @Description :從Spring容器中拿到配置好的DataSource,然話u根據傳入的資料源DataSource建立事務管理對象
     * PlatformTransactionManager并存入Spring容器中
     * @Date 22:13 2020/8/9 0009
     * @Param * @param dataSource :
     * @return org.springframework.transaction.PlatformTransactionManager
     **/
    @Bean
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return  new DataSourceTransactionManager(dataSource);
    }
}

           

5.建立測試類

package test;

import config.SpringConfig;
import entity.Account;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import service.AccountService;
import java.util.Date;
import java.util.List;

/**
 * Classname:AccountTest
 *
 * @description:
 * @author: 陌意随影
 * @Date: 2020-08-01 17:39
 * @Version: 1.0
 **/
public class AccountTest {
    private ApplicationContext applicationContext;
    private  AccountService accountService;
    @BeforeEach
    public void init(){
        applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    }
    @AfterEach
    public void destroy(){

    }
    @Test
    public  void testSave(){
        Account account = applicationContext.getBean("account", Account.class);
        account.setAge(434);
        account.setName("李四");
        account.setPassword("打個卡的撒老顧客");
        account.setCreateTime(new Date());
        account.setMoney(1000);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        boolean fla = accountService.saveAccount(account);
        System.out.println("插入使用者:"+fla);
    }
    @Test
    public  void testUpdate(){
        Account account = applicationContext.getBean("account", Account.class);
        account.setAge(77);
        account.setName("站幹啥");
        account.setPassword("54545");
        account.setCreateTime(new Date());
        account.setId(3);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        boolean fla = accountService.updateAccount(account);
        System.out.println("更新使用者:"+fla);
    }
    @Test
    public  void testDelete(){
        Account account = applicationContext.getBean("account", Account.class);
        account.setId(5);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        boolean fla = accountService.deleteAccountById(account.getId());
        System.out.println("删除使用者:"+fla);
    }
    @Test
    public  void  testFindOne(){
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        Account account1 = accountService.findAccountById(6);
        System.out.println("查找單個使用者"+account1);
    }  
    @Test
    public void  testFindAll(){
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        List<Account> accountList = accountService.findAll();
        for (Account a :accountList) {
            System.out.println(a);

        }
    }
    @Test
    public void testTran(){
        //擷取沒有經過事務管理的普通AccountServiceImpl
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        accountService.tranferMoney(3,6,100);
    }
}

           

建立好後就可以進行測試。

6.本次實驗代碼已經上傳到個人部落格,如有需要請自行移步下載下傳(在文章底部有下載下傳連接配接):

http://moyisuiying.com/index.php/javastudy/238.html

繼續閱讀