詳解SpringBoot中JdbcTemplate的事務(wù)控制
前言
JdbcTemplate是spring-jdbc提供的數(shù)據(jù)庫核心操作類,那對JdbcTemplate進行事務(wù)控制呢?
我的環(huán)境:spring-boot-2.1.3,druid-1.1.3。
原生Jdbc的事務(wù)控制
即,批處理+自動提交的控制方式,
public static void demo(String[] args) throws SQLException, ClassNotFoundException {
String url = "jdbc:mysql://10.1.4.16:3306/szhtest";
String username = "ababab";
String password = "123456";
String sql1 = "insert xx";
String sql2 = "insert xx";
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, username, password);
Statement statement = conn.createStatement();
// 獲取到原本的自動提交狀態(tài)
boolean ac = conn.getAutoCommit();
// 批處理多條sql操作
statement.addBatch(sql1);
statement.addBatch(sql2);
// 關(guān)閉自動提交
conn.setAutoCommit(false);
try {
// 提交批處理
statement.executeBatch();
// 若批處理無異常,則準(zhǔn)備手動commit
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 批處理拋異常,則rollback
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 恢復(fù)到原本的自動提交狀態(tài)
conn.setAutoCommit(ac);
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Spring的聲明式事務(wù)控制
Bean的類或方法上加@Transactional,事務(wù)控制粒度較大,只能控制在方法級別,不能控制到代碼粒度級別。
嘗試JdbcTemplate的事務(wù)控制
采取跟原生jdbc事務(wù)控制一樣的方法試試,在批處理前關(guān)閉自動提交,若批處理失敗則回滾的思路。
@RequestMapping("/druidData1")
public String druidData1() throws SQLException {
String sql1 = "INSERT INTO user_tmp(`id`, `username`) VALUES(22, 222)";
// id=1的主鍵沖突插入失敗
String sql2 = "INSERT INTO user_tmp(`id`, `username`) VALUES(1, 111)";
Connection conn = jdbcTemplate.getDataSource().getConnection();
LOG.info("1:{}", conn);
boolean ac = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
int[] rs2 = jdbcTemplate.batchUpdate(new String[]{sql1, sql2});
conn.commit();
} catch (Throwable e) {
LOG.error("Error occured, cause by: {}", e.getMessage());
conn.rollback();
} finally {
conn.setAutoCommit(ac);
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
LOG.error("Error occurred while closing connectin, cause by: {}", e.getMessage());
}
}
}
return "test";
}
期望結(jié)果:id=1的因為主鍵沖突,所以id=22的也要回滾。
實際結(jié)果:id=1的插入失敗,id=22的插入成功,未回滾。
原因分析:自始至終都是同一個connection連接對象,按道理不應(yīng)該無法控制自動提交,唯一的解釋是jdbcTemplate.batchUpdate()中真正使用的連接對象并非代碼中的conn,于是一方面把conn打印出來,另一方面準(zhǔn)備調(diào)試jdbcTemplate.batchUpdate()源碼內(nèi)部,看看是否使用了另外獲取到的connection。
調(diào)試流程:jdbcTemplate.batchUpdate()

→execute(new BatchUpdateStatementCallback())

→DataSourceUtils.getConnection(obtainDataSource())

對比兩個connection,確非同一對象,因此對我們的conn進行事務(wù)的控制不會影響jdbcTemplate內(nèi)部真正使用的con,

→緊接著進入源碼376行,回調(diào)函數(shù)action.doInStatement(stmt)

在回調(diào)函數(shù)中,真正進行數(shù)據(jù)庫操作。至此,便明白了這樣的方法為何不能成功控制jdbcTemplate事務(wù)的原因,即我們控制的conn和jdbcTemplate真正使用的con不是同一個對象。那如果Druid數(shù)據(jù)庫連接池里只有1個conn呢,這樣的方法會不會成功控制?
于是修改druid配置,將initial-size、max-active、min-idle都設(shè)置為1,這樣,你jdbcTemplate里獲取到的跟我的conn總該是同一對象了吧?然而,方法運行約1min后,拋出異常:
Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60001, active 1, maxActive 1, creating 0
繼續(xù)跟了一下源碼,原來是池子里最大只有一個連接conn,而它又未被釋放,導(dǎo)致jdbcTemplate內(nèi)部再去從池子里獲取con時,一直在等待已有連接conn的釋放,一直等不到釋放,所以等待了max-wait=60000ms的時間,最后報錯。

