链接MySQL数据库出现CannotCreateTransactionException

本文探讨了在应用中使用DBCP连接池时遇到的通信异常问题,并通过对比DBCP与JNDI链接池的配置,特别是最大闲置时间设置,成功定位并解决了异常。解释了异常产生的原因及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上线应用链接MySQL数据库时出现如下异常信息:

org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception iscom.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 


** BEGIN NESTED EXCEPTION ** 


java.net.SocketException

MESSAGE: Connection reset


STACKTRACE:


java.net.SocketException: Connection reset

at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:96)

at java.net.SocketOutputStream.write(SocketOutputStream.java:136)

at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)

at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)

at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:2637)

at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1554)

at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)

at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)

at com.mysql.jdbc.Connection.setAutoCommit(Connection.java:5273)

at org.apache.commons.dbcp.DelegatingConnection.setAutoCommit(DelegatingConnection.java:331)

at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.setAutoCommit(PoolingDataSource.java:317)

at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:203)

at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:322)

at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:255)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:102)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:176)

at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:615)

at com.alibaba.intl.ae.biz.wsproduct.product.service.impl.WsProductServiceImpl$$EnhancerByCGLIB$$3c743538.findWsproductById(<generated>)

 

从异常信息可以看出是DBCP链接池中的链接的通信异常了。

如果我们将数据链接池切换为Jboss的JNDI链接,该异常就不再出现了,故一探究竟。

在上网查询信息,发现MySQL有一个空闲链接的参数,默认值是28800(8小时),可以通过show global variables like 'wait_timeout'语句在MySQL数据库里查询。但其实发现出现上述异常信息,大概5分钟就会出现,而不需要8小时。而线上生产环境的机器是通过F5链接数据库集群的,而F5的链接超时时间是5分钟,这就解决了为什么5分钟左右就会出现通信异常而不是8小时了。

那么推测出就是DBCP链接池的失效时间大于了5分钟,导致F5断掉了链接,而JNDI的链接失效时间是小于5分钟的,所以应用从JNDI拿到链接都是有效的链接,从而不会抛出文章开头的异常信息。

那么DBCP和JNDI的最大闲置时间在那里配置的呢?

查看了线上生产环境的代码,DBCP在Spring的配置文件里面配置,配置如下:

<bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" abstract="true">

<property name="maxActive" value="10" />

<property name="maxIdle" value="4" />

<property name="minIdle" value="0" />

<property name="maxWait" value="100000" />

<property name="timeBetweenEvictionRunsMillis" value="600000" />

<property name="minEvictableIdleTimeMillis" value="300000" />

</bean>

timeBetweenEvictionRunsMillis的值就是配置的DBCP的最大闲置时间,线上配置的是600000毫秒,即10分钟。

 

JNDI的线上配置如下:

<min-pool-size>0</min-pool-size>

<max-pool-size>20</max-pool-size>

<blocking-timeout-millis>5000</blocking-timeout-millis>

<idle-timeout-minutes>3</idle-timeout-minutes>

<prepared-statement-cache-size>50</prepared-statement-cache-size>

idle-timeout-minutes的值就是配置JNDI的最大闲置时间,单位为分钟,线上配置为3分钟。

 

参考文章:

http://www.iteye.com/topic/550296

http://www.iteye.com/topic/569864

http://episode.iteye.com/blog/323244

http://blog.youkuaiyun.com/windy26205/article/details/6572724


最近遇到过这个问题,所以把文章转载到这里了,但是我的这个问题还是没有找到原因,之后会在查找问题的

