1. 程式人生 > 實用技巧 >Spring(10)深入Spring 資料庫事務管理(二)

Spring(10)深入Spring 資料庫事務管理(二)

一、Spring事務配置

1.程式設計式事務

  程式設計時事務以程式碼方式管理事務,就是說事務由開發者通過程式碼方式實現,這裡需要使用一個事務定類介面TransactionDefinition,我們使用預設實現類DefaultTransactionDefinition就可以。這裡不做重點介紹。

2.宣告式事務

  程式設計式事務是一種約定型的事務,在大部分情況下用資料庫事務時,大部分的場景是在程式碼中發生了異常時,需要回滾事務,而不發生異常時則是提交事務,從而保證資料庫資料的一致性。從這點出發,Spring 給了一個約定 (AOP 開發也給了我們一個約定),

如果使用的是宣告式事務,當業務方法不發生異常(或者發生異常 但該異常也被配置資訊允許提交事務)時, Spring 就會讓事務管理器提交事務。而發生異常(並且該異常不被你的配置資訊所允許提交事務)時,則讓事務管理器回滾事務。

3.基於XML的宣告式事務控制(配置方式)重點

(1)環境搭建

  1》引入依賴包

<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
>com.xhbjava</groupId> <artifactId>Spring02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> </dependencies> </project>

  2》建立Spring配置檔案並引入約束

  需要匯入 aop 和 tx 兩個名稱空間
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/tx 
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

  3》準備實體類和資料庫表

    這裡我們還是以以前建立好的account表和類進行。

  4》業務介面和實現層編寫

package com.xhbjava.service;

import com.xhbjava.pojo.Account;

/**
 * 賬戶的業務層介面
 * 
 * @author mr.wang
 *
 */
public interface IAccountService {
/**
 * 根據id查詢賬戶資訊
 * @param id
 * @return
 */
Account findAccountById(Integer id);

    /**
     * 轉賬
     * 
     * @param sourceName
     * @param targeName
     * @param money
     */
void transfer(String sourceName,String targeName,Float money);

}
package com.xhbjava.service.impl;

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

/**
 * 賬戶業務層介面實現類
 * 
 * @author mr.wang
 *
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public Account findAccountById(Integer id) {
        // TODO Auto-generated method stub
        return accountDao.findAccountById(id);
    }
    @Override
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根據名稱查詢兩個賬戶
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改兩個賬戶的金額
        source.setMoney(source.getMoney() - money);// 轉出賬戶減錢
        target.setMoney(target.getMoney() + money);// 轉入賬戶加錢
        // 3.更新兩個賬戶
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

}

  5》Dao層介面和實現類類

package com.xhbjava.dao;

import com.xhbjava.pojo.Account;

/**
 * 賬戶持久層介面
 * 
 * @author mr.wang
 *
 */
public interface IAccountDao {
    /**
     * 根據id查詢賬戶
     * 
     * @param id
     * @return
     */
    Account findAccountById(Integer id);

    /**
     * 根據名字查詢賬戶
     * 
     * @param sourceName
     * @return
     */
    Account findAccountByName(String sourceName);

    /**
     * 更新賬戶
     * 
     * @param source
     */
    void updateAccount(Account source);

}
package com.xhbjava.dao.impl;

import java.util.List;

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

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.pojo.AccountRowMapper;

/**
 * 使用者持久層介面實現類
 * 
 * @author mr.wang
 *
 */
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccountById(Integer id) {
        List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public Account findAccountByName(String sourceName) {
        List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(),
                sourceName);
        if (list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件");
        }
        return list.get(0);
    }

    @Override
    public void updateAccount(Account source) {
        // TODO Auto-generated method stub
        jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId());
    }

}
package com.xhbjava.pojo;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;
/**
 * 賬戶的封裝類 RowMapper 的實現類
 * @author mr.wang
 *
 */
public class AccountRowMapper implements RowMapper<Account>{

public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
    Account account = new Account();
    account.setId(rs.getInt("id"));
    account.setName(rs.getString("name"));
    account.setMoney(rs.getFloat("money"));
    return account;
}
}

6》Spring檔案中配置業務層和持久層以及資料等

<!-- 配置service -->
    <bean id="accountService"
        class="com.xhbjava.service.impl.AccountServiceImpl">
    </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.xhbjava.dao.impl.AccountDaoImpl">
    </bean>
    <!-- 資料庫模板配置 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 資料庫配置 -->
    <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/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

(2)事務配置

  1》配置事務管理器

  在Spring配置檔案中進行配置:

