Skip to content

Commit 0a22142

Browse files
✨ Support annotation based on MyBatis for Spring Boot 3 Native
#994
1 parent 04b2032 commit 0a22142

File tree

23 files changed

+1164
-1
lines changed

23 files changed

+1164
-1
lines changed

.github/workflows/native.yaml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#
2+
# Copyright 2015-2024 the original author or 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+
# 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+
17+
name: Spring Boot Native Support Samples
18+
19+
on: [ push, pull_request ]
20+
21+
jobs:
22+
test-ubuntu:
23+
runs-on: ${{ matrix.os }}
24+
strategy:
25+
matrix:
26+
os: [ ubuntu-latest, macOS-latest ]
27+
java: [ 17.0.9, 21.0.2, 22.0.2 ]
28+
fail-fast: false
29+
max-parallel: 5
30+
name: Test GraalVM JDK ${{ matrix.java }}, ${{ matrix.os }}
31+
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- name: Ubuntu Set up JDK
36+
if: ${{ matrix.os == 'ubuntu-latest' }}
37+
run: |
38+
JAVA_HOME=$RUNNER_WORKSPACE/.graalvm
39+
echo $JAVA_HOME
40+
mkdir -p $JAVA_HOME
41+
echo "JAVA_HOME=$JAVA_HOME" >> $GITHUB_ENV
42+
curl -L -o graalvm.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${{ matrix.java }}/graalvm-community-jdk-${{ matrix.java }}_linux-x64_bin.tar.gz
43+
tar -zxvf graalvm.tar.gz -C $JAVA_HOME --strip-components=1
44+
ls -lh $JAVA_HOME
45+
mvn -v
46+
47+
- name: MacOS Set up JDK
48+
if: ${{ matrix.os == 'macOS-latest' }}
49+
run: |
50+
JAVA_HOME=$RUNNER_WORKSPACE/.graalvm
51+
echo $JAVA_HOME
52+
mkdir -p $JAVA_HOME
53+
curl -L -o graalvm.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${{ matrix.java }}/graalvm-community-jdk-${{ matrix.java }}_macos-x64_bin.tar.gz
54+
tar -zxvf graalvm.tar.gz -C $JAVA_HOME --strip-components=1
55+
JAVA_HOME=$RUNNER_WORKSPACE/.graalvm/Contents/Home
56+
echo $JAVA_HOME
57+
echo "JAVA_HOME=$JAVA_HOME" >> $GITHUB_ENV
58+
ls -lh $JAVA_HOME
59+
mvn -v
60+
61+
- name: Ubuntu Prerequisites
62+
if: ${{ matrix.os == 'ubuntu-latest' }}
63+
run: sudo apt-get install build-essential zlib1g-dev
64+
65+
#- name: MacOS Prerequisites
66+
# if: ${{ matrix.os == 'macOS-latest' }}
67+
# run: xcode-select --install
68+
69+
- name: Test with Spring Boot Native Latest
70+
run: ./mvnw -V compile -Pnative native:compile -am -pl mybatis-spring-boot-samples/mybatis-spring-boot-sample-graalvm-annotation
71+
72+
- name: Run Native Latest
73+
run: mybatis-spring-boot-samples/mybatis-spring-boot-sample-graalvm-annotation/target/mybatis-spring-boot-sample-graalvm-annotation
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2015-2024 the original author or 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 org.mybatis.spring.boot.autoconfigure;
17+
18+
import java.lang.reflect.Method;
19+
import java.lang.reflect.ParameterizedType;
20+
import java.lang.reflect.Type;
21+
import java.util.HashSet;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
25+
import java.util.stream.Stream;
26+
27+
import org.apache.ibatis.reflection.TypeParameterResolver;
28+
import org.mybatis.spring.mapper.MapperFactoryBean;
29+
import org.mybatis.spring.mapper.MapperScannerConfigurer;
30+
import org.springframework.aot.hint.MemberCategory;
31+
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.beans.PropertyValue;
33+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
34+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
35+
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
36+
import org.springframework.beans.factory.config.BeanDefinition;
37+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
38+
import org.springframework.beans.factory.support.RegisteredBean;
39+
import org.springframework.util.ReflectionUtils;
40+
41+
/**
42+
* @since 3.0.4
43+
*/
44+
class MyBatisBeanFactoryInitializationAotProcessor
45+
implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter {
46+
47+
private static final Set<Class<?>> EXCLUDE_CLASSES = new HashSet<>();
48+
49+
static {
50+
EXCLUDE_CLASSES.add(MapperScannerConfigurer.class);
51+
}
52+
53+
@Override
54+
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
55+
String[] beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class);
56+
if (beanNames.length == 0) {
57+
return null;
58+
}
59+
return (context, code) -> {
60+
RuntimeHints hints = context.getRuntimeHints();
61+
for (String beanName : beanNames) {
62+
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1));
63+
PropertyValue mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface");
64+
if (mapperInterface != null && mapperInterface.getValue() != null) {
65+
Class<?> mapperInterfaceType = (Class<?>) mapperInterface.getValue();
66+
if (mapperInterfaceType != null) {
67+
registerReflectionTypeIfNecessary(mapperInterfaceType, hints);
68+
hints.proxies().registerJdkProxy(mapperInterfaceType);
69+
registerMapperRelationships(mapperInterfaceType, hints);
70+
}
71+
}
72+
}
73+
};
74+
}
75+
76+
@Override
77+
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
78+
return EXCLUDE_CLASSES.contains(registeredBean.getBeanClass());
79+
}
80+
81+
private void registerMapperRelationships(Class<?> mapperInterfaceType, RuntimeHints hints) {
82+
Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType);
83+
for (Method method : methods) {
84+
if (method.getDeclaringClass() != Object.class) {
85+
ReflectionUtils.makeAccessible(method);
86+
Class<?> returnType = MyBatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method);
87+
registerReflectionTypeIfNecessary(returnType, hints);
88+
MyBatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method)
89+
.forEach(x -> registerReflectionTypeIfNecessary(x, hints));
90+
}
91+
}
92+
}
93+
94+
static class MyBatisMapperTypeUtils {
95+
private MyBatisMapperTypeUtils() {
96+
// NOP
97+
}
98+
99+
static Class<?> resolveReturnClass(Class<?> mapperInterface, Method method) {
100+
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
101+
return typeToClass(resolvedReturnType, method.getReturnType());
102+
}
103+
104+
static Set<Class<?>> resolveParameterClasses(Class<?> mapperInterface, Method method) {
105+
return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface))
106+
.map(x -> typeToClass(x, x instanceof Class ? (Class<?>) x : Object.class)).collect(Collectors.toSet());
107+
}
108+
109+
private static Class<?> typeToClass(Type src, Class<?> fallback) {
110+
Class<?> result = null;
111+
if (src instanceof Class<?>) {
112+
if (((Class<?>) src).isArray()) {
113+
result = ((Class<?>) src).getComponentType();
114+
} else {
115+
result = (Class<?>) src;
116+
}
117+
} else if (src instanceof ParameterizedType parameterizedType) {
118+
int index = (parameterizedType.getRawType() instanceof Class
119+
&& Map.class.isAssignableFrom((Class<?>) parameterizedType.getRawType())
120+
&& parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0;
121+
Type actualType = parameterizedType.getActualTypeArguments()[index];
122+
result = typeToClass(actualType, fallback);
123+
}
124+
if (result == null) {
125+
result = fallback;
126+
}
127+
return result;
128+
}
129+
130+
}
131+
132+
private void registerReflectionTypeIfNecessary(Class<?> type, RuntimeHints hints) {
133+
if (!type.isPrimitive() && !type.getName().startsWith("java")) {
134+
hints.reflection().registerType(type, MemberCategory.values());
135+
}
136+
}
137+
138+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2015-2024 the original author or 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 org.mybatis.spring.boot.autoconfigure;
17+
18+
import org.apache.commons.logging.Log;
19+
import org.apache.commons.logging.LogFactory;
20+
import org.mybatis.spring.mapper.MapperFactoryBean;
21+
import org.springframework.beans.factory.BeanFactory;
22+
import org.springframework.beans.factory.BeanFactoryAware;
23+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
24+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
25+
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
26+
import org.springframework.beans.factory.support.RootBeanDefinition;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.ResolvableType;
29+
import org.springframework.util.ClassUtils;
30+
31+
/**
32+
* The {@code BeanDefinitionPostProcessor} for customizing a {@code MapperFactoryBean}.
33+
*
34+
* @author Stéphane Nicoll
35+
* @author Kazuki Shimizu
36+
* @author xuxiaowei
37+
*
38+
* @since 3.0.4
39+
*/
40+
@Configuration
41+
class MyBatisMapperFactoryBeanPostProcessor implements MergedBeanDefinitionPostProcessor, BeanFactoryAware {
42+
43+
private static final Log LOG = LogFactory.getLog(MyBatisMapperFactoryBeanPostProcessor.class);
44+
45+
private static final String MAPPER_FACTORY_BEAN = MapperFactoryBean.class.getName();
46+
47+
private ConfigurableBeanFactory beanFactory;
48+
49+
@Override
50+
public void setBeanFactory(BeanFactory beanFactory) {
51+
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
52+
}
53+
54+
@Override
55+
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
56+
if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) {
57+
resolveMapperFactoryBeanTypeIfNecessary(beanDefinition);
58+
}
59+
}
60+
61+
private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) {
62+
if (!beanDefinition.hasBeanClass() || !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) {
63+
return;
64+
}
65+
if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) {
66+
Class<?> mapperInterface = getMapperInterface(beanDefinition);
67+
if (mapperInterface != null) {
68+
// Exposes a generic type information to context for prevent early initializing
69+
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
70+
constructorArgumentValues.addGenericArgumentValue(mapperInterface);
71+
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
72+
beanDefinition
73+
.setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface));
74+
}
75+
}
76+
}
77+
78+
private Class<?> getMapperInterface(RootBeanDefinition beanDefinition) {
79+
try {
80+
return (Class<?>) beanDefinition.getPropertyValues().get("mapperInterface");
81+
} catch (Exception e) {
82+
LOG.debug("Fail getting mapper interface type.", e);
83+
return null;
84+
}
85+
}
86+
87+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{ "name": "org.apache.ibatis.logging.slf4j.Slf4jImpl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
3+
{ "name": "org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
4+
{ "name": "org.apache.ibatis.logging.log4j2.Log4j2Impl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
5+
{ "name": "org.apache.ibatis.logging.log4j.Log4jImpl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
6+
{ "name": "org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
7+
{ "name": "org.apache.ibatis.logging.nologging.NoLoggingImpl", "allDeclaredConstructors": true, "allDeclaredMethods": true },
8+
{ "name": "org.apache.ibatis.plugin.Interceptor", "allDeclaredConstructors": true, "allDeclaredMethods": true },
9+
{ "name": "org.apache.ibatis.javassist.util.proxy.ProxyFactory", "allDeclaredConstructors": true, "allDeclaredMethods": true },
10+
{ "name": "org.apache.ibatis.scripting.xmltags.XMLLanguageDriver", "methods": [{ "name": "<init>", "parameterTypes": [] }] },
11+
{ "name": "org.apache.ibatis.scripting.defaults.RawLanguageDriver", "methods": [{ "name": "<init>", "parameterTypes": [] }] },
12+
{ "name": "org.mybatis.spring.SqlSessionFactoryBean", "allDeclaredConstructors": true, "allDeclaredMethods": true },
13+
{ "name": "java.util.ArrayList", "methods": [{ "name": "<init>", "parameterTypes": [] }] }
14+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Copyright 2015-2024 the original author or 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+
Args = -H:ReflectionConfigurationResources=${.}/mybatis-reflection.json
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
2+
org.mybatis.spring.boot.autoconfigure.MyBatisBeanFactoryInitializationAotProcessor
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
22
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
3+
org.mybatis.spring.boot.autoconfigure.MyBatisMapperFactoryBeanPostProcessor
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright ${license.git.copyrightYears} the original author or authors.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
https://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2015-2024 the original author or authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
https://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<!DOCTYPE Format>
20+
<Format>
21+
<!-- Dummy format file -->
22+
</Format>

0 commit comments

Comments
 (0)