Skip to content

Commit 5ac6fbd

Browse files
nakamura-toclaude
andcommitted
Fix class loader issues with JDBC drivers
Wrap JDBC drivers to ensure proper class loader context switching when executing driver operations. This prevents ClassNotFoundException and other class loading issues when the driver and plugin are loaded by different class loaders. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent acbf3e6 commit 5ac6fbd

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

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+
}

0 commit comments

Comments
 (0)