詳解SpringBoot中JdbcTemplate的事務(wù)控制
前言
JdbcTemplate是spring-jdbc提供的數(shù)據(jù)庫核心操作類,那對JdbcTemplate進(jìn)行事務(wù)控制呢?
我的環(huán)境:spring-boot-2.1.3,druid-1.1.3。
原生Jdbc的事務(wù)控制
即,批處理+自動(dòng)提交的控制方式,
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(); // 獲取到原本的自動(dòng)提交狀態(tài) boolean ac = conn.getAutoCommit(); // 批處理多條sql操作 statement.addBatch(sql1); statement.addBatch(sql2); // 關(guān)閉自動(dòng)提交 conn.setAutoCommit(false); try { // 提交批處理 statement.executeBatch(); // 若批處理無異常,則準(zhǔn)備手動(dòng)commit conn.commit(); } catch (Exception e) { e.printStackTrace(); // 批處理拋異常,則rollback try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 恢復(fù)到原本的自動(dòng)提交狀態(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)閉自動(dòng)提交,若批處理失敗則回滾的思路。
@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的因?yàn)橹麈I沖突,所以id=22的也要回滾。
實(shí)際結(jié)果:id=1的插入失敗,id=22的插入成功,未回滾。
原因分析:自始至終都是同一個(gè)connection連接對象,按道理不應(yīng)該無法控制自動(dòng)提交,唯一的解釋是jdbcTemplate.batchUpdate()中真正使用的連接對象并非代碼中的conn,于是一方面把conn打印出來,另一方面準(zhǔn)備調(diào)試jdbcTemplate.batchUpdate()源碼內(nèi)部,看看是否使用了另外獲取到的connection。
調(diào)試流程:jdbcTemplate.batchUpdate()
→execute(new BatchUpdateStatementCallback())
→DataSourceUtils.getConnection(obtainDataSource())
對比兩個(gè)connection,確非同一對象,因此對我們的conn進(jìn)行事務(wù)的控制不會影響jdbcTemplate內(nèi)部真正使用的con,
→緊接著進(jìn)入源碼376行,回調(diào)函數(shù)action.doInStatement(stmt)
在回調(diào)函數(shù)中,真正進(jìn)行數(shù)據(jù)庫操作。至此,便明白了這樣的方法為何不能成功控制jdbcTemplate事務(wù)的原因,即我們控制的conn和jdbcTemplate真正使用的con不是同一個(gè)對象。那如果Druid數(shù)據(jù)庫連接池里只有1個(gè)conn呢,這樣的方法會不會成功控制?
于是修改druid配置,將initial-size、max-active、min-idle都設(shè)置為1,這樣,你jdbcTemplate里獲取到的跟我的conn總該是同一對象了吧?然而,方法運(yùn)行約1min后,拋出異常:
Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60001, active 1, maxActive 1, creating 0
繼續(xù)跟了一下源碼,原來是池子里最大只有一個(gè)連接conn,而它又未被釋放,導(dǎo)致jdbcTemplate內(nèi)部再去從池子里獲取con時(shí),一直在等待已有連接conn的釋放,一直等不到釋放,所以等待了max-wait=60000ms的時(shí)間,最后報(bào)錯(cuò)。
所以這樣的控制也是不合理的,那究竟如何控制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實(shí)例 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,可以實(shí)現(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組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測好用)
- 詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源配置詳細(xì)教程(JdbcTemplate、mybatis)
- SpringBoot使用JdbcTemplate操作數(shù)據(jù)庫
- 詳解spring boot中使用JdbcTemplate
- Spring Boot中的JdbcTemplate是什么及用法小結(jié)
相關(guān)文章
Spring?Security過濾器鏈體系的實(shí)例詳解
這篇文章主要介紹了Spring?Security過濾器鏈體系,通過思維導(dǎo)圖可以很好的幫助大家理解配置類的相關(guān)知識,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02java接口類中的@selectProvider接口的使用及說明
這篇文章主要介紹了java接口類中的@selectProvider接口的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08已解決:No ''Access-Control-Allow-Origin''跨域問題
這篇文章主要介紹了已解決:No 'Access-Control-Allow-Origin' 跨域,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06利用HttpUrlConnection 上傳 接收文件的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄肏ttpUrlConnection 上傳 接收文件的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11mvc架構(gòu)實(shí)現(xiàn)商品的購買(二)
這篇文章主要為大家詳細(xì)介紹了mvc架構(gòu)實(shí)現(xiàn)商品購買功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11