<!-- 配置事務管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入dataSource -->
    <property name="dataSource" ref="dataSource"></property>
    </bean>

  2》配置事務通知引用事務管理器

<!-- 配置事務管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

3》配置事務的屬性

<!-- 配置事務的通知引用事務管理器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" read-only="false" propagation="REQUIRED" />
            <tx:method name="find*" read-only="true" propagation="SUPPORTS" />
        </tx:attributes>
    </tx:advice>
  指定方法名稱:是業務核心方法   read-only:是否是隻讀事務。預設 false,不只讀。   isolation:指定事務的隔離級別。預設值是使用資料庫的預設隔離級別。   propagation:指定事務的傳播行為。   timeout:指定超時時間。預設值為:-1。永不超時。   rollback-for:用於指定一個異常,當執行產生該異常時,事務回滾。產生其他異常,事務不回滾。沒有預設值,任何異常都回滾。   no-rollback-for:用於指定一個異常,當產生該異常時,事務不回滾,產生其他異常時,事務回滾。沒有預設值,任何異常都回滾。   4》配置 AOP 切入點表示式
    <!--配置aop -->
    <aop:config>
    <!-- 配置切入點表示式 -->
     <aop:pointcut expression="execution(* com.xhbjava.service.impl.*.*(..))" id="pt1"/>
    <!-- 在 aop:config 標籤內部:建立事務的通知和切入點表示式的關係 -->
     <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    </aop:config>

5》配置切入點表示式和事務通知的對應關係

<!-- 在 aop:config 標籤內部:建立事務的通知和切入點表示式的關係 --> 

<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>

(3)測試

測試之前資料庫:

呼叫的方法:

執行測試方法:

測試後資料庫:

4.基於註解方式宣告式事務控制

(1)環境搭建

  1》引入依賴包

  略。

  2》配置Spring配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/tx 
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置spring建立容器時要掃描的包 -->
    <context:component-scan base-package="com.xhbjava"></context:component-scan>
    <!-- 資料庫模板配置 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 資料庫配置 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
</beans>

3》建立資料庫表和實體類

  略,參照上面即可。

4》建立業務層和Dao層介面和實現類並使用註解讓 spring 管理

package com.xhbjava.service.impl;

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

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

