1 概述
Spring
為開發者提供了JDBCTemplate
,可以簡化很多數據庫操作相關的代碼,本文主要介紹JDBCTemplate
的使用以及事務管理功能。
2 JDBC Template
2.1 配置
配置的話主要配置以下幾項:
- 數據源:
org.springframework.jdbc.datasource.DriverManager.DataSource
- 數據庫驅動:
com.cj.mysql.jdbc.Driver
,這里采用的是MySQL 8
,注意MySQL 5.7
以下的驅動名字不同,另外若是其他數據庫請對應修改 - 數據庫
URL
:jdbc:mysql://localhost:3306/test
,MySQL
默認的3306
端口,數據庫test
- 數據庫用戶名
- 數據庫密碼
JDBC
模板:org.springframework.jdbc.core.jdbcTemplate
參考配置如下:
<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 依賴
首先導入依賴:
<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
的版本請根據個人需要更改,或使用其他數據庫的驅動。
2.3.2 配置文件
完整配置文件如下:
<?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
的話會提示不推薦:
所以利用了Lombok
的注解@RequiredArgsConstructor
,效果相當如下構造方法,只不過是簡化了一點:
@Autowired
public TestDao(JdbcTemplate template)
{
this.template = template;
}
2.3.5 測試
測試之前先建表:
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
中的事務管理有兩種方法:
- 編程式事務管理:代碼中顯式調用
beginTransaction
、commit
、rollback
等就是編程式事務管理 - 聲明式事務管理:通過
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
實現
根據PlatformTransactionManager
、TransactionDefinition
、TransactionStatus
幾個核心接口,通過編程方式進行事務管理,首先配置事務管理器:
<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)
開啟 - 執行業務方法
- 根據業務方法是否出現異常手動調用
DataSourceTransaction
的commit(TransactionStatus)
進行提交 - 出現異常調用
rollback(TransactionStatus)
進行回滾
測試如下:
3.1.2 基於TransactionTemplate
步驟:
- 通過調用
TransactionTemplate
的execute
實現 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
版: