Skip to content

Commit 2d4b168

Browse files
authored
Merge pull request #228 from domaframework/fix/infer-jdbc-driver-for-tc
Fix JDBC driver class loading and Testcontainers support
2 parents 756b7bd + 5ac6fbd commit 2d4b168

File tree

5 files changed

+166
-14
lines changed

5 files changed

+166
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
/.metadata
99
/.idea/
1010
.factorypath
11+
/.claude/

codegen/src/main/java/org/seasar/doma/gradle/codegen/extension/CodeGenConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.seasar.doma.gradle.codegen.dialect.CodeGenDialectRegistry;
3030
import org.seasar.doma.gradle.codegen.exception.CodeGenException;
3131
import org.seasar.doma.gradle.codegen.generator.Generator;
32+
import org.seasar.doma.gradle.codegen.jdbc.DriverWrapper;
3233
import org.seasar.doma.gradle.codegen.message.Message;
3334
import org.seasar.doma.gradle.codegen.util.ClassUtil;
3435
import org.seasar.doma.gradle.codegen.util.JdbcUtil;
@@ -159,9 +160,10 @@ private Provider<DataSource> dataSourceProvider() {
159160
ClassLoader classLoader = createClassLoader();
160161
Driver driver =
161162
ClassUtil.newInstance(Driver.class, driverClassName, "driverClassName", classLoader);
163+
DriverWrapper driverWrapper = new DriverWrapper(driver);
162164
return globalFactory
163165
.get()
164-
.createDataSource(driver, user.getOrNull(), password.getOrNull(), url.get());
166+
.createDataSource(driverWrapper, user.getOrNull(), password.getOrNull(), url.get());
165167
});
166168
}
167169

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package org.seasar.doma.gradle.codegen.jdbc;
2+
3+
import java.sql.Connection;
4+
import java.sql.Driver;
5+
import java.sql.DriverPropertyInfo;
6+
import java.sql.SQLException;
7+
import java.sql.SQLFeatureNotSupportedException;
8+
import java.util.Objects;
9+
import java.util.Properties;
10+
import java.util.logging.Logger;
11+
12+
/**
13+
* A wrapper for JDBC Driver that manages class loader context switching.
14+
*
15+
* <p>This wrapper ensures that all driver operations are executed with the correct class loader
16+
* context, preventing class loading issues when the driver and calling code are loaded by different
17+
* class loaders.
18+
*
19+
* @see java.sql.Driver
20+
*/
21+
public class DriverWrapper implements Driver {
22+
23+
private final Driver driver;
24+
25+
/**
26+
* Constructs a new DriverWrapper with the specified driver.
27+
*
28+
* @param driver the driver to wrap, must not be null
29+
* @throws NullPointerException if driver is null
30+
*/
31+
public DriverWrapper(Driver driver) {
32+
this.driver = Objects.requireNonNull(driver);
33+
}
34+
35+
/**
36+
* {@inheritDoc}
37+
*
38+
* <p>Executes with the driver's class loader context.
39+
*/
40+
@Override
41+
public Connection connect(String url, Properties info) throws SQLException {
42+
return execute(() -> driver.connect(url, info));
43+
}
44+
45+
/**
46+
* {@inheritDoc}
47+
*
48+
* <p>Executes with the driver's class loader context.
49+
*/
50+
@Override
51+
public boolean acceptsURL(String url) throws SQLException {
52+
return execute(() -> driver.acceptsURL(url));
53+
}
54+
55+
/**
56+
* {@inheritDoc}
57+
*
58+
* <p>Executes with the driver's class loader context.
59+
*/
60+
@Override
61+
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
62+
return execute(() -> driver.getPropertyInfo(url, info));
63+
}
64+
65+
/**
66+
* {@inheritDoc}
67+
*
68+
* <p>Executes with the driver's class loader context.
69+
*/
70+
@Override
71+
public int getMajorVersion() {
72+
return execute(driver::getMajorVersion);
73+
}
74+
75+
/**
76+
* {@inheritDoc}
77+
*
78+
* <p>Executes with the driver's class loader context.
79+
*/
80+
@Override
81+
public int getMinorVersion() {
82+
return execute(driver::getMinorVersion);
83+
}
84+
85+
/**
86+
* {@inheritDoc}
87+
*
88+
* <p>Executes with the driver's class loader context.
89+
*/
90+
@Override
91+
public boolean jdbcCompliant() {
92+
return execute(driver::jdbcCompliant);
93+
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*
98+
* <p>Executes with the driver's class loader context.
99+
*/
100+
@Override
101+
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
102+
return execute(driver::getParentLogger);
103+
}
104+
105+
/**
106+
* Executes an operation with the driver's class loader as the context class loader.
107+
*
108+
* <p>This method temporarily switches the thread's context class loader to the driver's class
109+
* loader, executes the operation, and then restores the original class loader. This ensures that
110+
* the driver can properly load its internal classes and resources.
111+
*
112+
* @param <R> the return type of the executable
113+
* @param <TH> the type of exception that may be thrown
114+
* @param executable the operation to execute
115+
* @return the result of the executable operation
116+
* @throws TH if the executable operation throws an exception
117+
*/
118+
private <R, TH extends Exception> R execute(Executable<R, TH> executable) throws TH {
119+
var classLoader = Thread.currentThread().getContextClassLoader();
120+
Thread.currentThread().setContextClassLoader(driver.getClass().getClassLoader());
121+
try {
122+
return executable.execute();
123+
} finally {
124+
Thread.currentThread().setContextClassLoader(classLoader);
125+
}
126+
}
127+
128+
/**
129+
* Represents an executable operation that can return a result and throw an exception.
130+
*
131+
* @param <R> the return type
132+
* @param <TH> the type of exception that may be thrown
133+
*/
134+
private interface Executable<R, TH extends Exception> {
135+
/**
136+
* Executes the operation.
137+
*
138+
* @return the result of the operation
139+
* @throws TH if an error occurs during execution
140+
*/
141+
R execute() throws TH;
142+
}
143+
}

