Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析

25 篇文章 0 订阅

1. 前言

使用MySQL数据库时,使用事务与不使用事务相比,出现问题时排查更复杂。

不使用事务时,客户端只需要请求MySQL服务一次(只考虑显式执行的SQL语句);使用事务时,客户端至少需要请求MySQL服务四次(开启事务、执行SQL语句、提交/回滚事务、恢复自动提交)。

在Java中存在一些用法会导致事务失效,有的问题比较明显可以较快定位,有的问题隐藏较深可能需要较长时间排查。

因此需要对MySQL的事务执行原理进行分析,并整理用于排查事务相关问题的快速有效的方法。

在Java应用中访问MySQL服务时,涉及Java应用、网络传输、MySQL服务这三层,在每一层都可以对执行的SQL语句与事务操作进行监控与观测,涉及的内容如下图所示:

在这里插入图片描述

在分析使用事务执行SQL语句之前,需要先分析相对简单的场景,以下对Java应用使用Spring、MyBatis、Druid、MySQL Connector,不使用事务执行SQL语句的情况进行分析。

可参考以下相关内容:

内容链接
MySQL SQL语句与事务执行及日志分析https://blog.csdn.net/a82514921/article/details/126563449
tcpdump、Wireshark抓包分析MySQL SQL语句与事务执行https://blog.csdn.net/a82514921/article/details/126563471
Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析https://blog.csdn.net/a82514921/article/details/126563515
Spring、MyBatis、Druid、MySQL使用事务执行SQL语句分析https://blog.csdn.net/a82514921/article/details/126563542
Spring、MyBatis、Druid、MySQL执行SQL语句与事务监控https://blog.csdn.net/a82514921/article/details/126563558
数据源使用错误导致MySQL事务失效分析https://blog.csdn.net/a82514921/article/details/126563573
TiDB乐观事务、悲观事务模型验证https://blog.csdn.net/a82514921/article/details/126563502

2. 不使用事务执行SQL语句大致阶段

不使用事务执行SQL语句,每次执行SQL语句时的大致阶段如下:

从连接池借出连接(可能需要创建新连接)
执行SQL语句
归还连接至连接池

如下图所示:

在这里插入图片描述

不使用事务执行SQL语句时,主要由MyBatis完成,与Spring关系不大。

3. 示例项目

以下使用的示例项目下载地址为:https://github.com/Adrninistrator/DB-Transaction-test,使用说明可参考“README.md”,

相关的执行日志保存在DB-Transaction-test-log目录中,log-print_stack_trace_off目录中是未打印调用堆栈的日志,log-print_stack_trace_on目录中是打印调用堆栈的日志。

4. 验证环境

  • JDK版本

1.8

  • Spring版本

5.3.20

  • Mybatis版本

org.mybatis:mybatis:3.2.8
org.mybatis:mybatis-spring:1.2.2

  • Druid版本

1.2.10

  • MySQL Connector版本

8.0.29

  • MySQL版本

MariaDB 10.0.36

  • Spring事务传播机制

默认值REQUIRED

5. 不使用事务执行SQL语句详细过程

不使用事务执行SQL语句,每次执行SQL语句时的详细过程如下:

5.1. 创建连接

Druid创建连接包含以下两种情况:

5.1.1. 应用启动时创建连接

在Java应用启动时,会初始化Druid数据源,按照配置的initialSize参数,在初始化时创建指定数量的连接。该参数值可以为0,即初始化时可以不创建连接。

应用启动时创建连接,是在执行应用启动的线程中创建连接。

从Druid的DruidDataSource类的初始化方法init(),到MySQL Connector与MySQL服务进行Socket连接的调用堆栈如下:

com.alibaba.druid.pool.DruidDataSource.init(DruidDataSource:924)
com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource:1733)
com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource:1669)
com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl:150)
com.alibaba.druid.filter.FilterEventAdapter.connection_connect(FilterEventAdapter:38)
com.alibaba.druid.filter.FilterAdapter.connection_connect(FilterAdapter:787)
com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl:156)
com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver:198)
com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl:241)
com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl:448)
com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl:818)
com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl:948)
com.mysql.cj.NativeSession.connect(NativeSession:120)
com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection:63)
com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory:153)
java.net.Socket.connect(Socket:559)

5.1.2. 连接不够时创建连接

