目录
  • 原生jdbc的事务控制
  • spring的声明式事务控制
  • 尝试jdbctemplate的事务控制
  • transactiontemplate的编程式事务控制

前言

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;
 
/**
 * 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,最后可以通过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事务控制内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!