Skip to content

Fix NPE in ConnectionFactory when nested REQUIRES_NEW transaction establishes no JDBC connections#764

Merged
huayanYu merged 4 commits intomasterfrom
copilot/fix-connection-proxy-map-null
Mar 12, 2026
Merged

Fix NPE in ConnectionFactory when nested REQUIRES_NEW transaction establishes no JDBC connections#764
huayanYu merged 4 commits intomasterfrom
copilot/fix-connection-proxy-map-null

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 10, 2026

ConnectionFactory.notify() throws NPE when a REQUIRES_NEW transaction nested within a REQUIRED transaction completes without establishing any JDBC connections.

Root Cause

When a transaction establishes no connections, concurrentHashMap.get(xid) returns null. Line 150 attempts to iterate over connectionProxyMap.values() without checking for null:

for (ConnectionProxy connectionProxy : connectionProxyMap.values()) {
    // NPE when connectionProxyMap is null
}

Changes

  • ConnectionFactory.notify(): Added null check before accessing connectionProxyMap

    Map<String, ConnectionProxy> connectionProxyMap = concurrentHashMap.get(xid);
    if (connectionProxyMap == null) {
        return;
    }
  • Test Coverage: Added NonDatabaseConnectionService and NoConnectionService to reproduce the scenario where an outer REQUIRED transaction has JDBC connections but inner REQUIRES_NEW does not

Scenario

@DSTransactional(propagation = DsPropagation.REQUIRED)
public void outerRequiredWithConnection() {
    // Establishes JDBC connection
    runQuery();
    // Nested REQUIRES_NEW with no JDBC operations
    innerService.businessLogicOnly();  // Previously threw NPE on commit
}

@DSTransactional(propagation = DsPropagation.REQUIRES_NEW)
public void businessLogicOnly() {
    // No database operations - pure business logic
}

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • maven.aliyun.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -XX:MaxMetaspaceSize=512m -XX:&#43;HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant (dns block)
    • Triggering command: /usr/lib/jvm/temurin-8-jdk-amd64/bin/java /usr/lib/jvm/temurin-8-jdk-amd64/bin/java -XX:MaxMetaspaceSize=512m -XX:&#43;HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.13-bin/5xuhj0ry160q40clulazy9h7d/gradle-8.13/lib/gradle-daemon-main-8.13.jar -javaagent:/home/REDACTED/.gradle/wrapper/dists/gradle-8.13-bin/5xuhj0ry160q40clulazy9h7d/gradle-8.13/lib/agents/gradle-instrumentation-agent-8.13.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 8.13 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>REQUIRED中嵌套REQUIRES_NEW时,满足一定条件必定报错"connectionProxyMap" is null</issue_title>
<issue_description>Describe the bug
REQUIRED中嵌套REQUIRES_NEW时,若REQUIRED中存在jdbc连接,但REQUIRES_NEW中不存在jdbc连接,必定触发报错"connectionProxyMap" is null
注:不论是通过注解DSTransactional还是TransactionalTemplate,都会有这个问题

To Reproduce
本人使用的版本:4.3.1

      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
        <version>4.3.1</version>
      </dependency>

复现代码:

@SpringBootTest
class NonTransactionTests {
    
    @Autowired
    NonDatabaseConnectionService nonDatabaseConnectionService;
    
    /**
     * 复现问题
     */
    @Test
    void test1() {
        nonDatabaseConnectionService.reProduce();
    }
    
}
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.dynamic.datasource.tx.DsPropagation;
import com.ideaaedi.zoo.example.springboot.dynamic.datasource.mapper.MysqlStudentMapper;
import jakarta.annotation.Resource;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;

import java.util.concurrent.ThreadLocalRandom;

@Component
public class NonDatabaseConnectionService {
    
    @Resource
    private MysqlStudentMapper mysqlStudentMapper;
    
    /**
     * 问题复现
     */
    @DSTransactional(propagation = DsPropagation.REQUIRED)
    public void reProduce() {
        triggerJdbcConnection();
        // 获取aop对象,确保bizLogic的aop生效
        ((NonDatabaseConnectionService) AopContext.currentProxy()).bizLogic();
    }
    