codegen/src/main/java/org/seasar/doma/gradle/codegen/util/JdbcUtil.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public final class JdbcUtil {
1919
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JdbcUtil.class);
2020

2121
protected static final Pattern jdbcUrlPattern =
22-
Pattern.compile("jdbc:(?:tc:)?([^:]+):(([^:]+)?:)?");
22+
Pattern.compile("jdbc:(tc:)?([^:]+):(([^:]+)?:)?");
2323

2424
public static Connection getConnection(DataSource dataSource) {
2525
try {
@@ -79,7 +79,7 @@ public static String inferDialectName(String url) {
7979
return match(
8080
url,
8181
names -> {
82-
switch (names.getFirst()) {
82+
switch (names.second()) {
8383
case "h2":
8484
return "h2";
8585
case "hsqldb":
@@ -107,15 +107,18 @@ public static String inferDriverClassName(String url) {
107107
return match(
108108
url,
109109
names -> {
110-
switch (names.getFirst()) {
110+
if ("tc:".equals(names.first())) {
111+
return "org.testcontainers.jdbc.ContainerDatabaseDriver";
112+
}
113+
switch (names.second()) {
111114
case "h2":
112115
return "org.h2.Driver";
113116
case "hsqldb":
114117
return "org.hsqldb.jdbc.JDBCDriver";
115118
case "sqlite":
116119
return "org.sqlite.JDBC";
117120
case "mysql":
118-
if ("aws".equals(names.getSecond())) {
121+
if ("aws".equals(names.third())) {
119122
return "software.aws.rds.jdbc.mysql.Driver";
120123
}
121124
return "com.mysql.cj.jdbc.Driver";
@@ -135,16 +138,19 @@ public static String inferDriverClassName(String url) {
135138
});
136139
}
137140

138-
protected static <R> R match(String url, Function<Pair<String, String>, R> mapper) {
141+
protected static <R> R match(String url, Function<Triple, R> mapper) {
139142
if (url == null) {
140143
throw new CodeGenNullPointerException("url");
141144
}
142145
Matcher matcher = jdbcUrlPattern.matcher(url);
143146
if (matcher.lookingAt()) {
144147
String first = matcher.group(1);
145-
String second = matcher.group(3);
146-
return mapper.apply(new Pair<>(first, second));
148+
String second = matcher.group(2);
149+
String third = matcher.group(4);
150+
return mapper.apply(new Triple(first, second, third));
147151
}
148152
return null;
149153
}
154+
155+
protected record Triple(String first, String second, String third) {}
150156
}

codegen/src/test/java/org/seasar/doma/gradle/codegen/util/JdbcUtilTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,36 @@ public void testInferDialectName_testcontainers_db2() throws Exception {
116116
@Test
117117
public void testInferDriverClassName_testcontainers_postgresql() throws Exception {
118118
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:postgresql:13:///test");
119-
assertEquals("org.postgresql.Driver", driverClassName);
119+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
120120
}
121121

122122
@Test
123123
public void testInferDriverClassName_testcontainers_mysql() throws Exception {
124124
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:mysql:8:///test");
125-
assertEquals("com.mysql.cj.jdbc.Driver", driverClassName);
125+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
126126
}
127127

128128
@Test
129129
public void testInferDriverClassName_testcontainers_mariadb() throws Exception {
130130
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:mariadb:10.5:///test");
131-
assertEquals("org.mariadb.jdbc.Driver", driverClassName);
131+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
132132
}
133133

134134
@Test
135135
public void testInferDriverClassName_testcontainers_oracle() throws Exception {
136136
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:oracle:21c:///test");
137-
assertEquals("oracle.jdbc.driver.OracleDriver", driverClassName);
137+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
138138
}
139139

140140
@Test
141141
public void testInferDriverClassName_testcontainers_sqlserver() throws Exception {
142142
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:sqlserver:2019:///test");
143-
assertEquals("com.microsoft.sqlserver.jdbc.SQLServerDriver", driverClassName);
143+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
144144
}
145145

146146
@Test
147147
public void testInferDriverClassName_testcontainers_db2() throws Exception {
148148
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:db2:11.5:///test");
149-
assertEquals("com.ibm.db2.jcc.DB2Driver", driverClassName);
149+
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
150150
}
151151
}

0 commit comments

Comments
 (0)