所以這樣的控制也是不合理的,那究竟如何控制JdbcTemplate的事務(wù)呢?答案就是TransactionTemplate。
TransactionTemplate的編程式事務(wù)控制
注冊事務(wù)相關(guān)bean:TransactionTemplate,如下:
package com.boot.druid.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
/**
* Druid數(shù)據(jù)庫連接池配置文件
*/
@Configuration
public class DruidConfig {
private static final Logger logger = LoggerFactory.getLogger(DruidConfig.class);
@Value("${spring.datasource.druid.url}")
private String dbUrl;
@Value("${spring.datasource.druid.username}")
private String username;
@Value("${spring.datasource.druid.password}")
private String password;
@Value("${spring.datasource.druid.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.druid.max-wait}")
private int maxWait;
/**
* Druid 連接池配置
*/
@Bean //聲明其為Bean實例
public DruidDataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
try {
datasource.setFilters(filters);
} catch (Exception e) {
logger.error("druid configuration initialization filter", e);
}
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
/**
* JDBC操作配置
*/
@Bean(name = "dataOneTemplate")
public JdbcTemplate jdbcTemplate (@Autowired DruidDataSource dataSource){
return new JdbcTemplate(dataSource) ;
}
/**
* 裝配事務(wù)管理器
*/
@Bean(name="transactionManager")
public DataSourceTransactionManager transactionManager(@Autowired DruidDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* JDBC事務(wù)操作配置
*/
@Bean(name = "txTemplate")
public TransactionTemplate transactionTemplate (@Autowired DataSourceTransactionManager transactionManager){
return new TransactionTemplate(transactionManager);
}
/**
* 配置 Druid 監(jiān)控界面
*/
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean srb =
new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//設(shè)置控制臺管理用戶
srb.addInitParameter("loginUsername","root");
srb.addInitParameter("loginPassword","root");
//是否可以重置數(shù)據(jù)
srb.addInitParameter("resetEnable","false");
return srb;
}
@Bean
public FilterRegistrationBean statFilter(){
//創(chuàng)建過濾器
FilterRegistrationBean frb =
new FilterRegistrationBean(new WebStatFilter());
//設(shè)置過濾器過濾路徑
frb.addUrlPatterns("/*");
//忽略過濾的形式
frb.addInitParameter("exclusions",
"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return frb;
}
}
然后注入TransactionTemplate,使用transactionTemplate.execute(new TransactionCallback<> action)或者transactionTemplate.execute(new TransactionCallbackWithoutResult<> action)執(zhí)行多條sql,最后可以通過transactionStatus的setRollbackOnly()或rollbackToSavepoint(savepoint) 控制事務(wù),如下:
@RequestMapping("/druidData2")
public String runTransactionSamples() {
String sql1 = "INSERT INTO user_tmp(`id`, `username`) VALUES(22, 222)";
String sql2 = "INSERT INTO user_tmp(`id`, `username`) VALUES(1, 111)";
txTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
Object savepoint = transactionStatus.createSavepoint();
// DML執(zhí)行
try {
int[] rs2 = jdbcTemplate.batchUpdate(new String[]{sql1, sql2});
} catch (Throwable e) {
LOG.error("Error occured, cause by: {}", e.getMessage());
transactionStatus.setRollbackOnly();
// transactionStatus.rollbackToSavepoint(savepoint);
}
return null;
}
});
return "test2";
}
上面是不帶參數(shù)的多條sql的事務(wù)執(zhí)行,若是帶參數(shù)的多條sql,可以實現(xiàn)如下:
@RequestMapping("/druidData3")
public String runTransactionSamples2() {
String sql1 = "INSERT INTO user_tmp(`id`, `username`) VALUES(?, ?)";
Object[] args1 = new Object[] {22, 222};
String sql2 = "INSERT INTO user_tmp(`id`, `username`) VALUES(?, ?)";
Object[] args2 = new Object[] {1, 111};
txTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
Object savepoint = transactionStatus.createSavepoint();
// DML執(zhí)行
try {
int rs1 = jdbcTemplate.update(sql1, args1);
int rs2 = jdbcTemplate.update(sql2, args2);
} catch (Throwable e) {
LOG.error("Error occured, cause by: {}", e.getMessage());
transactionStatus.setRollbackOnly();
// transactionStatus.rollbackToSavepoint(savepoint);
}
return null;
}
});
return "test2";
}
到此這篇關(guān)于SpringBoot中JdbcTemplate的事務(wù)控制的文章就介紹到這了,更多相關(guān)SpringBoot JdbcTemplate事務(wù)控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring?Boot整合持久層之JdbcTemplate多數(shù)據(jù)源
- Spring Boot 整合持久層之JdbcTemplate
- SpringBoot2使用JTA組件實現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測好用)
- 詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis)
- SpringBoot使用JdbcTemplate操作數(shù)據(jù)庫
- 詳解spring boot中使用JdbcTemplate
- Spring Boot中的JdbcTemplate是什么及用法小結(jié)
相關(guān)文章
java接口類中的@selectProvider接口的使用及說明
這篇文章主要介紹了java接口類中的@selectProvider接口的使用及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
已解決:No ''Access-Control-Allow-Origin''跨域問題
這篇文章主要介紹了已解決:No 'Access-Control-Allow-Origin' 跨域,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
利用HttpUrlConnection 上傳 接收文件的實現(xiàn)方法
下面小編就為大家?guī)硪黄肏ttpUrlConnection 上傳 接收文件的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11