    /**
     * 触发jdbc连接
     */
    public void triggerJdbcConnection() {
        /*
         * 调试时发现,只有当触发了jdbc connection时,dynamic-datasource才会往ConnectionFactory.CONNECTION_HOLDER中放置事务连接信息
         */
        mysqlStudentMapper.list();
    }
    
    /**
     * 业务逻辑(模拟:满足某些业务条件时,可能不会有任何与数据库有关的操作)
     */
    @DSTransactional(propagation = DsPropagation.REQUIRES_NEW)
    public void bizLogic() {
        if (ThreadLocalRandom.current().nextBoolean()) {
            /*
             * bug反馈:如果代码走到这个if中,那么就会报错:
             *
             * java.lang.NullPointerException: Cannot invoke "java.util.Map.values()" because "connectionProxyMap" is null
             *
             * 	at com.baomidou.dynamic.datasource.tx.ConnectionFactory.notify(ConnectionFactory.java:150)
             * 	at com.baomidou.dynamic.datasource.tx.LocalTxUtil.commit(LocalTxUtil.java:91)
             * 	at com.baomidou.dynamic.datasource.tx.TransactionalTemplate.doExecute(TransactionalTemplate.java:126)
             * 	at com.baomidou.dynamic.datasource.tx.TransactionalTemplate.execute(TransactionalTemplate.java:94)
             * 	at com.baomidou.dynamic.datasource.aop.DynamicLocalTransactionInterceptor.invoke(DynamicLocalTransactionInterceptor.java:59)
             * 	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
             */
            System.err.println("bizLogic");
        } else {
            triggerJdbcConnection();
        }
    }
}

Expected behavior
期望如果方法不涉及jdbc操作的话,一切正常,不报错NPE

Screenshots

Desktop (please complete the following information):

  • OS: WINDOWS10

Smartphone (please complete the following information):

Additional context

</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 10, 2026 02:59
Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com>
Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com>
Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix null connectionProxyMap error in nested transactions Fix NPE in ConnectionFactory when nested REQUIRES_NEW transaction establishes no JDBC connections Feb 10, 2026
Copilot AI requested a review from huayanYu February 10, 2026 03:07
@huayanYu huayanYu marked this pull request as ready for review March 12, 2026 08:29
Copilot AI review requested due to automatic review settings March 12, 2026 08:29
@huayanYu huayanYu merged commit da3df59 into master Mar 12, 2026
3 of 4 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an NPE scenario in the dynamic datasource local transaction lifecycle when a REQUIRES_NEW transactional method performs no JDBC work, and adds an integration test reproduction to prevent regressions.

Changes:

  • Add a guard in ConnectionFactory.notify(...) to safely handle transactions with no tracked JDBC connections.
  • Introduce two Spring test services to reproduce the “REQUIRES_NEW without JDBC connection” case.
  • Add a Spring Boot 3 starter test that exercises the outer REQUIRED + inner REQUIRES_NEW (no JDBC) flow.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
dynamic-datasource-test-common/.../NonDatabaseConnectionService.java Adds a test helper service that opens a JDBC connection in an outer REQUIRED transaction and invokes an inner REQUIRES_NEW method.
dynamic-datasource-test-common/.../NoConnectionService.java Adds a test helper service with a REQUIRES_NEW method that performs no JDBC operations.
dynamic-datasource-spring/.../ConnectionFactory.java Adds a null-guard to avoid NPE when committing/rolling back a transaction that never obtained a JDBC connection.
dynamic-datasource-spring-boot3-starter/.../DsTransactionalTest.java Adds an integration test covering the new edge case.

@DSTransactional(propagation = DsPropagation.REQUIRES_NEW)
public void innerRequiresNewWithoutConnection() {
// No database operations - just business logic
System.out.println("Business logic without database operations");
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid writing to stdout from this Spring @service. It will add noisy console output to the test suite (and potentially to any app that happens to include test-common). Prefer removing the print entirely or using the project’s logger (e.g., Slf4j) if you need an observable side-effect.

Suggested change
System.out.println("Business logic without database operations");

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REQUIRED中嵌套REQUIRES_NEW时,满足一定条件必定报错"connectionProxyMap" is null

3 participants