Spring學習筆記(五):JDBCTemplate+事務管理

1 概述

Spring爲開發者提供了JDBCTemplate,能夠簡化不少數據庫操做相關的代碼,本文主要介紹JDBCTemplate的使用以及事務管理功能。java

2 JDBC Template

2.1 配置

配置的話主要配置如下幾項:mysql

  • 數據源:org.springframework.jdbc.datasource.DriverManager.DataSource
  • 數據庫驅動:com.cj.mysql.jdbc.Driver,這裏採用的是MySQL 8,注意MySQL 5.7如下的驅動名字不一樣,另外如果其餘數據庫請對應修改
  • 數據庫URLjdbc:mysql://localhost:3306/testMySQL默認的3306端口,數據庫test
  • 數據庫用戶名
  • 數據庫密碼
  • JDBC模板:org.springframework.jdbc.core.jdbcTemplate

參考配置以下:git

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="test"/> 
    <property name="password" value="test"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>
<context:component-scan base-package="pers.dao"/>

2.2 經常使用方法

  • int update(String sql,Object args[]):增/刪/改操做,使用args設置其中的參數,返回更新的行數
  • List<T> query(String sql,RowMapper<T> rowMapper,Object []args):查詢操做,rowMapper將結果集映射到用戶自定義的類中

2.3 示例

2.3.1 依賴

首先導入依賴:github

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

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

MySQL的版本請根據我的須要更改,或使用其餘數據庫的驅動。spring

2.3.2 配置文件

完整配置文件以下:sql

<?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: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 
       https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="test"/>
        <property name="password" value="test"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <context:component-scan base-package="pers.dao"/>
</beans>

2.3.3 實體類

public class MyUser {
    private Integer id;
    private String uname;
    private String usex;
}

2.3.4 數據訪問層

添加@Repository以及@RequiredArgsConstructor數據庫

@Repository
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestDao {
    private final JdbcTemplate template;

    public int update(String sql,Object[] args)
    {
        return template.update(sql,args);
    }

    public List<MyUser> query(String sql, Object[] args)
    {
        RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class);
        return template.query(sql,mapper,args);
    }
}

由於直接使用@Autowired的話會提示不推薦:express

在這裏插入圖片描述

因此利用了Lombok的註解@RequiredArgsConstructor,效果至關以下構造方法,只不過是簡化了一點:編程

@Autowired
public TestDao(JdbcTemplate template)
{
    this.template = template;
}

2.3.5 測試

測試以前先建表:app

create table MyUser(
    id INT AUTO_INCREMENT PRIMARY KEY ,
    uname varchar(20),
    usex varchar(20)
)

測試類:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TestDao dao = (TestDao)context.getBean("testDao");
        String insertSql = "insert into MyUser(uname,usex) values(?,?)";
        String[] param1 = {"chenhengfa1","男"};
        String[] param2 = {"chenhengfa2","男"};
        String[] param3 = {"chenhengfa3","男"};
        String[] param4 = {"chenhengfa4","男"};

        dao.update(insertSql,param1);
        dao.update(insertSql,param2);
        dao.update(insertSql,param3);
        dao.update(insertSql,param4);

        String selectSql = "select * from MyUser";
        List<MyUser> list = dao.query(selectSql,null);
        for(MyUser mu:list)
        {
            System.out.println(mu);
        }
    }
}

輸出:

在這裏插入圖片描述

若是出現異常或插入不成功等其餘狀況,請檢查SQL語句是否編寫正確,包括表名以及字段名。

3 事務管理

Spring中的事務管理有兩種方法:

  • 編程式事務管理:代碼中顯式調用beginTransactioncommitrollback等就是編程式事務管理
  • 聲明式事務管理:經過AOP實現,不須要經過編程方式管理事務,所以不須要再業務邏輯代碼中摻瑣事務處理的代碼,開發更加簡單,便於後期維護

下面先來看一下編程式事務管理的實現。

3.1 編程式事務管理

編程式事務管理的配置又有兩種方法:

  • 基於底層API
  • 基於TransactionTemplate

須要的依賴以下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

3.1.1 底層API實現

根據PlatformTransactionManagerTransactionDefinitionTransactionStatus幾個核心接口,經過編程方式進行事務管理,首先配置事務管理器:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

接着修改數據庫訪問類:

@Repository
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestDao {
    private final JdbcTemplate template;
    private final DataSourceTransactionManager manager;

    public int update(String sql,Object[] args)
    {
        return template.update(sql,args);
    }

    public List<MyUser> query(String sql,Object[] args)
    {
        RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class);
        return template.query(sql,mapper,args);
    }

    public void testTransaction()
    {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = manager.getTransaction(definition);
        String message = "執行成功,沒有事務回滾";

        try
        {
            String sql1 = "delete from MyUser";
            String sql2 = "insert into MyUser(id,uname,usex) values(?,?,?)";
            Object [] param2 = {1,"張三","男"};
            template.update(sql1);
            template.update(sql2,param2);
            template.update(sql2,param2);
            manager.commit(status);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            manager.rollback(status);
            message = "主鍵重複,事務回滾";
        }
        System.out.println(message);
    }
}

3.1.1.1 事務定義

TransactionDefinition是事務定義,是一個接口:

在這裏插入圖片描述

主要定義了:

  • 事務隔離級別
  • 事務傳播行爲
  • 事務超時時間
  • 是否爲只讀事務

DefaultTransactionDefinition就是上面屬性的一些默認配置,好比:

