详解SpringBoot中JdbcTemplate的事务控制⽬录
前⾔
原⽣Jdbc的事务控制
Spring的声明式事务控制
尝试JdbcTemplate的事务控制
TransactionTemplate的编程式事务控制
前⾔
JdbcTemplate是spring-jdbc提供的数据库核⼼操作类,那对JdbcTemplate进⾏事务控制呢?
我的环境:spring-boot-2.1.3,druid-1.1.3。
原⽣Jdbc的事务控制
即,批处理+⾃动提交的控制⽅式,
druid连接池配置详解
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("sql.jdbc.Driver");
Connection conn = Connection(url, username, password);
Statement statement = ateStatement();
// 获取到原本的⾃动提交状态
boolean ac = AutoCommit();
/
/ 批处理多条sql操作
statement.addBatch(sql1);
statement.addBatch(sql2);
// 关闭⾃动提交
conn.setAutoCommit(false);
try {
// 提交批处理
// 若批处理⽆异常,则准备⼿动commit
connmit();
} catch (Exception e) {
e.printStackTrace();
// 批处理抛异常,则rollback
try {
} 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 = DataSource().getConnection();
LOG.info("1:{}", conn);
boolean ac = AutoCommit();
conn.setAutoCommit(false);
try {
int[] rs2 = jdbcTemplate.batchUpdate(new String[]{sql1, sql2});
connmit();
} catch (Throwable e) {
<("Error occured, cause by: {}", e.getMessage());
} finally {
conn.setAutoCommit(ac);
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
<("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())
→Connection(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.fig;
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 t.annotation.Bean;
import t.annotation.Configuration;
import org.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import ansaction.support.TransactionTemplate;
/**
* Druid数据库连接池配置⽂件
*/
@Configuration
public class DruidConfig {
private static final Logger logger = Logger(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;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。