Spring事務管理

Spring的事務管理

Spring的事務管理簡化了傳統的事務管理流程,提升了開發效率。可是首先先要了解Spring的數據庫編程。java

Spring的數據庫編程

數據庫編程是互聯網編程的基礎,Spring框架爲開發者提供了JDBC模板模式,即jdbcTemplate,它能夠簡化許多代碼,但在實際應用中jdbcTemplate使用並不常見,在大多數時候都採用Spring結合MyBatis進行開發。在這裏,只講述Spring的jdbcTemplate開發。mysql

SpringJDBC的配置

本節Spring數據庫編程主要使用的是SpringJDBC模塊的core和DataSource包,core是JDBC的核心包,包括經常使用的JdbcTemplate類,DataSource是訪問數據源的工具類包。若是要使用SpringJDBC操做數據庫,須要進行配置,配置以下:spring

<!--配置數據源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!--Mysql驅動-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <!--鏈接的url-->
    <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
    <!--用戶名密碼的配置-->
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
<!--配置JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

在上述示例中,配置JDBC模板須要將dataSource注入到jdbcTemplate,而在數據訪問層(Dao)中須要使用jdbcTemplate時也須要將jdbcTemplate注入到對應的bean中。sql

@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired //按照類型注入
    private JdbcTemplate jdbcTemplate;

JDBCTemplate的經常使用方法

public int update(String sql,Object args[]):該方法能夠對數據表進行增長、修改、刪除。使用args[]設置參數,函數返回的是更新的行數。示例以下:數據庫

String insertSQL="insert into user values(NULL,?,?)";
Onject param[] = {"chencheng","m"};
jdbcTemplate.update(insertSQL,param);

public List<T> query(String sql,RowMapper<T> rowmapper,Object args[]):該方法能夠對數據表進行查詢操做,rowMapper將結果集映射到用戶自定義的類中(前提是類的屬性名與字段名相同)。示例以下:express

jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);

具體的實現步驟

  1. 建立並編輯配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--指定須要掃描的包-->
    <context:component-scan base-package="com.ch5"/>
    <!--配置數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--Mysql驅動-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--鏈接的url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
        <!--用戶名密碼的配置-->
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 建立映射數據庫的實體類
package com.ch5;

public class User {
    private Integer id;
    private String name;
    private double money;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public double getMoney() {
        return money;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  1. 建立數據庫訪問層TestDao和TestDaoImpl
package com.ch5.dao;
import com.ch5.User;
import java.util.List;
public interface TestDao {
    public int update(String sql,Object[] param);
    public List<User> query(String sql,Object[] param);
}
package com.ch5.dao.Impl;
import com.ch5.User;
import com.ch5.dao.TestDao;
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;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired //按照類型注入
    private JdbcTemplate jdbcTemplate;
    @Override
    public int update(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }
    @Override
    public List<User> query(String sql, Object[] param) {
        return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);
    }
}
  1. 編寫測試類JdbcTemplateTest
package com.ch5.Test;

import com.ch5.User;
import com.ch5.dao.TestDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class JdbcTemplateTest {
    public static void main(String[] args) {
        ApplicationContext appCo = new ClassPathXmlApplicationContext("appliationContext.xml");
        TestDao testDao=(TestDao)appCo.getBean("testDao");
        String insertSql="insert into account values(null,?,?)";
        Object param[] = {"chencheng",1050.0};
        testDao.update(insertSql,param);
        String selectSql="select * from account";
        List<User> list=testDao.query(selectSql,null);
        for (User user : list) {
            System.out.println(user);
        }
    }
}

編程式事務管理

在代碼中顯式的調用beginTransactioncommitrollback等與事務處理相關的方法,這就是編程式事務管理,當只有少數事務操做時,編程式事務管理才比較適合。編程

基於XML的AOP實現事務控制

  1. 編寫事務管理的類