Druid在初始化时会创建名称前缀为“Druid-ConnectionPool-Create-”的线程,该线程会等待信号;当Java应用尝试从连接池借出连接,且连接不够需要创建时,会唤醒以上线程,使其执行创建连接的操作。

  • 创建用于创建连接的线程

在DruidDataSource类的初始化方法init()中,会调用createAndStartCreatorThread()方法,在该方法中会创建名称前缀为“Druid-ConnectionPool-Create-”的线程。

以上线程对应的类为DruidDataSource$CreateConnectionThread,在run()方法中会一直循环执行,假如当前不需要创建连接,则会调用Condition对象的await()方法,等待直到被唤醒。

  • 唤醒创建连接的线程

当Java应用尝试从连接池借出连接,但连接池中的连接不够,且还未达到最大允许的连接数maxActive时,会唤醒以上线程。

从DruidDataSource类的getConnection()方法,到调用Condition对象的signal()方法以唤醒线程,调用堆栈如下:

com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:100)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1399)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1407)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5055)
test.db.druid_filter.DruidMonitorFilter.dataSource_getConnection(DruidMonitorFilter:59)
com.alibaba.druid.filter.FilterAdapter.dataSource_getConnection(FilterAdapter:2756)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5059)
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource:1429)
com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource:1690)
com.alibaba.druid.pool.DruidDataSource.pollLast(DruidDataSource:2253)
com.alibaba.druid.pool.DruidDataSource.emptySignal(DruidDataSource:3890)
  • 执行创建连接操作

当名称前缀为“Druid-ConnectionPool-Create-”的线程被唤醒后,会执行创建连接的操作。

以上线程对应的类为DruidDataSource$CreateConnectionThread,其run()方法会调用DruidAbstractDataSource.createPhysicalConnection()方法,执行创建连接的操作。

后续的方法调用信息,与“应用启动时创建连接”部分相同,即创建连接的后续过程。

5.2. 从调用MyBatis的Mapper接口到SimpleExecutor

项目代码在调用MyBatis的Mapper接口时,实际调用的是MyBatis生成的代理类,后续的从连接池借出连接、执行SQL语句、归还连接至连接池阶段,均通过MyBatis完成。

参考“Configuration”https://mybatis.org/mybatis-3/configuration.htmlMyBatis的defaultExecutorType参数用于设置执行器,默认值为SIMPLE,默认情况下MyBatis使用SimpleExecutor作为执行器,即没有特殊处理的执行器。

在MyBatis的SqlSessionTemplate$SqlSessionInterceptor.invoke()方法中会调用执行SQL语句的方法,该方法中后续还会执行归还连接至连接池操作。

5.2.1. 执行查询操作

执行查询操作时,会执行SimpleExecutor.doQuery()方法,如示例项目的TransactionService类的doOperate()方法在调用Ds1TaskLockMapper接口的selectForUpdate()方法时,调用堆栈如下:

test.db.transaction.service.TransactionService.doOperate(TransactionService:111)
com.sun.proxy.$Proxy40.selectForUpdate(Unknown Source)
org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:52)
org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:68)
org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate:163)
com.sun.proxy.$Proxy38.selectOne(Unknown Source)
org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:358)
java.lang.reflect.Method.invoke(Method:498)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl:43)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl:62)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession:66)
org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession:102)
org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession:108)
org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor:77)
org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor:96)
org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor:137)
org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor:267)
org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor:59)

可以看到TaskLockMapper接口对应的代理类为com.sun.proxy.$Proxy40,还存在对代理类com.sun.proxy.$Proxy38的调用。

可以使用Arthas https://arthas.aliyun.com/zh-cn/ 的dump命令 https://arthas.aliyun.com/doc/dump.html ,对以上代理类的class文件进行dump。

  • com.sun.proxy.$Proxy40

以上Proxy40类继承了java.lang.reflect.Proxy类,实现了项目中的test.db.dao.ds1.Ds1TaskLockMapper接口。

Proxy40类在实现Ds1TaskLockMapper接口的方法时,会调用成员变量InvocationHandler h的invoke()方法。

通过调试可知,Proxy40类的成员变量InvocationHandler h类型为org.apache.ibatis.binding.MapperProxy。

在以上调用堆栈中,Proxy40.selectForUpdate()方法调用了MapperProxy.invoke()方法,与以上分析相符。

  • com.sun.proxy.$Proxy38

