Skip to content

Commit da3df59

Browse files
CopilothuayanYu
andauthored
Fix NPE in ConnectionFactory when nested REQUIRES_NEW transaction establishes no JDBC connections (#764)
* Initial plan * Fix NPE in ConnectionFactory.notify() when connectionProxyMap is null Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com> * Add test for REQUIRED with nested REQUIRES_NEW without JDBC connections Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com> * Fix test to avoid duplicate datasource creation Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com>
1 parent 1d7f40e commit da3df59

4 files changed

Lines changed: 124 additions & 0 deletions

File tree

dynamic-datasource-spring-boot3-starter/src/test/java/com/baomidou/dynamic/datasource/common/v3/DsTransactionalTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class DsTransactionalTest {
4545
private AccountService accountService;
4646
@Autowired
4747
private ProductService productService;
48+
@Autowired
49+
private NonDatabaseConnectionService nonDatabaseConnectionService;
4850
private DynamicRoutingDataSource ds;
4951

5052
@Test
@@ -84,6 +86,19 @@ public void testDsTransactional() {
8486
assertThat(productService.selectProduct()).isEqualTo(new Product(1, 10.0, 15));
8587
}
8688

89+
@Test
90+
public void testRequiredWithRequiresNewNoConnection() {
91+
// Setup datasources
92+
ds = (DynamicRoutingDataSource) dataSource;
93+
if (!ds.getDataSources().containsKey("order")) {
94+
DataSourceProperty orderDataSourceProperty = createDataSourceProperty("order");
95+
ds.addDataSource(orderDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(orderDataSourceProperty));
96+
}
97+
98+
// This should not throw NPE even though the inner REQUIRES_NEW transaction has no JDBC connections
99+
nonDatabaseConnectionService.outerRequiredWithConnection();
100+
}
101+
87102
private DataSourceProperty createDataSourceProperty(String poolName) {
88103
DataSourceProperty result = new DataSourceProperty();
89104
result.setPoolName(poolName);

dynamic-datasource-spring/src/main/java/com/baomidou/dynamic/datasource/tx/ConnectionFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ public static void notify(String xid, Boolean state) throws Exception {
109109
boolean hasSavepoint = hasSavepoint(xid);
110110
List<SavePointHolder> savePointHolders = savePointMap.get(xid);
111111
Map<String, ConnectionProxy> connectionProxyMap = concurrentHashMap.get(xid);
112+
if (connectionProxyMap == null) {
113+
return;
114+
}
112115
try {
113116
//If there is a savepoint,Indicates a nested transaction.
114117
if (hasSavepoint) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.common.service.tx;
17+
18+
import com.baomidou.dynamic.datasource.annotation.DS;
19+
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
20+
import com.baomidou.dynamic.datasource.tx.DsPropagation;
21+
import org.springframework.stereotype.Service;
22+
23+
/**
24+
* Service with REQUIRES_NEW transaction but no JDBC connections
25+
*/
26+
@Service
27+
@DS("order")
28+
public class NoConnectionService {
29+
30+
/**
31+
* Inner REQUIRES_NEW transaction without JDBC connection
32+
* This should not throw NPE when committing
33+
*/
34+
@DSTransactional(propagation = DsPropagation.REQUIRES_NEW)
35+
public void innerRequiresNewWithoutConnection() {
36+
// No database operations - just business logic
37+
System.out.println("Business logic without database operations");
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.common.service.tx;
17+
18+
import com.baomidou.dynamic.datasource.annotation.DS;
19+
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
20+
import com.baomidou.dynamic.datasource.tx.DsPropagation;
21+
import org.springframework.stereotype.Service;
22+
23+
import javax.sql.DataSource;
24+
import java.sql.Connection;
25+
import java.sql.ResultSet;
26+
import java.sql.Statement;
27+
28+
/**
29+
* Test service to reproduce the NPE issue when REQUIRES_NEW has no JDBC connections
30+
*/
31+
@Service
32+
@DS("order")
33+
public class NonDatabaseConnectionService {
34+
private final DataSource dataSource;
35+
private final NoConnectionService noConnectionService;
36+
37+
public NonDatabaseConnectionService(DataSource dataSource, NoConnectionService noConnectionService) {
38+
this.dataSource = dataSource;
39+
this.noConnectionService = noConnectionService;
40+
}
41+
42+
/**
43+
* Outer REQUIRED transaction with JDBC connection
44+
*/
45+
@DSTransactional(propagation = DsPropagation.REQUIRED)
46+
public void outerRequiredWithConnection() {
47+
// Trigger JDBC connection
48+
triggerJdbcConnection();
49+
// Call nested REQUIRES_NEW without JDBC connection
50+
noConnectionService.innerRequiresNewWithoutConnection();
51+
}
52+
53+
/**
54+
* Trigger a JDBC connection
55+
*/
56+
private void triggerJdbcConnection() {
57+
try (Connection connection = dataSource.getConnection();
58+
Statement statement = connection.createStatement();
59+
ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM p_order")) {
60+
if (resultSet.next()) {
61+
resultSet.getInt(1);
62+
}
63+
} catch (Exception e) {
64+
throw new RuntimeException(e);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)