在這裏插入圖片描述

也就是定義了:

  • 傳播行爲爲0:也就是常量PROPAGATION_REQUIREDE,表示若是當前存在一個事務,則加入當前事務,若是不存在任何事務,就建立一個新事務
  • 隔離級別爲-1:這個也是TransactionDefinition的默認參數,表示使用數據庫的默認隔離級別,一般狀況下爲Read Committed
  • 超時爲-1:默認設置不超時,如須要設置超時請調用setTimeout方法,好比若是設置爲了60,那麼至關於若是操做時間超過了60s,並且後面還涉及到CRUD操做,那麼會拋出超時異常並回滾,若是超時操做的後面沒有涉及到CRUD操做,那麼不會回滾
  • 只讀事務爲false:默認爲false,可是該變量不是代表「不能」進行修改等操做,而是一種暗示,若是不包含修改操做,那麼JDBC驅動和數據庫就有可能針對該事務進行一些特定的優化

3.1.1.2 具體執行流程

具體執行流程以下:

  • 定義事務:實例類爲DefaultTransactionDefinition
  • 開啓事務:經過getTransaction(TransactionDefinition)開啓
  • 執行業務方法
  • 根據業務方法是否出現異常手動調用DataSourceTransactioncommit(TransactionStatus)進行提交
  • 出現異常調用rollback(TransactionStatus)進行回滾

測試以下:

在這裏插入圖片描述

3.1.2 基於TransactionTemplate

步驟:

  • 經過調用TransactionTemplateexecute實現
  • execute接受一個TransactionCallback接口參數
  • TransactionCallback定義了一個doInTransaction方法
  • 一般以匿名內部類的方式實現TransactionCallback接口,在其中的doInTransaction編寫業務邏輯代碼
  • doInTransaction有一個TransactionStatus的參數,能夠調用setRollbackOnly進行回滾

默認的回滾規則以下:

  • 若是拋出未檢查異常或者手動調用setRollbackOnly,則回滾
  • 若是執行完成或拋出檢查異常,則提交事務

示例以下,首先編寫配置文件對Bean進行注入:

<!--事務管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
</bean>
<!--事務模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="txManager"/>
</bean>

其次修改數據訪問類,添加一個測試方法:

public void testTransactionTemplate()
{
    System.out.println(transactionTemplate.execute((TransactionCallback<Object>) transactionStatus -> {
        String deleteSql = "delete from MyUser";
        String insertSql = "insert into MyUser(id,uname,usex) values(?,?,?)";
        Object[] parm = {1, "張三", "男"};
        try {
            template.update(deleteSql);
            template.update(insertSql, parm);
            template.update(insertSql, parm);
        } catch (Exception e) {
            message = "主鍵重複,事務回滾";
            e.printStackTrace();
        }
        return message;
    }));
}

大部分代碼與第一個例子相似就不解釋了,結果也是由於主鍵重複出現異常,形成事務回滾:

在這裏插入圖片描述

3.2 聲明式事務管理

Spring聲明式事務管理經過AOP實現,本質是在方法先後進行攔截,在目標方法開始以前建立或加入一個事務,執行目標方法完成以後根據執行狀況提交或回滾事務。相比起編程式事務管理,聲明式最大的優勢就是不須要經過編程的方式管理事務,業務邏輯代碼無需混瑣事務代碼,可是惟一不足的地方就是最細粒度只能做用到方法上,而不能作到代碼塊級別。

實現方式有以下兩種:

  • 基於XML實現
  • 基於@Transactional實現

3.2.1 基於XML

Spring提供了tx命令空間來配置事務:

  • <tx:advice>:配置事務通知,通常須要指定id以及transaction-manager
  • <tx:attributes>:配置多個<tx:method>指定執行事務的細節

3.2.1.1 配置文件

完整配置文件以下:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:cache="http://www.springframework.org/schema/cache"
       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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.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"
>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="test"/>
        <property name="password" value="test"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <context:component-scan base-package="pers.dao"/>
    <!--事務管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"/>
    </bean>

	<!--聲明式事務-->
    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
        	<!--任意方法-->
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
    <!--aop配置,具體能夠看筆者以前的文章-->
    <aop:config>
    	<!--定義切點,執行testXMLTranscation()時進行加強-->
        <aop:pointcut id="txPointCut" expression="execution(* pers.dao.TestDao.testXMLTransaction())"/>
        <!--切面-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

3.2.1.2 測試

測試方法以下:

public void testXMLTransaction()
{
    String deleteSql = "delete from MyUser";
    String saveSql = "insert into MyUser(id,uname,usex) values(?,?,?)";
    Object [] parm = {1,"張三","男"};
    template.update(deleteSql);
    template.update(saveSql,parm);
    template.update(saveSql,parm);
}

運行結果:

在這裏插入圖片描述

能夠看到提示主鍵重複了。

3.2.2 基於@Transactional

@Transactional通常做用於類上,使得該類全部public方法都具備該類型的事務屬性。下面建立一個示例。

3.2.2.1 配置文件

將上一個例子中的<aop:config>以及<tx:advice>註釋掉,同時添加:

<!--事務管理的註解驅動器-->
<tx:annotation-driven transaction-manager="txManager"/>

在這裏插入圖片描述

3.2.2.2 測試

測試方法與上一個例子一致,結果也是如此:

在這裏插入圖片描述

4 參考源碼

Java版:

Kotlin版:

相關文章
相關標籤/搜索