以上Proxy38类继承了java.lang.reflect.Proxy类,实现了org.apache.ibatis.session.SqlSession接口。

Proxy38类在实现SqlSession接口的方法时,会调用成员变量InvocationHandler h的invoke()方法。

通过调试可知,Proxy38类的成员变量InvocationHandler h类型为org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor。

在以上调用堆栈中,Proxy38.selectOne()方法调用了SqlSessionTemplate$SqlSessionInterceptor.invoke()方法方法,与以上分析相符。

5.2.2. 执行更新操作

执行更新操作时,会执行SimpleExecutor.doUpdate()方法,如示例项目的TransactionService类的doOperate()方法在调用Ds1TaskLockMapper接口的updateLockFlagNoCheck()方法时,调用堆栈如下:

test.db.transaction.service.TransactionService.doOperate(TransactionService:124)
com.sun.proxy.$Proxy40.updateLockFlagNoCheck(Unknown Source)
org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:52)
org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:54)
org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate:254)
com.sun.proxy.$Proxy38.update(Unknown Source)
org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:358)
java.lang.reflect.Method.invoke(Method:498)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl:43)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl:62)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession:152)
org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor:71)
org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor:105)
org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor:48)

在执行更新操作时,Proxy38类的update()方法会被调用,该类为实现了org.apache.ibatis.session.SqlSession接口的代理类。

5.3. 从连接池借出连接

不使用事务执行SQL语句时,执行SQL语句之前,MyBatis从连接池获取一个可用的连接即可,对于连接没有特殊的要求。

执行查询操作时,从MyBatis的SimpleExecutor.doQuery()方法开始,到Druid从连接池借出连接操作,调用堆栈如下:

org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor:59)
org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor:72)
org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor:279)
org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction:67)
org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction:81)
org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils:80)
org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils:117)
org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils:159)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:100)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1399)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1407)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5055)
test.db.druid_filter.DruidMonitorFilter.dataSource_getConnection(DruidMonitorFilter:59)
com.alibaba.druid.filter.FilterAdapter.dataSource_getConnection(FilterAdapter:2756)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5059)
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource:1429)
com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource:1783)
com.alibaba.druid.pool.DruidPooledConnection.<init>(DruidPooledConnection:79)

项目代码通过MyBatis的Mapper接口到调用SimpleExecutor类部分的调用堆栈已省略。

执行更新操作时,SimpleExecutor.doUpdate()方法中也会调用prepareStatement()方法,后续的调用堆栈与以上相同。

5.3.1. 不使用事务执行SQL语句时获取连接处理

在MyBatis的SimpleExecutor类中,查询操作对应的doQuery()方法、更新操作对应的doUpdate()方法,都会调用prepareStatement()方法,再调用父类BaseExecutor的getConnection()方法获取连接,该方法会调用SpringManagedTransaction类的getConnection()方法获取连接。

在SpringManagedTransaction类的getConnection()方法中,由于未使用事务,成员变量Connection connection为null,因此需要调用openConnection()方法打开连接;

SpringManagedTransaction类的相关方法代码如下:

public Connection getConnection() throws SQLException {
  if (this.connection == null) {
    openConnection();
  }
  return this.connection;
}

private void openConnection() throws SQLException {
  this.connection = DataSourceUtils.getConnection(this.dataSource);
  this.autoCommit = this.connection.getAutoCommit();
  this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

在openConnection()方法中会调用DataSourceUtils类的getConnection()、doGetConnection()方法,在DataSourceUtils.doGetConnection()方法中,会调用TransactionSynchronizationManager.getResource()方法,由于未使用事务,因此获取到的ConnectionHolder对象为null,后续会调用DataSourceUtils.fetchConnection()方法,以获取连接。

DataSourceUtils类的相关方法代码如下:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
	try {
		return doGetConnection(dataSource);

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");

    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(fetchConnection(dataSource));
        }
        return conHolder.getConnection();
    }
    // Else we either got no holder or an empty thread-bound holder here.

    logger.debug("Fetching JDBC Connection from DataSource");
    Connection con = fetchConnection(dataSource);            

5.4. 执行SQL语句

执行查询操作时,从MyBatis的SimpleExecutor.doQuery()方法开始,经过Druid,到MySQL Connector通过输出流向MySQL发送SQL语句,调用堆栈如下:

org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor:60)
org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler:73)
org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler:59)
com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement:497)
com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl:167)
com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl:3459)
com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter:440)
com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl:3461)
com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement:354)
com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement:893)
com.mysql.cj.NativeSession.execSQL(NativeSession:665)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol:683)
com.mysql.cj.protocol.a.NativeProtocol.send(NativeProtocol:619)
com.mysql.cj.protocol.a.TimeTrackingPacketSender.send(TimeTrackingPacketSender:50)
com.mysql.cj.protocol.a.SimplePacketSender.send(SimplePacketSender:51)
java.io.FilterOutputStream.write(FilterOutputStream:97)
java.io.BufferedOutputStream.write(BufferedOutputStream:117)

参考“Mapper XML Files”https://mybatis.org/mybatis-3/sqlmap-xml.html,MyBatis Mapper XML中,select、insert、update、delete等语句支持statementType属性,该属性可配置为STATEMENT、PREPARED或CALLABLE,分别代表使用Statement、PreparedStatement或CallableStatement,该属性默认值为PREPARED,即默认使用预编译语句PreparedStatement。

示例项目未开启服务端预编译,因此会使用客户端预编译方式执行SQL语句,如以上调用堆栈所示,使用MySQL Connector中的ClientPreparedStatement类,对应客户端预编译方式。

项目代码通过MyBatis的Mapper接口到调用SimpleExecutor类部分的调用堆栈已省略。

执行更新操作时,从SimpleExecutor.doUpdate()方法开始,到DruidPooledPreparedStatement.execute()方法的调用堆栈如下,后续的调用堆栈与以上相同:

org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor:48)
org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler:69)
org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler:44)
com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement:497)

5.5. 归还连接至连接池

不使用事务执行SQL语句时,执行SQL语句之后,MyBatis需要归还连接至连接池。

在MyBatis的SqlSessionTemplate$SqlSessionInterceptor类的invoke()方法中,在调用被代理的方法时会进行异常处理,在catch和finally代码块中会调用closeSqlSession()方法,执行关闭会话操作。

从MyBatis的Mapper接口的代理类开始,到调用Druid的归还连接至连接池,调用堆栈如下:

com.sun.proxy.$Proxy40.selectForUpdate(Unknown Source)
org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:52)
org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:68)
org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate:163)
com.sun.proxy.$Proxy38.selectOne(Unknown Source)
org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:379)
org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils:170)
org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession:210)
org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor:61)
org.apache.ibatis.executor.BaseExecutor.close(BaseExecutor:83)
org.mybatis.spring.transaction.SpringManagedTransaction.close(SpringManagedTransaction:123)
org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(DataSourceUtils:360)
org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils:393)
org.springframework.jdbc.datasource.DataSourceUtils.doCloseConnection(DataSourceUtils:406)
com.alibaba.druid.pool.DruidPooledConnection.close(DruidPooledConnection:286)
com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl:5045)
test.db.druid_filter.DruidMonitorFilter.dataSource_releaseConnection(DruidMonitorFilter:119)
com.alibaba.druid.filter.FilterAdapter.dataSource_releaseConnection(FilterAdapter:2750)
com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl:5049)
com.alibaba.druid.pool.DruidPooledConnection.recycle(DruidPooledConnection:351)
com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource:1951)
com.alibaba.druid.pool.DruidConnectionHolder.reset(DruidConnectionHolder:296)

以上为查询操作的调用堆栈示例,更新操作的类似。

5.6. 关闭连接

Druid关闭连接包含以下两种情况:

5.6.1. 应用停止时关闭数据源

在Java应用停止时,会关闭Druid数据源,并在执行应用停止的线程中关闭连接。

从DruidDataSource类的关闭方法close(),到MySQL Connector关闭MySQL服务相关Socket的调用堆栈如下:

com.alibaba.druid.pool.DruidDataSource.close(DruidDataSource:2118)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.close(ConnectionProxyImpl:114)
com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl:181)
test.db.druid_filter.DruidMonitorFilter.connection_close(DruidMonitorFilter:139)
com.alibaba.druid.filter.FilterAdapter.connection_close(FilterAdapter:777)
com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl:186)
com.mysql.cj.jdbc.ConnectionImpl.close(ConnectionImpl:713)
com.mysql.cj.jdbc.ConnectionImpl.realClose(ConnectionImpl:1721)
com.mysql.cj.NativeSession.quit(NativeSession:149)
com.mysql.cj.protocol.a.NativeProtocol.quit(NativeProtocol:1338)
com.mysql.cj.protocol.AbstractSocketConnection.forceClose(AbstractSocketConnection:103)
com.mysql.cj.protocol.NetworkResources.forceClose(NetworkResources:94)
java.net.Socket.close(Socket:1491)