<think>我们正在创建一个自定义异常类 `CannotCreateTransactionException`。这个异常应该继承自Spring的事务异常基类,通常我们会选择继承 `TransactionException`,因为 `CannotCreateTransactionException` 在Spring中已经存在,但如果我们想自定义,可以继承它或者创建一个新的。 但是,请注意,Spring已经有一个 `org.springframework.transaction.CannotCreateTransactionException`。如果我们需要自定义行为或添加额外信息,我们可以扩展这个异常。 不过,根据问题,用户可能希望自己创建一个自定义的异常。因此,我们可以创建一个新的异常类,继承自Spring的 `TransactionException` 或更具体的 `CannotCreateTransactionException`(如果我们想保持与Spring一致)或者直接继承 `RuntimeException`(因为事务异常通常是运行时异常)。 这里,我们假设用户希望创建一个自定义的事务创建失败异常,我们可以这样设计: 1. 继承Spring的 `CannotCreateTransactionException` 以保持一致性,或者继承 `RuntimeException` 并添加必要的构造方法。 2. 添加自定义的属性和方法,以便携带更多关于失败原因的信息。 但是,由于Spring的 `CannotCreateTransactionException` 已经存在,并且已经提供了多个构造方法,我们可以直接使用它,而不需要自定义。如果我们需要额外的信息,我们可以扩展它。 下面是一个自定义的 `CannotCreateTransactionException` 的例子,它扩展了Spring的 `CannotCreateTransactionException`,并添加了一个自定义字段(例如错误代码): 注意:如果不需要额外字段,直接使用Spring提供的即可。 但是,如果用户确实需要自定义,我们可以这样写: ```java import org.springframework.transaction.CannotCreateTransactionException; public class CustomCannotCreateTransactionException extends CannotCreateTransactionException { private final String errorCode; public CustomCannotCreateTransactionException(String msg, Throwable cause, String errorCode) { super(msg, cause); this.errorCode = errorCode; } public CustomCannotCreateTransactionException(String msg, String errorCode) { super(msg); this.errorCode = errorCode; } public String getErrorCode() { return errorCode; } } ``` 这样,我们就创建了一个自定义异常,它包含一个错误代码,可以用来标识特定的错误类型。 但是,请注意,在Spring事务管理器中,当无法创建事务时,默认抛出的就是 `CannotCreateTransactionException`。如果我们想要抛出自定义的异常,我们需要在事务管理器中进行配置或者修改事务管理的代码。这通常涉及到自定义事务管理器,或者使用AOP拦截异常并转换。 另一种做法是,在需要抛出该异常的地方,直接抛出我们自定义的异常。例如,在事务开始的地方,如果检测到某些条件不满足(比如无法获取数据库连接),我们可以抛出这个自定义异常。 示例: ```java import org.springframework.transaction.support.TransactionSynchronizationManager; // 在某个事务方法中 public void performTransaction() { // 检查事务是否已经激活 if (!TransactionSynchronizationManager.isActualTransactionActive()) { // 如果无法创建事务,我们抛出自定义异常 throw new CustomCannotCreateTransactionException("无法创建事务", "ERR_TX_CREATE_001"); } // ... 业务逻辑 } ``` 但是,请注意,Spring的事务管理通常由事务管理器处理,我们通常不会手动检查事务是否激活。上面的示例仅用于演示。 更常见的做法是,在事务管理器的内部,当无法获取数据库连接时,会抛出 `CannotCreateTransactionException`。如果我们想要抛出带错误代码的异常,我们需要自定义事务管理器。 例如,我们可以扩展 `DataSourceTransactionManager` 并重写 `doBegin` 方法: ```java import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; import javax.sql.DataSource; import java.sql.SQLException; public class CustomDataSourceTransactionManager extends DataSourceTransactionManager { public CustomDataSourceTransactionManager(DataSource dataSource) { super(dataSource); } @Override protected void doBegin(Object transaction, TransactionDefinition definition) { try { super.doBegin(transaction, definition); } catch (CannotCreateTransactionException ex) { // 这里我们可以将原始的异常转换为自定义异常 throw new CustomCannotCreateTransactionException(ex.getMessage(), ex.getCause(), "ERR_TX_CREATE_001"); } catch (Exception ex) { // 其他异常也可以转换 throw new CustomCannotCreateTransactionException("创建事务时发生错误", ex, "ERR_TX_CREATE_002"); } } } ``` 然后,在Spring配置中使用这个自定义的事务管理器。 但是,请注意,在Spring中,事务创建失败通常已经抛出了 `CannotCreateTransactionException`,我们这里捕获它并重新抛出自定义异常,可能会丢失一些原始信息。因此,除非有特殊需求(比如需要添加错误代码),否则直接使用Spring提供的异常即可。 总结:自定义异常类可以扩展Spring的 `CannotCreateTransactionException`,并添加需要的字段和方法。然后在适当的地方(如自定义事务管理器)抛出这个异常。 相关问题: 1. Spring事务管理器的默认行为是什么? 2. 如何在Spring中配置自定义的事务管理器? 3. 如何处理事务创建失败的情况? 4. 事务回滚的规则是什么?如何自定义回滚规则? 5. 如何捕获并处理事务创建异常?</think>### 如何在 Java 中创建自定义的 `CannotCreateTransactionException` 异常类 以下是创建自定义事务异常类的完整实现,继承自 Spring 的 `TransactionException` 基类: ```java import org.springframework.transaction.TransactionException; /** * 自定义事务创建失败异常 * 用于处理数据库事务创建失败的情况 */ public class CannotCreateTransactionException extends TransactionException { // 错误码字段(可选扩展) private final String errorCode; /** * 基础构造方法 * @param msg 异常描述信息 */ public CannotCreateTransactionException(String msg) { super(msg); this.errorCode = "TX_ERR_001"; } /** * 包含根本原因的构造方法 * @param msg 异常描述信息 * @param cause 触发异常的原始异常 */ public CannotCreateTransactionException(String msg, Throwable cause) { super(msg, cause); this.errorCode = "TX_ERR_002"; } /** * 包含错误码的构造方法 * @param msg 异常描述信息 * @param cause 触发异常的原始异常 * @param errorCode 自定义错误码 */ public CannotCreateTransactionException(String msg, Throwable cause, String errorCode) { super(msg, cause); this.errorCode = errorCode; } // 获取错误码 public String getErrorCode() { return errorCode; } @Override public String toString() { return "CannotCreateTransactionException [errorCode=" + errorCode + "]: " + getMessage(); } } ``` ### 关键设计说明 1. **继承关系**: ```mermaid classDiagram Throwable <|-- Exception Exception <|-- RuntimeException RuntimeException <|-- TransactionException TransactionException <|-- CannotCreateTransactionException ``` 继承 Spring 的 `TransactionException` 保证与 Spring 事务体系兼容[^3] 2. **核心构造方法**: - `CannotCreateTransactionException(String msg)`:基础异常 - `CannotCreateTransactionException(String msg, Throwable cause)`:携带原始异常 - `CannotCreateTransactionException(String msg, Throwable cause, String errorCode)`:带自定义错误码 3. **使用场景示例**: ```java @Service public class TransactionalService { @Transactional public void criticalOperation() { try { // 尝试获取数据库连接 Connection conn = dataSource.getConnection(); // 业务操作... } catch (SQLException ex) { // 抛出自定义事务异常 throw new CannotCreateTransactionException( "无法创建数据库事务连接", ex, "DB_CONN_FAIL" ); } } } ``` ### 最佳实践建议 1. **异常触发条件**: - 数据库连接池耗尽 - 事务管理器初始化失败 - 分布式事务协调器不可用 - 事务超时设置冲突 2. **异常处理示例**: ```java @RestControllerAdvice public class TransactionExceptionHandler { @ExceptionHandler(CannotCreateTransactionException.class) public ResponseEntity<ErrorResponse> handleTransactionFailure( CannotCreateTransactionException ex) { ErrorResponse error = new ErrorResponse( ex.getErrorCode(), "事务创建失败: " + ex.getMessage(), Instant.now() ); return ResponseEntity .status(HttpStatus.SERVICE_UNAVAILABLE) .body(error); } } ``` 3. **与 Spring 事务集成**: - 在 `@Transactional` 注解方法中抛出此异常会自动触发回滚 - 符合 Spring 对 unchecked exception 的回滚规则[^1] - 避免在 try-catch 块中吞没异常,否则事务不会回滚 ### 常见问题解决 1. **事务不回滚**: ```java // 错误做法:捕获异常未重新抛出 try { transactionalService.criticalOperation(); } catch (CannotCreateTransactionException e) { logger.error("事务失败", e); // 此处缺少 throw e; 会导致事务不回滚 } ``` 2. **动态数据源切换问题**: 使用 `AbstractRoutingDataSource` 时,确保在事务边界内正确切换数据源[^4],推荐在事务管理器层面处理: ```java public class CustomTransactionManager extends DataSourceTransactionManager { @Override protected void doBegin(Object transaction, TransactionDefinition definition) { // 动态切换数据源逻辑 DataSourceContextHolder.setDataSourceType(definition.getName()); super.doBegin(transaction, definition); } } ``` --- ### 相关问题 1. Spring 事务默认回滚哪些类型的异常?[^1] 2. 如何自定义 Spring 事务管理器的回滚规则? 3. 使用 `@Transactional` 时如何避免数据源切换问题?[^4] 4. 如何在全局异常处理器中区分不同类型的事务异常? 5. 事务传播行为(PROPAGATION_REQUIRES_NEW)如何影响异常处理? 6. 分布式事务场景下如何处理 `CannotCreateTransactionException`?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值