Skip to content

Commit 7690b2c

Browse files
graemerocherCopilotradovanradicmicronaut-buildsdelamo
authored
Add MyBatis integration module (#1921)
* Apply PR thread fixes for MyBatis factory, test, and docs wording Agent-Logs-Url: https://github.com/micronaut-projects/micronaut-sql/sessions/d0df09e1-b61d-443d-b470-4db856f6bab1 Co-authored-by: graemerocher <66626+graemerocher@users.noreply.github.com> * Reapply MyBatis module and docs entries after base merge Co-authored-by: graemerocher <66626+graemerocher@users.noreply.github.com> * correct * Update to micronaut-core 5.0.3 (#2023) * [skip ci] Release v7.0.2 * chore: Bump version to 7.0.3-SNAPSHOT * add logback.xml to mybatis * binaryCompatibility after 7.1.0 * Fix MyBatis factory bean resolution Update MyBatis configuration creation to resolve the datasource-specific TransactionFactory through BeanLocator, falling back to JdbcTransactionFactory when none is registered. Also type the SqlSessionManager bean explicitly so it is exposed as SqlSessionManager. * Update * guides example * fix docs * Move mappers to example.micronaut.mappers * fix binary compatibility * remove duplicated * use kotlin DSL * Update mybatis/src/main/java/io/micronaut/configuration/mybatis/package-info.java --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: graemerocher <66626+graemerocher@users.noreply.github.com> Co-authored-by: Radovan Radic <radicr@gmail.com> Co-authored-by: micronaut-build <micronaut-build-account@grails.org> Co-authored-by: Sergio del Amo <sergio.delamo@softamo.com> Co-authored-by: Sergio del Amo <sergio.del.amo.caballero@oracle.com>
1 parent 87c916d commit 7690b2c

78 files changed

Lines changed: 3446 additions & 8 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

buildSrc/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ repositories {
88
}
99

1010
dependencies {
11-
implementation libs.gradle.micronaut
11+
implementation(libs.gradle.micronaut)
12+
implementation(providers.gradleProperty("micronaut-build-version").map { "io.micronaut.build.internal:micronaut-kotlin-build-plugins:${it}" }.get())
13+
1214
}

gradle/libs.versions.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ managed-jooq = "3.21.6"
2525
managed-hibernate = "7.4.4.Final"
2626
managed-hibernate-reactive = "4.5.1.Final"
2727
managed-jasync = "2.2.4"
28+
managed-mybatis = "3.5.19"
2829
managed-jdbi = "3.54.0"
2930

3031
# JDBC Drivers
@@ -127,6 +128,10 @@ managed-ucp17 = { module = "com.oracle.database.jdbc:ucp17", version.ref = "mana
127128
# UCP
128129

129130
dms = { module = "com.oracle.database.observability:dms", version.ref = "managed-dms" }
131+
132+
# MyBatis
133+
managed-mybatis = { module = "org.mybatis:mybatis", version.ref = "managed-mybatis" }
134+
130135
# JDBC drivers
131136

132137
managed-postgresql = { module = "org.postgresql:postgresql", version.ref = "managed-postgres-driver" }

jdbc-unpooled/build.gradle

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ plugins {
22
id 'io.micronaut.build.internal.sql-module'
33
}
44

5-
micronautBuild {
6-
binaryCompatibility {
7-
enabledAfter("7.0.0")
8-
}
9-
}
10-
115
dependencies {
126
api projects.micronautJdbc
137
api(mn.micronaut.inject)

mybatis/build.gradle.kts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
plugins {
2+
id("io.micronaut.build.internal.sql-module")
3+
}
4+
dependencies {
5+
api(projects.micronautJdbc)
6+
api(mn.micronaut.context)
7+
api(mn.micronaut.inject)
8+
api(libs.managed.mybatis)
9+
testAnnotationProcessor(mn.micronaut.inject.java)
10+
testImplementation(mnTest.micronaut.test.junit5)
11+
testRuntimeOnly(projects.micronautJdbcHikari)
12+
testRuntimeOnly(libs.managed.h2)
13+
testRuntimeOnly(mnTest.junit.jupiter.engine)
14+
testRuntimeOnly(mnTest.junit.platform.launcher)
15+
}
16+
17+
tasks.withType<Test> {
18+
useJUnitPlatform()
19+
}
20+
21+
micronautBuild {
22+
binaryCompatibility {
23+
enabledAfter("7.1.0")
24+
}
25+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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 io.micronaut.configuration.mybatis;
17+
18+
import org.apache.ibatis.session.Configuration;
19+
20+
/**
21+
* Allows custom actions to be performed on a MyBatis {@link Configuration}.
22+
* Customizers may be annotated with {@link jakarta.inject.Named} to target a specific datasource.
23+
*
24+
* @author Graeme Rocher
25+
* @since 7.1.0
26+
*/
27+
public interface MyBatisConfigurationCustomizer {
28+
29+
/**
30+
* Performs custom configuration operations on the given MyBatis configuration.
31+
*
32+
* @param configuration The configuration to customize
33+
*/
34+
void customize(Configuration configuration);
35+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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 io.micronaut.configuration.mybatis;
17+
18+
import io.micronaut.context.BeanLocator;
19+
import io.micronaut.context.annotation.Bean;
20+
import io.micronaut.context.annotation.EachBean;
21+
import io.micronaut.context.annotation.Factory;
22+
import io.micronaut.context.annotation.Parameter;
23+
import io.micronaut.inject.qualifiers.Qualifiers;
24+
import org.apache.ibatis.mapping.Environment;
25+
import org.apache.ibatis.session.Configuration;
26+
import org.apache.ibatis.session.SqlSessionFactory;
27+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
28+
import org.apache.ibatis.session.SqlSessionManager;
29+
import org.apache.ibatis.transaction.TransactionFactory;
30+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
31+
import org.jspecify.annotations.Nullable;
32+
33+
import javax.sql.DataSource;
34+
35+
/**
36+
* Configures MyBatis beans from Micronaut {@link DataSource} beans.
37+
*
38+
* @author Graeme Rocher
39+
* @since 7.1.0
40+
*/
41+
@Factory
42+
public class MyBatisFactory {
43+
44+
/**
45+
* Creates the MyBatis {@link Configuration} for a datasource.
46+
*
47+
* @param name The datasource name
48+
* @param dataSource The datasource
49+
* @param beanLocator The bean locator
50+
* @param transactionFactory Transaction Factory
51+
* @return The MyBatis configuration
52+
*/
53+
@EachBean(DataSource.class)
54+
public Configuration myBatisConfiguration(
55+
@Parameter String name,
56+
@Parameter DataSource dataSource,
57+
@Parameter @Nullable TransactionFactory transactionFactory,
58+
BeanLocator beanLocator) {
59+
TransactionFactory resolvedTransactionFactory = transactionFactory != null ? transactionFactory : new JdbcTransactionFactory();
60+
Configuration configuration = new Configuration(new Environment(name, resolvedTransactionFactory, dataSource));
61+
for (MyBatisConfigurationCustomizer customizer : beanLocator.getBeansOfType(
62+
MyBatisConfigurationCustomizer.class,
63+
Qualifiers.byName(name)
64+
)) {
65+
customizer.customize(configuration);
66+
}
67+
return configuration;
68+
}
69+
70+
/**
71+
* Creates the MyBatis {@link SqlSessionFactory} for a datasource.
72+
*
73+
* @param configuration The MyBatis configuration
74+
* @return The session factory
75+
*/
76+
@EachBean(Configuration.class)
77+
public SqlSessionFactory sqlSessionFactory(Configuration configuration) {
78+
return new SqlSessionFactoryBuilder().build(configuration);
79+
}
80+
81+
/**
82+
* Creates the thread-safe {@link SqlSessionManager} for a datasource.
83+
*
84+
* @param sqlSessionFactory The session factory
85+
* @return The session manager
86+
*/
87+
@EachBean(SqlSessionFactory.class)
88+
@Bean(typed = SqlSessionManager.class)
89+
public SqlSessionManager sqlSessionManager(SqlSessionFactory sqlSessionFactory) {
90+
return SqlSessionManager.newInstance(sqlSessionFactory);
91+
}
92+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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+
/**
17+
* Configuration for MyBatis integration.
18+
*
19+
* @author Graeme Rocher
20+
* @since 7.1.0
21+
*/
22+
@Configuration
23+
@Requires(classes = SqlSessionFactory.class)
24+
package io.micronaut.configuration.mybatis;
25+
26+
import io.micronaut.context.annotation.Configuration;
27+
import io.micronaut.context.annotation.Requires;
28+
import org.apache.ibatis.session.SqlSessionFactory;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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 io.micronaut.configuration.mybatis;
17+
18+
import io.micronaut.context.ApplicationContext;
19+
import io.micronaut.inject.qualifiers.Qualifiers;
20+
import io.micronaut.configuration.mybatis.support.TestTransactionFactory;
21+
import org.apache.ibatis.session.Configuration;
22+
import org.apache.ibatis.session.SqlSessionFactory;
23+
import org.apache.ibatis.session.SqlSessionManager;
24+
import org.apache.ibatis.transaction.TransactionFactory;
25+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
26+
import org.junit.jupiter.api.Test;
27+
28+
import javax.sql.DataSource;
29+
import java.sql.Connection;
30+
import java.sql.Statement;
31+
import java.util.Map;
32+
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertFalse;
35+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
36+
import static org.junit.jupiter.api.Assertions.assertNotNull;
37+
import static org.junit.jupiter.api.Assertions.assertSame;
38+
import static org.junit.jupiter.api.Assertions.assertTrue;
39+
40+
class MyBatisFactoryTest {
41+
42+
@Test
43+
void doesNotCreateBeansWithoutDataSource() {
44+
try (ApplicationContext applicationContext = ApplicationContext.builder("test").start()) {
45+
assertFalse(applicationContext.containsBean(DataSource.class));
46+
assertFalse(applicationContext.containsBean(Configuration.class));
47+
assertFalse(applicationContext.containsBean(SqlSessionFactory.class));
48+
assertFalse(applicationContext.containsBean(SqlSessionManager.class));
49+
}
50+
}
51+
52+
@Test
53+
void createsMyBatisBeansForTheDefaultDataSource() throws Exception {
54+
try (ApplicationContext applicationContext = ApplicationContext.builder("test")
55+
.properties(Map.of("datasources.default", Map.of(), "spec.name", "MyBatisFactoryTest"))
56+
.start()) {
57+
assertTrue(applicationContext.containsBean(DataSource.class));
58+
assertTrue(applicationContext.containsBean(Configuration.class));
59+
assertTrue(applicationContext.containsBean(SqlSessionFactory.class));
60+
assertTrue(applicationContext.containsBean(SqlSessionManager.class));
61+
62+
Configuration configuration = applicationContext.getBean(Configuration.class);
63+
assertTrue(configuration.hasMapper(TestMapper.class));
64+
assertTrue(configuration.isMapUnderscoreToCamelCase());
65+
assertInstanceOf(TestTransactionFactory.class, configuration.getEnvironment().getTransactionFactory());
66+
assertSame(
67+
applicationContext.getBean(TransactionFactory.class, Qualifiers.byName("default")),
68+
configuration.getEnvironment().getTransactionFactory()
69+
);
70+
71+
SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
72+
assertNotNull(sqlSessionFactory.getConfiguration().getEnvironment());
73+
assertEquals("default", sqlSessionFactory.getConfiguration().getEnvironment().getId());
74+
75+
initializeSchema(applicationContext.getBean(DataSource.class));
76+
77+
SqlSessionManager sqlSessionManager = applicationContext.getBean(SqlSessionManager.class);
78+
TestMapper mapper = sqlSessionManager.getMapper(TestMapper.class);
79+
assertEquals(1, mapper.count());
80+
mapper.insert();
81+
assertEquals(2, mapper.count());
82+
}
83+
}
84+
85+
@Test
86+
void ignoresWronglyQualifiedBeansAndFallsBackToJdbcTransactions() {
87+
try (ApplicationContext applicationContext = ApplicationContext.builder("test")
88+
.properties(Map.of("datasources.default2", Map.of()))
89+
.start()) {
90+
assertTrue(applicationContext.containsBean(DataSource.class));
91+
assertTrue(applicationContext.containsBean(Configuration.class));
92+
assertTrue(applicationContext.containsBean(SqlSessionFactory.class));
93+
assertTrue(applicationContext.containsBean(SqlSessionManager.class));
94+
95+
Configuration configuration = applicationContext.getBean(Configuration.class);
96+
assertFalse(configuration.hasMapper(TestMapper.class));
97+
assertInstanceOf(JdbcTransactionFactory.class, configuration.getEnvironment().getTransactionFactory());
98+
}
99+
}
100+
101+
private static void initializeSchema(DataSource dataSource) throws Exception {
102+
try (Connection connection = dataSource.getConnection();
103+
Statement statement = connection.createStatement()) {
104+
statement.execute("CREATE TABLE foo(id INT PRIMARY KEY)");
105+
statement.execute("INSERT INTO foo(id) VALUES(1)");
106+
}
107+
}
108+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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 io.micronaut.configuration.mybatis;
17+
18+
import org.apache.ibatis.annotations.Insert;
19+
import org.apache.ibatis.annotations.Select;
20+
21+
interface TestMapper {
22+
23+
@Select("SELECT COUNT(*) FROM foo")
24+
int count();
25+
26+
@Insert("INSERT INTO foo(id) VALUES(2)")
27+
void insert();
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2017-2026 original authors
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+
* https://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 io.micronaut.configuration.mybatis;
17+
18+
import jakarta.inject.Named;
19+
import jakarta.inject.Singleton;
20+
import org.apache.ibatis.session.Configuration;
21+
22+
@Named("default")
23+
@Singleton
24+
final class TestMyBatisConfigurationCustomizer implements MyBatisConfigurationCustomizer {
25+
26+
@Override
27+
public void customize(Configuration configuration) {
28+
configuration.addMapper(TestMapper.class);
29+
configuration.setMapUnderscoreToCamelCase(true);
30+
}
31+
}

0 commit comments

Comments
 (0)