详解SpringBoot中JdbcTemplate的事务控制

网友投稿 1649 2022-12-08

详解SpringBoot中JdbcTemplate的事务控制

详解SpringBoot中JdbcTemplate的事务控制

目录前言原生Jdbc的事务控制Spring的声明式事务控制尝试JdbcTemplate的事务控制TransactionTemplate的编程式事务控制

前言

JdbcTemplate是spring-jdbc提供的数据库核心操作类,那对JdbcTemplate进行事务控制呢?

我的环境:sprpwgXZmguing-boot-2.1.3,druid-1.1.3。

原生Jdbc的事务控制

即,批处理+自动提交的控制方式,

public static void demo(String[] args) throwshttp:// 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();

// 获取到原本的自动提交状态

boolean ac = conn.getAutoCommit();

// 批处理多条sql操作

statement.addBatch(sql1);

statement.addBatch(sql2);

// 关闭自动提交

conn.setAutoCommit(false);

try {

// 提交批处理

statement.executeBatch();

// 若批处理无异常,则准备手动commit

conn.commit();

} catch (Exception e) {

e.printStackTrace();

// 批处理抛异常,则rollback

try {

conn.rollback();

} catch (SQLException ex) {

ex.printStackTrace();

}

} finally {

// 恢复到原本的自动提交状态

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的声明式事务控制

Bean的类或方法上加@Transactional,事务控制粒度较大,只能控制在方法级别,不能控制到代码粒度级别。

尝试JdbcTemplate的事务控制

采取跟原生jdbc事务控制一样的方法试试,在批处理前关闭自动提交,若批处理失败则回滚的思路。

@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";

}

期望结果:id=1的因为主键冲突,所以id=22的也要回滚。

实际结果:id=1的插入失败,id=22的插入成功,未回滚。

原因分析:自始至终都是同一个connection连接对象,按道理不应该无法控制自动提交,唯一的解释是jdbcTemplate.batchUpdate()中真正使用的连接对象并非代码中的conn,于是一方面把conn打印出来,另一方面准备调试jdbcTemplate.batchUpdate()源码内部,看看是否使用了另外获取到的connection。

调试流程:jdbcTemplate.batchUpdate()

→execute(new BatchUpdateStatementCallback())

→DataSourceUtils.getConnection(obtainDataSource())

对比两个connection,确非同一对象,因此对我们的conn进行事务的控制不会影响jdbcTemplate内部真正使用的con,

→紧接着进入源码376行,回调函数action.doInStatement(stmt)

在回调函数中,真正进行数据库操作。至此,便明白了这样的方法为何不能成功控制jdbcTemplate事务的原因,即我们控制的conn和jdbcTemplate真正使用的con不是同一个对象。那如果Druid数据库连接池里只有1个conn呢,这样的方法会不会成功控制?

于是修改druid配置,将initial-size、max-active、min-idle都设置为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

继续跟了一下源码,原来是池子里最大只有一个连接conn,而它又未被释放,导致jdbcTemplate内部再去从池子里获取con时,一直在等待已有连接conn的释放,一直等不到释放,所以等待了max-wait=60000ms的时间,最后报错。

所以这样的控制也是不合理的,那究竟如何控制JdbcTemplate的事务呢?答案就是TransactionTemplate。

TransactionTemplate的编程式事务控制

注册事务相关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数据库连接池配置文件

*/

@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) ;

}

/**

* 装配事务管理

*/

@Bean(name="transactionManager")

public DataSourceTransactionManager transactionManager(@Autowired DruidDataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

/**

* JDBC事务操作配置

*/

@Bean(name = "txTemplate")

public TransactionTemplate transactionTemplate (@Autowired DataSourceTransactionManager transactionManager){

return new TransactionTemplate(transactionManager);

}

/**

* 配置 Druid 监控界面

*/

@Bean

public ServletRegistrationBean statViewServlet(){

ServletRegistrationBean srb =

new ServletRegistrationBean(new StatViewServlet(),"/druid/*");

//设置控制台管理用户

srb.addInitParameter("loginUsername","root");

srb.addInitParameter("loginPassword","root");

//是否可以重置数据

srb.addInitParameter("resetEnable","false");

return srb;

}

@Bean

public FilterRegistrationBean statFilter(){

//创建过滤器

FilterRegistrationBean frb =

new FilterRegistrationBean(new WebStatFilter());

//设置过滤器过滤路径

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)执行多条sql,最后可以通过transactionStatus的setRollbackOnly()或rollbackToSavepoint(savepoint) 控制事务,如下:

@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() {

@Override

public Object doInTransaction(TransactionStatus transactionStatus) {

Object savepoint = transactionStatus.createSavepoint();

// DML执行

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";

}

上面是不带参数的多条sql的事务执行,若是带参数的多条sql,可以实现如下:

@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() {

@Override

public Object doInTransaction(TransactionStatus transactionStatus) {

Object savepoint = transactionStatus.createSavepoint();

pwgXZmgu // DML执行

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";

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:HashMap的get()方法的NullPointerException问题
下一篇:Maven Plugin的@Mojo和@Execute的具体使用
相关文章

 发表评论

暂时没有评论,来抢沙发吧~