package com.itheima.utils;
/**
 * 和事務管理相關的工具類,它包含了,開啓事務,提交事務,回滾事務和釋放鏈接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /**
     * 開啓事務
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 提交事務
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 回滾事務
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 釋放鏈接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//還回鏈接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  1. 配置aop
<!-- 配置事務管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
    <!-- 注入ConnectionUtils -->
   <property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..)"/>
        <aop:aspect id="txAdvice" ref="txManager">
        <!--配置前置事務,開啓事務-->
            <aop:before method="beginTransaction" pointcut-ref="pt1"/>
        <!--配置後置事務,提交事務-->
            <aop:after-returning method="commit" pointcut-ref="pt1"/>
        <!--配置異常事務,回滾事務-->
            <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
        <!--配置最終事務,釋放鏈接-->
            <aop:after method="release" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>

基於底層API的編程式事務管理

基於底層API的編程式事務管理就是根據PlatformTransactionManagerTransactionDefinitionTeansactionStatus等幾個核心接口,經過編程的方式進行事務管理,下面經過一個實例描述底層API的事務管理實現:api

  1. 給數據源配置事務管理器
<!--配置事務管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 建立數據訪問類
package com.ch5.dao.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Repository("codeTransaction")
public class CodeTransaction {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    public String testTransaction(){
        //默認事務定義
        TransactionDefinition definition=new DefaultTransactionDefinition();
        //開啓事務
        TransactionStatus transactionStatus = transactionManager.getTransaction(definition);
        String message="執行成功,沒有回滾";
        try{
            String sql = "delete * from account";
            String insertSql = "insert into account values(?,?,?)";
            Object param[] = {"1","chenheng",2000};
            jdbcTemplate.update(sql);
            //id重複,所以發生錯誤。
            jdbcTemplate.update(insertSql,param);
            jdbcTemplate.update(insertSql,param);
            //提交事務
            transactionManager.commit(transactionStatus);
        }catch (Exception e){
            //出現異常,回滾
            transactionManager.rollback(transactionStatus);
            message="事務回滾";
            e.printStackTrace();
        }
        return message;
    }
}
  1. 定義測試類
package com.ch5.Test;

import com.ch5.dao.Impl.CodeTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionMangagerTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml");
        CodeTransaction codeTransaction = (CodeTransaction)appCo.getBean("codeTransaction");
        String result = codeTransaction.testTransaction();
        System.out.println(result);
    }
}

基於TransactionTemplate的編程式事務管理

事務處理的代碼散落在業務邏輯代碼中,破壞了原有代碼的條理性,而且每個事務都會有相似的啓動事務,提交以及回滾事務的代碼。app

TransactionTemplateexcute方法有一個TransactionCallback接口類型的參數,該接口定義了一個DoInTransaction的方法,一般以匿名內部類的方式實現TransactionCallback接口,並在其doInTransaction方法中寫業務邏輯代碼。在這裏可使用默認的事務提交和回滾規則,在業務代碼中不須要顯式調用任何事務處理的API,doInTransaction方法有一個TransactionStatus類型的參數,能夠在方法的任何位置調用該參數的setRollbackOnly方法將事務標識爲回滾,以執行事務回滾。框架

根據默認規則,若是在執行回調方法的過程當中拋出未檢查異常,或者顯式調用了setRollbackOnly方法,則回滾事務;若是事務執行完成或者拋出了checked類型的異常,則提交事務。

基於TransactionTemplate的編程式事務管理的步驟以下:

  1. 爲事務管理添加事務模板:在基於底層的API開發的applicationContext.xml配置文件上使用springframwork提供的org,springframework,transaction.support.TransactionTemplate類爲事務管理器添加事務模板。完整的配置文件以下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--指定須要掃描的包-->
    <context:component-scan base-package="com.ch5"/>
    <!--配置數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--Mysql驅動-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--鏈接的url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
        <!--用戶名密碼的配置-->
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--配置事務管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--爲事務管理器txManager建立transactionTemplate-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"/>
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 建立數據訪問類TransactionTemplateDao
package com.ch5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("transactionTemplateDao")
public class TransactionTemplateDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private TransactionTemplate transactionTemplate;
    String message = "";
    public String TransactionTemplateTest(){
        //以你命好內部類的方式實現TransactionCallback接口。使用默認的事務規則。
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                String insertSql = "insert into account values(?,?,?)";
                Object param[] = {9,"chen",5000.0};
                try{
                    jdbcTemplate.update(insertSql,param);
                    jdbcTemplate.update(insertSql,param);
                    message="執行成功,未回滾";
                }catch (Exception e){
                    message="事務回滾";
                }
                return message;
            }
        });
        return message;
    }
}
  1. 建立測試類TransactionTemplateDaoTest
package com.ch5.Test;

import com.ch5.TransactionTemplateDao;
import com.ch5.dao.Impl.CodeTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionTemplateDaoTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml");
        TransactionTemplateDao transactionTemplateDao = appCo.getBean("transactionTemplateDao", TransactionTemplateDao.class);
        String result = transactionTemplateDao.TransactionTemplateTest();
        System.out.println(result);
    }
}

聲明式事務管理

Spring的聲明式事務管理是經過AOP技術實現的事務管理,其本質是對方法先後攔截,而後再目標方法開始以前建立一個事務,在執行完成後提交或回滾事務。

與編程式事務管理相比較,聲明式事務惟一不足的地方是最細粒度只能做用到方法級別,沒法作到像編程式事務管理那樣能夠做用到代碼塊級別,但即使有這樣的須要,能夠經過變通方法進行解決。例如能夠將要進行事務處理的代碼塊單獨封裝爲方法。

Spring聲明式事務管理能夠經過兩種方式實現,一是基於XML方式,二是基於@Transactional註解的方式

基於XML方式的聲明式事務管理

基於XML方式的聲明式事務管理是經過在配置文件中配置事務規則的相關聲明來實現的。Spring提供了tx命名空間來配置事務管理,提供了<tx:advice>元素來配置事務的通知,在配置<tx:advice>時通常要指定id和transaction-manager屬性,其中id是配置文件的惟一標識。transaction-manager指定了事務管理器。另外還須要配置<tx:attributes>子元素,該子元素可配置多個<tx:method>子元素決定執行事務的細節。

<tx:advice>元素配置了事務的加強處理後就能夠經過編寫AOP配置讓Spring自動對目標對象生成代理,下面經過實例演示XML方式讓Spring實現聲明式事務管理。爲了體現事務管理的流程,建立Dao、Service、Controller3層實現。

  1. 建立Dao接口和實現類
package statment.dao;

public interface TestDao {
    public int save(String sql,Object param[]);
    public int delete(String sql,Object param[]);
}
package statment.dao.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import statment.dao.TestDao;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public int save(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }

    public int delete(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }
}
  1. 建立Service接口和實現類
package statment.Service;

public interface TestService {
    public void test();
}
package statment.Service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import statment.Service.TestService;
import statment.dao.TestDao;
@Service("testService")
public class TestServiceImpl implements TestService {
    @Autowired
    private TestDao testDao;

    public void test() {
        String deleteSql="delete from account";
        String saveSql="insert into account values(?,?,?)";
        Object param[] = {1,"shitji",5000};
        testDao.delete(deleteSql,null);
        testDao.save(saveSql,param);
    }
}
  1. 建立Controller類
package statment.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import statment.Service.TestService;
@Controller
public class StatementController {
    @Autowired
    private TestService testService;
    public void test(){
        testService.test();
    }
}
  1. 編寫配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       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="statment"/>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="txPonintCut" expression="execution(* statment.Service.*.*(..))"/>
        <aop:advisor advice-ref="myAdvice" pointcut-ref="txPonintCut"/>
    </aop:config>
</beans>
  1. 編寫測試類
package statment.Test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import statment.controller.StatementController;
public class XMLTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("bean.xml");
        StatementController controller = appCo.getBean("statementController", StatementController.class);
        controller.test();
    }
}

基於註解的聲明式事務管理

package statment.Service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import statment.Service.TestService;
import statment.dao.TestDao;
@Service("testService")
@Transactional
public class TestServiceImpl implements TestService {
    @Autowired
    private TestDao testDao;

    public void test() {
        String deleteSql="delete from account";
        String saveSql="insert into account values(?,?,?)";
        Object param[] = {1,"shitji",5000};
        testDao.delete(deleteSql,null);
        testDao.save(saveSql,param);
    }
}

加入@Transactional,就能夠指定這個類須要受到Spring的事務管理,注意該註解只針對public修飾的方法添加。

在事務處理中捕獲異常

聲明式事務處理的流程是:

  1. Spring根據配置完成事務定義,設置事務屬性。
  2. 執行開發者的代碼邏輯。
  3. 若是開發者的代碼產生異常而且知足事務回滾的配置條件,則事務回滾,不然提交事務。
  4. 事務資源釋放。

若是開發者在代碼邏輯中加入try...catch語句,Spring不能在聲明式事務處理中正常執行事務的回滾。緣由是Spring只在發生未被捕獲的RuntimeException時纔會回滾事務。所以須要處理這種問題。

基於XML方式的聲明式事務管理中捕獲異常

在基於XML方式的聲明式事務管理捕獲異常,須要補充兩個步驟:

  1. 修改聲明事務的配置
<tx:method name="*" rollback-for="java.lang.Exception"/>
  1. 在catch語句中添加throw new RuntimeException();

基於註解方式的聲明式事務管理中捕獲異常

  1. 修改註解內容,添加rollbackFor屬性
@Transactional(rollbackFor = {Exception.class})
  1. 在catch語句中添加throw new RuntimeException();
相關文章
相關標籤/搜索