5.6.2. 连接长时间空闲时关闭连接

Druid在初始化时,会启动DruidDataSource$DestroyConnectionThread类对应的线程,线程名前缀为“Druid-ConnectionPool-Destroy-”,该线程会查找空闲时间超过minEvictableIdleTimeMillis参数值的连接,将对应的连接关闭,以上操作执行的时间间隔通过timeBetweenEvictionRunsMillis参数控制。

从DruidDataSource$DestroyConnectionThread类的run()方法,到Druid的ConnectionProxyImpl.close()方法的调用堆栈如下:

com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource:2924)
com.alibaba.druid.pool.DruidDataSource$DestroyTask.run(DruidDataSource:2940)
com.alibaba.druid.pool.DruidDataSource.shrink(DruidDataSource:3196)
com.alibaba.druid.util.JdbcUtils.close(JdbcUtils:85)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.close(ConnectionProxyImpl:114)

以上调用堆栈之后的内容,与“应用停止时关闭数据源”部分相同,即关闭连接的后续过程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于创建独立的、基于生产级别的Spring应用程序的框架。它简化了Spring应用程序的配置和部署过程,并提供了许多开箱即用的功能和插件,使开发人员能够更地构建应用程序。 MyBatis是一个开源的持久层框架,它通过XML或注解的方式将Java对象映射到数据库中的SQL语句。它提供了灵活的SQL映射配置和强大的动态SQL支持,使得开发人员能够更方便地进行数据库操作。 Druid是一个开源的数据库连接池,它提供了高性能、可扩展和可监控的数据库连接池解决方案。它具有强大的监控和统计功能,可以帮助开发人员更好地管理和优化数据库连接。 在Spring Boot中使用MyBatisDruid的步骤如下: 1. 添加相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> ``` 2. 配置数据库连接信息: 在application.yml文件中添加以下配置: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/mydatabase username: root password: password driver-class-name: com.mysql.jdbc.Driver ``` 3. 编写MyBatis的Mapper接口和SQL映射文件: 创建Mapper接口,并使用@Mapper注解标识: ```java @Mapper public interface UserMapper { List<User> getAllUsers(); void insertUser(User user); void updateUser(User user); void deleteUser(int id); } ``` 创建SQL映射文件,例如UserMapper.xml: ```xml <mapper namespace="com.example.mapper.UserMapper"> <select id="getAllUsers" resultType="com.example.model.User"> SELECT * FROM users </select> <insert id="insertUser" parameterType="com.example.model.User"> INSERT INTO users (name, age) VALUES (#{name}, #{age}) </insert> <update id="updateUser" parameterType="com.example.model.User"> UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id} </update> <delete id="deleteUser" parameterType="int"> DELETE FROM users WHERE id = #{id} </delete> </mapper> ``` 4. 编写Service和Controller: 创建Service接口和实现类,用于处理业务逻辑: ```java public interface UserService { List<User> getAllUsers(); void insertUser(User user); void updateUser(User user); void deleteUser(int id); } @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> getAllUsers() { return userMapper.getAllUsers(); } @Override public void insertUser(User user) { userMapper.insertUser(user); } @Override public void updateUser(User user) { userMapper.updateUser(user); } @Override public void deleteUser(int id) { userMapper.deleteUser(id); } } @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/users") public List<User> getAllUsers() { return userService.getAllUsers(); } @PostMapping("/users") public void insertUser(@RequestBody User user) { userService.insertUser(user); } @PutMapping("/users/{id}") public void updateUser(@PathVariable int id, @RequestBody User user) { user.setId(id); userService.updateUser(user); } @DeleteMapping("/users/{id}") public void deleteUser(@PathVariable int id) { userService.deleteUser(id); } } ``` 以上是使用Spring Boot、MyBatisDruid的基本配置和使用方法。通过以上步骤,你可以在Spring Boot应用程序中使用MyBatisDruid进行数据库操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值