广告
返回顶部
首页 > 资讯 > 后端开发 > Python >详解SpringBoot中JdbcTemplate的事务控制
  • 305
分享到

详解SpringBoot中JdbcTemplate的事务控制

2024-04-02 19:04:59 305人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

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

前言

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

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

原生Jdbc的事务控制

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


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();
    // 获取到原本的自动提交状态
    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;
 

@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;
 
    
    @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;
    }
    
    @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);
    }
 
    
    @Bean(name = "txTemplate")
    public TransactionTemplate transactionTemplate (@Autowired DataSourceTransactionManager transactionManager){
        return new TransactionTemplate(transactionManager);
    }
 
    
    @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,最后可以通过transactionStatussetRollbackOnly()或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<Object>() {
        @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<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus transactionStatus) {
            Object savepoint = transactionStatus.createSavepoint();
            // 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";
}

到此这篇关于SpringBoot中JdbcTemplate的事务控制的文章就介绍到这了,更多相关SpringBoot JdbcTemplate事务控制内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 详解SpringBoot中JdbcTemplate的事务控制

本文链接: https://www.lsjlt.com/news/135547.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解SpringBoot中JdbcTemplate的事务控制
    目录前言原生Jdbc的事务控制Spring的声明式事务控制尝试JdbcTemplate的事务控制TransactionTemplate的编程式事务控制前言 JdbcTemplate是...
    99+
    2022-11-12
  • 详解Golang中如何控制事务
    Golang是目前比较流行的编程语言之一,其在处理高并发和大量数据方面有着出色的表现。在开发过程中,事务是不可缺少的一部分。本文将介绍Golang如何控制事务。一、什么是事务?在数据库中,事务是指一组操作,位于 BEGIN 和 COMMIT...
    99+
    2023-05-14
  • 详解springboot springsecuroty中的注销和权限控制问题
    目录1 账户注销1.1 在SecurityConfig中加入开启注销功能的代码1.2 在index.html 添加注销的按钮1.3 启动项目测试2 权限控制2.1 导入springs...
    99+
    2022-11-13
  • 怎么在springBoot中利用service实现层事务控制的操作
    怎么在springBoot中利用service实现层事务控制的操作?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。springBoot使用事物比较简单,在Appl...
    99+
    2023-06-06
  • Java_Spring之Spring中的事务控制
    目录1 Spring 事务控制要明确的内容2 Spring 中事务控制的 API 介绍2.1 PlatformTransactionManager2.2 TransactionDef...
    99+
    2023-05-14
    Java Spring的事务控制 Spring中的事务控制
  • Spring方法中调用异步方法进行事务控制详解
    Spring 异步事务控制 文章目录 Spring 异步事务控制Spring 事务源码逻辑一、事务拦截器拦截二、进行事务控制三、事务开启 / 回滚 /提交操作四、事务完成,清除事务信息简单总结 异步方法事务控制方案一:自身编码...
    99+
    2023-08-20
    spring java 数据库 mysql
  • 详解Java中的流程控制
    1.分支结构的概念 当需要进行条件判断并做出选择时,使用分支结构 2.if分支结构 格式: if(条件表达式){ 语句块; } package com.lagou.Day...
    99+
    2022-11-12
  • 详解Node.js中的事件机制
    前言 在前端编程中,事件的应用十分广泛,DOM上的各种事件。在Ajax大规模应用之后,异步请求更得到广泛的认同,而Ajax亦是基于事件机制的。 通常js给我们的第一印象就是运行在客户端浏览器上面的脚本,通过...
    99+
    2022-06-04
    详解 机制 事件
  • 分析Springboot中嵌套事务失效原因详解
    首先两个事务方法,其中一个调用另一个。 @Transactional(rollbackFor = Exception.class) public void trance() { ...
    99+
    2022-11-12
  • Java 中的控制反转(IOC)详解
    目录IOC理论推导Spring管理对象Spring管理对象的简单例子Bean无参构造类创建和有参构造类创建Spring的一些配置别名Bean的配置import总结IOC理论推导 ...
    99+
    2022-11-12
  • Spring中的事务控制知识总结
    目录一、环境准备二、基于 XML 的事务控制三、基于注解的事务控制一、环境准备 为了演示 Spring 中的事务控制,我们创建一个空项目,项目目录如下: 导入依赖: <d...
    99+
    2022-11-12
  • PHP中的输出缓冲控制详解
    目录清除输出获得输出缓冲区的内容刷新(输出)缓冲区内容一些检测函数使用 ob_start() 的回调函数来进行输出缓冲区的内容替换添加 URL 重写器总结测试代码:在 PHP 中,我...
    99+
    2022-11-12
  • Java中的逻辑控制语句详解
    目录顺序结构分支结构if语句基本语法格式:举例:注意事项:switch语句基本语法格式:执行流程:注意事项:不能做switch参数的类型有哪些?循环结构while循环基本语法格式:w...
    99+
    2022-11-12
  • 详解JavaSelenium中的鼠标控制操作
    目录简介鼠标控制单击左键单击右键双击左键按压左键鼠标箭头移动鼠标释放鼠标拖拽鼠标等待简介 本文主要讲解如何用java Selenium 控制鼠标在浏览器上的操作方法。主要列举的代码示...
    99+
    2023-01-06
    Java Selenium鼠标操作 Java Selenium鼠标 Java Selenium
  • 详解JavaSelenium中的键盘控制操作
    目录简介键盘控制补充知识简介 本文主要简介如何使用java代码利用Selenium 控制浏览器中需要用到的键盘操作。 键盘控制 webdriver 中 Keys 类几乎提供了键盘上的...
    99+
    2023-01-06
    Java Selenium键盘操作 Java Selenium键盘 Java Selenium
  • node.js中的事件处理机制详解
    EventEmitter类 在Node.js的用于实现各种事件处理的event模块中,定义了一个EventEmitter类。所有可能触发事件的对象都是一个集成了EventEmitter类的子类的实例对象,在...
    99+
    2022-06-04
    详解 机制 事件
  • 关于SpringBoot的异常回滚和事务的使用详解
    目录Springboot中事务的使用:开启事务的方法中事务回滚的情况:Springboot @Transactional 事务不回滚一、异常捕获的原因二、数据库引擎不支持回滚(使用M...
    99+
    2023-05-19
    SpringBoot异常回滚 SpringBoot事务
  • MySQL中事务控制对的示例分析
    这篇文章主要介绍MySQL中事务控制对的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!什么是事务控制事务是指作为一个逻辑工作单元执行的一系列操作,这些操作要么全部成功,要么全...
    99+
    2022-10-18
  • Spring框架 注解配置事务控制的流程
    目录基于注解的事务控制1.配置事务管理器2.在业务层使用@Transactional 注解3.开启 spring 对注解事务的支持4.注解扫描器Spring 注解事务实现机制1.事务...
    99+
    2022-11-12
  • C语言中的线程信号控制详解
    目录一、场景介绍二、解决方法1、临时线程2、全局变量信号3、信号量一、场景介绍 存在三个线程,一个主线程和两个子线程(子线程数量不固定)。为了节省频繁创建销毁线程造成的资源浪费,将这...
    99+
    2023-02-03
    C语言 线程 信号控制 C语言 信号控制 C语言线程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作