Skip to content

Commit 943bf38

Browse files
authored
Merge pull request #463 from smallfour/master
mybatis support mybatis-spring
2 parents eada085 + e8578e8 commit 943bf38

File tree

8 files changed

+275
-5
lines changed

8 files changed

+275
-5
lines changed

plugin/hotswap-agent-mybatis-plugin/pom.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
<properties>
1616
<org.mybatis.version>3.5.9</org.mybatis.version>
1717
<jaxb.version>2.3.0</jaxb.version>
18+
<org.mybatis-spring.version>2.0.7</org.mybatis-spring.version>
19+
<org.springframework.version>5.3.19</org.springframework.version>
1820
</properties>
1921

2022

@@ -31,6 +33,33 @@
3133
<version>${org.mybatis.version}</version>
3234
</dependency>
3335

36+
<dependency>
37+
<groupId>org.mybatis</groupId>
38+
<artifactId>mybatis-spring</artifactId>
39+
<version>${org.mybatis-spring.version}</version>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>org.springframework</groupId>
44+
<artifactId>spring-test</artifactId>
45+
<version>${org.springframework.version}</version>
46+
<scope>test</scope>
47+
</dependency>
48+
49+
<dependency>
50+
<groupId>org.springframework</groupId>
51+
<artifactId>spring-context</artifactId>
52+
<version>${org.springframework.version}</version>
53+
<scope>test</scope>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.springframework</groupId>
58+
<artifactId>spring-jdbc</artifactId>
59+
<version>${org.springframework.version}</version>
60+
<scope>test</scope>
61+
</dependency>
62+
3463
<dependency>
3564
<groupId>org.hsqldb</groupId>
3665
<artifactId>hsqldb</artifactId>

plugin/hotswap-agent-mybatis-plugin/src/main/java/org/hotswap/agent/plugin/mybatis/MyBatisRefreshCommands.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.hotswap.agent.logging.AgentLogger;
2222
import org.hotswap.agent.plugin.mybatis.proxy.ConfigurationProxy;
23+
import org.hotswap.agent.plugin.mybatis.proxy.SpringMybatisConfigurationProxy;
2324

2425