/**
 * 賬戶業務層介面實現類
 * 
 * @author mr.wang
 *
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public Account findAccountById(Integer id) {
        // TODO Auto-generated method stub
        return accountDao.findAccountById(id);
    }

    @Override
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根據名稱查詢兩個賬戶
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改兩個賬戶的金額
        source.setMoney(source.getMoney() - money);// 轉出賬戶減錢
        target.setMoney(target.getMoney() + money);// 轉入賬戶加錢
        // 3.更新兩個賬戶
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

}
package com.xhbjava.dao.impl;

import java.util.List;

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

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.pojo.AccountRowMapper;

/**
 * 使用者持久層介面實現類
 * 
 * @author mr.wang
 *
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccountById(Integer id) {
        List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public Account findAccountByName(String sourceName) {
        List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(),
                sourceName);
        if (list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件");
        }
        return list.get(0);
    }

    @Override
    public void updateAccount(Account source) {
        // TODO Auto-generated method stub
        jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId());
    }

}

(2)事務配置

1》配置事務管理器並注入資料來源

<!-- 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

2》在業務層使用@Transactional 註解

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    .......
}
該註解的屬性和 xml 中的屬性含義一致。該註解可以出現在介面上,類上和方法上。 出現介面上,表示該介面的所有實現類都有事務支援。 出現在類上,表示類中所有方法有事務支援 出現在方法上,表示方法有事務支援。 以上三個位置的優先順序:方法>類>介面 3》在配置檔案中開啟 spring 對註解事務的支援
<!-- 開啟 spring 對註解事務的支援 -->
     <tx:annotation-driven transaction-manager="transactionManager"/>

不使用XML方式:(之前寫過)

@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//裡面配置資料來源,配置 JdbcTemplate,配置事務管理器。在之前的步驟已經寫過了。
}

4》測試

略。

5.Transactional 配置項

  在程式設計式事務允許自定義事務介面一一TransactionDefinition 它可以由 XML 或者註解@Transactional 進行配置,在上面我基於註解方式用到Transactional 配置項。

  下面是它的介面原始碼:

package org.springframework.transaction.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;

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;

/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

  
    @AliasFor("transactionManager")
    String value() default "";

    
    @AliasFor("value")
    String transactionManager() default "";

   
    Propagation propagation() default Propagation.REQUIRED;

    
    Isolation isolation() default Isolation.DEFAULT;

   
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

  
    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

  
    Class<? extends Throwable>[] noRollbackFor() default {};

  
    String[] noRollbackForClassName() default {};

}

通過程式碼發現他們的配置項不是很多,下面是他的具體配置項說明:

配置項 含義 備註
value 定義事務管理器 它是 Spring JoC 容器裡的 Bean id ,這個 Bean 1/li 要實現 介面 Pl atformTransactionManager
transactionManager 同上 同上
isolation 隔離級別 當一個數據庫在多個事務同時存在時出現,預設值取資料庫預設隔離級別
propagation   傳播行為 傳播行為是方法之間呼叫的問題。預設值為 Propagation .REQUIRED
timeout 超時時間 單位為秒,當超時時,會引發異常,預設會導致事務囚滾
readOnly 是否開啟只讀事務 預設值為 false
rollbackFor 回滾事務的異常類定義 也就是隻有當方法產生所定義異常時,才回滾事務,否則就提交事務
rollbackForClassName 回滾事務的異常類名定義 同rolbackFor ,只是使用類名稱定義
noRollbackFor 當產生哪些異常不回滾事務 當產生所定義異常時, Sprin 將繼續提交事務  
noRollbackForClassName 同noRollbackFor 同noRollbackFor ,只是使用類的名稱定義

6.事務定義器

  從註解@Transactional 或者 XML 中我 看到了事務定義器的身影,因此我們有必要討下事務定義器 TransactionDefinition。介面原始碼如下:

package org.springframework.transaction;
import java.sql.Connection;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {

//傳播行為7個常亮定義 int PROPAGATION_REQUIRED = 0;//預設傳播級別 int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6;   //隔離級別 5個定義 int ISOLATION_DEFAULT = -1;//預設級別 int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
   //-1代表永不超時 int TIMEOUT_DEFAULT = -1;   //獲取傳播行為 int getPropagationBehavior();    //獲取隔離級別 int getIsolationLevel(); //事務超時時間 int getTimeout();   //是否只讀事務 boolean isReadOnly();   //獲取事務定義器的名稱 @Nullable String getName(); }

7.宣告式事務的約定流程

  @Transaction註解可以使用在方法或者類上面,在SpringIoC容器初始化時,Spring會讀入這個註解或者XML配置的事務資訊,並且儲存到一個事務定義類裡面( TransactionDefinition 介面的子類),以備將來使用。當執行時會讓 Spring 攔截註解標註的某 一個方法或者類的所有方法,Spring將編寫的程式碼織入到 AOP 的流程中,然後給出它的約定。

  首先 Spring 通過事務管理器(PlatformTransactionManager 子類)建立事務,與此同時會把事務定義中的隔離級別、超時時間等屬性根據配置內容往事務上設定。而根據傳播行為配置採取一種特定的策略,Spring 根據配置完,無須編碼。啟動業務程式碼,我們知道Spring會通過反射的方式排程開發者的業務程式碼,但是反射的結果可能是正常返回或者產生異常,那麼它給的約定是隻要發生異常,井且符合事務定義類回滾條件的,Spring 就會將資料庫事務回滾,否則將資料庫事務提交,這也是 Spring 自己完成的。在編碼過程中發現,我們只 需要編寫業務程式碼和對事務屬性進行配置即可,無需程式碼干預,程式碼邏輯也更為清晰,更有利於維護。

  宣告式事務的流程如下:

    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=3)
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根據名稱查詢兩個賬戶
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改兩個賬戶的金額
        source.setMoney(source.getMoney() - money);// 轉出賬戶減錢
        target.setMoney(target.getMoney() + money);// 轉入賬戶加錢
        // 3.更新兩個賬戶
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

二、Spring+MyBatis 組合中使用事務

完成專案結構:

1.建立maven工程

2.建立資料庫表

略,沿用之前的表account。

3.環境搭建

(1)pom檔案配置,新增依賴

<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>com.xhbjava</groupId>
    <artifactId>Spring02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.1</version>
        </dependency>
          <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>


        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</project>

(2)配置Spring中bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/tx 
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 啟用掃描機制,並指定掃描對應的包 -->
    <context:annotation-config />
    <context:component-scan base-package="com.xhbjava.**" />
    <!-- 資料庫連線池配置 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!-- 整合MyBatis -->
    <bean id="sqlSessionFactory"
        class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
    </bean>
    <!-- 事務管理器配置資料來源事務 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 使用註解定義事務 -->
    <tx:annotation-driven
        transaction-manager="transactionManager" />
    <!-- 使用自動掃碼物件關係對映 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定會話工廠,如果當前上下文中只定義了一個則該屬性可省去 -->
        <property name="sqlSessionFactoryBeanName"
            value="sqlSessionFactory"></property>
        <!-- 指定要自動掃描介面的基礎包,實現介面 -->
        <property name="basePackage"
            value="com.xhbjava.mapper"></property>
    </bean>
</beans>

(3)建立Account實體類

  沿用之前,略。

(4)搭建 MyBatis 的對映檔案 建立 SQL POJO 的關係。

package com.xhbjava.mapper;

import org.springframework.stereotype.Repository;

import com.xhbjava.pojo.Account;

@Repository
public interface AccountMapper {

    public int insertAccount(Account account);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xhbjava.mapper.AccountMapper">
<insert id="insertAccount" parameterType="com.xhbjava.pojo.Account">
    insert into account(name,money)value(#{name},#{money})
</insert>
</mapper>

(5)為了引入這 對映器,配置MyBatis 配置檔案。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<mappers>
  <mapper resource="com/xhbjava/mapper/AccountMapper.xml" />
</mappers>
</configuration>

(6)新增介面類和實現類

package com.xhbjava.service;

import com.xhbjava.pojo.Account;

/**
 * 賬戶的業務層介面
 * 
 * @author mr.wang
 *
 */