2526
/**
@@ -42,6 +43,7 @@ public class MyBatisRefreshCommands {
4243
public static void reloadConfiguration() {
4344
LOGGER.debug("Refreshing MyBatis configuration.");
4445
ConfigurationProxy.refreshProxiedConfigurations();
46+
SpringMybatisConfigurationProxy.refreshProxiedConfigurations();
4547
LOGGER.reload("MyBatis configuration refreshed.");
4648
reloadFlag = false;
4749
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.hotswap.agent.plugin.mybatis.proxy;
2+
3+
import org.apache.ibatis.session.Configuration;
4+
import org.hotswap.agent.javassist.util.proxy.MethodHandler;
5+
import org.hotswap.agent.javassist.util.proxy.ProxyFactory;
6+
import org.hotswap.agent.util.ReflectionHelper;
7+
8+
import java.lang.reflect.Method;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
12+
public class SpringMybatisConfigurationProxy {
13+
14+
private static Map<Object, SpringMybatisConfigurationProxy> proxiedConfigurations = new HashMap<>();
15+
16+
public SpringMybatisConfigurationProxy(Object sqlSessionFactoryBean) {
17+
this.sqlSessionFactoryBean = sqlSessionFactoryBean;
18+
}
19+
20+
public static SpringMybatisConfigurationProxy getWrapper(Object sqlSessionFactoryBean) {
21+
if (!proxiedConfigurations.containsKey(sqlSessionFactoryBean)) {
22+
proxiedConfigurations.put(sqlSessionFactoryBean, new SpringMybatisConfigurationProxy(sqlSessionFactoryBean));
23+
}
24+
return proxiedConfigurations.get(sqlSessionFactoryBean);
25+
}
26+
27+
public static void refreshProxiedConfigurations() {
28+
for (SpringMybatisConfigurationProxy wrapper : proxiedConfigurations.values())
29+
try {
30+
wrapper.refreshProxiedConfiguration();
31+
} catch (Exception e) {
32+
e.printStackTrace();
33+
}
34+
}
35+
36+
public void refreshProxiedConfiguration() {
37+
Object newSqlSessionFactory = ReflectionHelper.invoke(this.sqlSessionFactoryBean, "buildSqlSessionFactory");
38+
this.configuration = (Configuration) ReflectionHelper.get(newSqlSessionFactory, "configuration");
39+
}
40+
41+
private Object sqlSessionFactoryBean;
42+
private Configuration configuration;
43+
private Configuration proxyInstance;
44+
45+
public Configuration proxy(Configuration origConfiguration) {
46+
this.configuration = origConfiguration;
47+
if (proxyInstance == null) {
48+
ProxyFactory factory = new ProxyFactory();
49+
factory.setSuperclass(Configuration.class);
50+
51+
MethodHandler handler = new MethodHandler() {
52+
@Override
53+
public Object invoke(Object self, Method overridden, Method forwarder,
54+
Object[] args) throws Throwable {
55+
return overridden.invoke(configuration, args);
56+
}
57+
};
58+
59+
try {
60+
proxyInstance = (Configuration) factory.create(new Class[0], null, handler);
61+
} catch (Exception e) {
62+
throw new Error("Unable instantiate Configuration proxy", e);
63+
}
64+
}
65+
return proxyInstance;
66+
}
67+
68+
69+
70+
}

plugin/hotswap-agent-mybatis-plugin/src/main/java/org/hotswap/agent/plugin/mybatis/transformers/MyBatisTransformers.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.hotswap.agent.logging.AgentLogger;
3232
import org.hotswap.agent.plugin.mybatis.MyBatisPlugin;
3333
import org.hotswap.agent.plugin.mybatis.proxy.ConfigurationProxy;
34+
import org.hotswap.agent.plugin.mybatis.proxy.SpringMybatisConfigurationProxy;
3435
import org.hotswap.agent.util.PluginManagerInvoker;
3536

3637
/**
@@ -46,6 +47,11 @@ public class MyBatisTransformers {
4647
public static final String REFRESH_DOCUMENT_METHOD = "$$ha$refreshDocument";
4748
public static final String REFRESH_METHOD = "$$ha$refresh";
4849

50+
private static final String INITIALIZED_FIELD = "$$ha$initialized";
51+
private static final String FACTORYBEAN_FIELD = "$$ha$factoryBean";
52+
public static final String FACTORYBEAN_SET_METHOD = "$$ha$setFactoryBean";
53+
public static final String CONFIGURATION_PROXY_METHOD = "$$ha$proxySqlSessionFactoryConfiguration";
54+
4955
@OnClassLoadEvent(classNameRegexp = "org.apache.ibatis.parsing.XPathParser")
5056
public static void patchXPathParser(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
5157
CtClass stringClass = classPool.get("java.lang.String");
@@ -123,4 +129,58 @@ public static void patchXMLMapperBuilder(CtClass ctClass, ClassPool classPool) t
123129
constructor.insertAfter(src.toString());
124130
LOGGER.debug("org.apache.ibatis.builder.xml.XMLMapperBuilder patched.");
125131
}
132+
133+
@OnClassLoadEvent(classNameRegexp = "org.apache.ibatis.session.SqlSessionFactoryBuilder")
134+
public static void patchSqlSessionFactoryBuilder(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
135+
// add $$ha$factoryBean field
136+
CtClass objClass = classPool.get("java.lang.Object");
137+
CtField factoryBeanField = new CtField(objClass, FACTORYBEAN_FIELD, ctClass);
138+
ctClass.addField(factoryBeanField);
139+
140+
CtMethod setMethod = CtNewMethod.make(
141+
"public void " + FACTORYBEAN_SET_METHOD + "(Object factoryBean) {" +
142+
"this." + FACTORYBEAN_FIELD + " = factoryBean;" +
143+
"}", ctClass);
144+
ctClass.addMethod(setMethod);
145+
146+
CtMethod buildMethod = ctClass.getDeclaredMethod("build",
147+
new CtClass[] {classPool.get("org.apache.ibatis.session.Configuration")});
148+
buildMethod.insertBefore("{" +
149+
"if (this." + FACTORYBEAN_FIELD + " != null) {" +
150+
"config = " + SqlSessionFactoryBeanCaller.class.getName() + ".proxyConfiguration(this." + FACTORYBEAN_FIELD + ", config);" +
151+
"}" +
152+
"}"
153+
);
154+
LOGGER.debug("org.apache.ibatis.session.SqlSessionFactoryBuilder patched.");
155+
}
156+
157+
@OnClassLoadEvent(classNameRegexp = "org.mybatis.spring.SqlSessionFactoryBean")
158+
public static void patchSqlSessionFactoryBean(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
159+
// add $$ha$initialized field
160+
CtClass booleanClass = classPool.get(boolean.class.getName());
161+
CtField sourceFileField = new CtField(booleanClass, INITIALIZED_FIELD, ctClass);
162+
ctClass.addField(sourceFileField);
163+
164+
CtMethod method = ctClass.getDeclaredMethod("afterPropertiesSet");
165+
method.insertAfter("{" +
166+
"this." + INITIALIZED_FIELD + " = true;" +
167+
"}"
168+
);
169+
170+
CtConstructor constructor = ctClass.getDeclaredConstructor(new CtClass[] {});
171+
constructor.insertAfter("{" +
172+
SqlSessionFactoryBeanCaller.class.getName() + ".setFactoryBean(this.sqlSessionFactoryBuilder, this);" +
173+
"}");
174+
175+
CtMethod proxyMethod = CtNewMethod.make(
176+
"public org.apache.ibatis.session.Configuration " + CONFIGURATION_PROXY_METHOD + "(org.apache.ibatis.session.Configuration configuration) {" +
177+
"if(this." + INITIALIZED_FIELD + ") {" +
178+
"return configuration;" +
179+
"} else {" +
180+
"return " + SpringMybatisConfigurationProxy.class.getName() + ".getWrapper(this).proxy(configuration);" +
181+
"}" +
182+
"}", ctClass);
183+
ctClass.addMethod(proxyMethod);
184+
LOGGER.debug("org.mybatis.spring.SqlSessionFactoryBean patched.");
185+
}
126186
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.hotswap.agent.plugin.mybatis.transformers;
2+
3+
import org.apache.ibatis.session.Configuration;
4+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
5+
import org.hotswap.agent.util.ReflectionHelper;
6+
import org.mybatis.spring.SqlSessionFactoryBean;
7+
8+
public class SqlSessionFactoryBeanCaller {
9+
10+
public static Configuration proxyConfiguration(Object factoryBean, Configuration configuration) {
11+
return (Configuration) ReflectionHelper.invoke(factoryBean, SqlSessionFactoryBean.class,
12+
MyBatisTransformers.CONFIGURATION_PROXY_METHOD, new Class[] {Configuration.class}, configuration);
13+
}
14+
15+
public static void setFactoryBean(SqlSessionFactoryBuilder builder, Object factoryBean) {
16+
ReflectionHelper.invoke(builder, SqlSessionFactoryBuilder.class,
17+
MyBatisTransformers.FACTORYBEAN_SET_METHOD, new Class[] {Object.class}, factoryBean);
18+
}
19+
}

plugin/hotswap-agent-mybatis-plugin/src/test/java/org/hotswap/agent/plugin/mybatis/MyBatisPluginTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.ibatis.session.SqlSessionFactory;
3838
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
3939
import org.hotswap.agent.util.test.WaitHelper;
40+
import org.junit.AfterClass;
4041
import org.junit.BeforeClass;
4142
import org.junit.Test;
4243

@@ -49,8 +50,6 @@ public static void setup() throws Exception {
4950
// create an SqlSessionFactory
5051
File f = Resources.getResourceAsFile("org/hotswap/agent/plugin/mybatis/Mapper1.xml");
5152
Files.copy(f.toPath(), f.toPath().getParent().resolve("Mapper.xml"));
52-
File tmp = Resources.getResourceAsFile("org/hotswap/agent/plugin/mybatis/Mapper.xml");
53-
tmp.deleteOnExit();
5453
try (Reader reader = Resources.getResourceAsReader("org/hotswap/agent/plugin/mybatis/mybatis-config.xml")) {
5554
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
5655
}
@@ -60,7 +59,13 @@ public static void setup() throws Exception {
6059
"org/hotswap/agent/plugin/mybatis/CreateDB.sql");
6160
}
6261

63-
private static void runScript(DataSource ds, String resource) throws IOException, SQLException {
62+
@AfterClass
63+
public static void tearDown() throws Exception {
64+
File tmp = Resources.getResourceAsFile("org/hotswap/agent/plugin/mybatis/Mapper.xml");
65+
tmp.delete();
66+
}
67+
68+
protected static void runScript(DataSource ds, String resource) throws IOException, SQLException {
6469
try (Connection connection = ds.getConnection()) {
6570
ScriptRunner runner = new ScriptRunner(connection);
6671
runner.setAutoCommit(true);
@@ -93,7 +98,7 @@ public void testUserFromAnnotation() throws Exception {
9398
}
9499
}
95100

96-
private void swapMapper(String mapperNew) throws Exception {
101+
protected static void swapMapper(String mapperNew) throws Exception {
97102
MyBatisRefreshCommands.reloadFlag = true;
98103
File f = Resources.getResourceAsFile(mapperNew);
99104
Files.copy(f.toPath(), f.toPath().getParent().resolve("Mapper.xml"), StandardCopyOption.REPLACE_EXISTING);
@@ -102,7 +107,7 @@ private void swapMapper(String mapperNew) throws Exception {
102107
public boolean result() throws Exception {
103108
return !MyBatisRefreshCommands.reloadFlag;
104109
}
105-
}, 2000 )); // Repository is regenerated within 2*DeltaSpikePlugin.WAIT_ON_REDEFINE
110+
}, 4000 )); // Repository is regenerated within 2*DeltaSpikePlugin.WAIT_ON_REDEFINE
106111

107112
// TODO do not know why sleep is needed, maybe a separate thread in owb refresh?
108113
Thread.sleep(100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.hotswap.agent.plugin.mybatis;
2+
3+
import org.apache.ibatis.io.Resources;
4+
import org.junit.AfterClass;
5+
import org.junit.Before;
6+
import org.junit.BeforeClass;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.context.ApplicationContext;
11+
import org.springframework.test.context.ContextConfiguration;
12+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13+
14+
import javax.sql.DataSource;
15+
import java.io.File;
16+
import java.nio.file.Files;
17+
18+
import static org.junit.Assert.assertEquals;
19+
20+
@RunWith(SpringJUnit4ClassRunner.class)
21+
@ContextConfiguration(locations = { "classpath:spring-mybatis.xml" })
22+
public class MybatisSpringTest {
23+
24+
@Autowired
25+
ApplicationContext applicationContext;
26+
27+
@Autowired
28+
Mapper mapper;
29+
30+
@Autowired
31+
DataSource dataSource;
32+
33+
@BeforeClass
34+
public static void setup() throws Exception {
35+
File f = Resources.getResourceAsFile("org/hotswap/agent/plugin/mybatis/Mapper1.xml");
36+
Files.copy(f.toPath(), f.toPath().getParent().resolve("Mapper.xml"));
37+
}
38+
39+
@AfterClass
40+
public static void tearDown() throws Exception {
41+
File tmp = Resources.getResourceAsFile("org/hotswap/agent/plugin/mybatis/Mapper.xml");
42+
tmp.delete();
43+
}
44+
45+
@Before
46+
public void init() throws Exception {
47+
MyBatisPluginTest.runScript(dataSource, "org/hotswap/agent/plugin/mybatis/CreateDB.sql");
48+
}
49+
50+
@Test
51+
public void testUserFromAnnotation() throws Exception {
52+
User user = mapper.getUserXML("User1");
53+
assertEquals("User1", user.getName1());
54+
55+
MyBatisPluginTest.swapMapper("org/hotswap/agent/plugin/mybatis/Mapper2.xml");
56+
user = mapper.getUserXML("User1");
57+
assertEquals("User2", user.getName1());
58+
}
59+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5+
6+
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
7+
<constructor-arg ref="sqlSessionFactory"/>
8+
</bean>
9+
10+
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
11+
<property name="dataSource" ref="dataSource" />
12+
<property name="mapperLocations" value="org/hotswap/agent/plugin/mybatis/Mapper.xml"/>
13+
</bean>
14+
15+
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource" >
16+
<property name="username" value="sa" />
17+
<property name="driver" ref="hSqldriver" />
18+
<property name="url" value="jdbc:hsqldb:mem:maptypehandler" />
19+
</bean>
20+
<bean id="hSqldriver" class="org.hsqldb.jdbcDriver"/>
21+
22+
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
23+
<property name="basePackage" value="org.hotswap.agent.plugin.mybatis"/>
24+
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
25+
</bean>
26+
</beans>

0 commit comments

Comments
 (0)