public interface IAccountService {

    public int insertAccount(Account account);

}
package com.xhbjava.service.impl;

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 com.xhbjava.mapper.AccountMapper;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;
@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private AccountMapper accountMapper =null;
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
    public int insertAccount(Account account) {
        return accountMapper.insertAccount(account);
    }

}
package com.xhbjava.service;

import java.util.List;

import com.xhbjava.pojo.Account;

/**
 * 賬戶的業務層介面
 * 
 * @author mr.wang
 *
 */
public interface IAccountListService {

    public int insertListAccount(List<Account> accountList);

}
package com.xhbjava.service.impl;

import java.util.List;

import org.apache.log4j.Logger;
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 com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountListService;
import com.xhbjava.service.IAccountService;
@Service
public class AccountListServiceImpl implements IAccountListService {

    @Autowired
    private IAccountService accountService;
    Logger logger = Logger.getLogger(AccountListServiceImpl.class);
    
    
    @Override
    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
    public int insertListAccount(List<Account> accountList) {
        
        int count = 0;
        for(Account account:accountList) {
            try {
                count +=accountService.insertAccount(account);
            }catch(Exception e) {
                logger.info(e);
            }
            
        }
        return count;
    }

}

(7)log4j配置

### set log levels ###
log4j.rootLogger = DEBUG,stdout

###  \u8F93\u51FA\u5230\u63A7\u5236\u53F0  ###
log4j.logger.org.springframework=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C:%m%n
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%d{yy/MM/dd HH:mm:ss:SSS}]-%l:%m%n

### \u8F93\u51FA\u5230\u65E5\u5FD7\u6587\u4EF6 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=${project}src\\main\\resources\\app.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n

4.測試

package com.xhbjava.test;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountListService;

public class TestSpring {
    public static void main(String args[]) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        IAccountListService accountListService = ctx.getBean(IAccountListService.class);
        List<Account> accountList = new ArrayList<Account>();
        for(int i=0;i<=2;i++) {
            Account account = new Account();
            account.setName("張三"+i);
            account.setMoney(100f+i);
            accountList.add(account);
        }
        int count = accountListService.insertListAccount(accountList);
        System.out.println(count);
    }

}

5.一些注意問題

(1)錯誤使用service

package com.xhbjava.controller;

import org.springframework.beans.factory.annotation.Autowired;

import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

public class AccountController {
    @Autowired
    private IAccountService accountService = null;
    
    public void errorUseServices() {
        Account account = new Account();
        account.setName("李四");
        account.setMoney(12f);
        accountService.insertAccount(account);
        Account account1 = new Account();
        account1.setName("李四1");
        account1.setMoney(12f);
        accountService.insertAccount(account1);
    }

}

  在上面程式碼中,Controller 使用 Service 方法時,如果這個 Service 標註有@Transactional ,那麼它就會啟用一個事務,而 一個Service 方法完成後,它就會釋放該事務,所以前後兩個insertRole 方法是在兩個不同的事務中完成的。這樣容易造成第一個插入成功,第二個插入失敗,造成資料不一致,這是我們需要注意的。