SpringNative:SpringBoot3整合Mybatis构建Native文件

前言

        本文章用于记录我在预研SpringNative (Spring Boot3)时的全过程。

       :本来想研究SpringNative的使用,然后发现 SpringNative已被SpringBoot3的官方Native支持取代。因此本篇记录的是 SpringBoot3构建Native文件的全过程,其中使用到了Mybatis框架(有坑)做mysql数据库的CRUD。

         文章对应的代码链接为:GitHub - github-sky33/spring-native-showtime: 云原生相关

环境准备

基础环境信息

        * Java17     (SpringBoot3需要)

        * GraalVM 17     (SpringBoot3需要)

        * Visual Stadio 2022(17.7.2)    (GraalVM 需要)

        * Maven 3.8.8      (忘了为啥选这个版本,好像是预研Quarkus时需要的运行环境)

        * Idea 2022.3.3       (Maven 3.8.8需要)

        * Mysql 8.0.33

        * Windows 11 企业版

        注:电脑配置建议有8G的可用内存,构建Native文件会比较耗内存。

GraalVM与Visual Stadio安装(注意版本)

        我的Native文件生成是在Windows环境,因此需要安装 Visual Stadio,linux版本未尝试,请自行查找。

        GraalVM与Visual Stadio的安装,我是参考知乎的一篇帖子但是,由于GraalVM17 对 Visual Stadio 2022以及其部分组件版本存在要求,部分组件的版本请参考我的配置,如下:

添加的单个组件如下:

MSVC v143 - VS 2022 C++ x64/x86 Spectre 缓解库(v14.36-17.6)
MSVC v143 - VS 2022 C++ x64/x86 生成工具(v14.36-17.6)
MSVC v143 - VS 2022 C++ ARM64/ARM64EC 生成工具(v14.36-17.6)
Visual Studio SDK
Windows 11 SDK (10.0.22621.0)

SpringBoot3

项目初始化

        我在Spring官网进行的初始化构建

Maven pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.3</version>
	</parent>

	<groupId>cn.mindray</groupId>
	<artifactId>spring-native-showtime</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<name>spring-native-showtime</name>
	<description>Show Time</description>

	<properties>
		<java.version>17</java.version>
		<mybatis.version>3.0.2</mybatis.version>
		<springdoc.version>2.2.0</springdoc.version>
		<alibaba.fastjson.version>1.2.83</alibaba.fastjson.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>${mybatis.version}</version>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<!-- JSON -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${alibaba.fastjson.version}</version>
		</dependency>

		<!-- openApi -->
		<dependency>
			<groupId>org.springdoc</groupId>
			<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
			<version>${springdoc.version}</version>
		</dependency>


	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.graalvm.buildtools</groupId>
					<artifactId>native-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>

				</configuration>
			</plugin>
		</plugins>


		<!--手动指定文件夹为resources-->
		<resources>
			<resource>
				<directory>src/lib</directory>
				<targetPath>/lib</targetPath>
				<includes>
					<include>**/*.jar</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
				<filtering>false</filtering>
			</resource>
		</resources>
	</build>

</project>

Mybatis适配

        到目前为止,直接依赖Mybatis去构建Native文件会报错(毕竟是依赖于反射与代理的框架)。因此需要添加配置类(源自 mybatis/spring-boot-starter 提供的Mybatis Native支持 : MyBatisNativeConfiguration):

package cn.mindray.showtime.base.config.mybatis;

import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.cache.decorators.FifoCache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.decorators.SoftCache;
import org.apache.ibatis.cache.decorators.WeakCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
import org.apache.ibatis.javassist.util.proxy.RuntimeSupport;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
import org.apache.ibatis.logging.log4j2.Log4j2Impl;
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Configuration(proxyBeanMethods = false)
@ImportRuntimeHints(MyBatisNativeConfiguration.MyBaitsRuntimeHintsRegistrar.class)
public class MyBatisNativeConfiguration {

  @Bean
  MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() {
    return new MyBatisBeanFactoryInitializationAotProcessor();
  }

  @Bean
  static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() {
    return new MyBatisMapperFactoryBeanPostProcessor();
  }

  static class MyBaitsRuntimeHintsRegistrar implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
      Stream.of(RawLanguageDriver.class,
          XMLLanguageDriver.class,
          RuntimeSupport.class,
          ProxyFactory.class,
          Slf4jImpl.class,
          Log.class,
          JakartaCommonsLoggingImpl.class,
          Log4j2Impl.class,
          Jdk14LoggingImpl.class,
          StdOutImpl.class,
          NoLoggingImpl.class,
          SqlSessionFactory.class,
          PerpetualCache.class,
          FifoCache.class,
          LruCache.class,
          SoftCache.class,
          WeakCache.class,
          SqlSessionFactoryBean.class,
          ArrayList.class,
          HashMap.class,
          TreeSet.class,
          HashSet.class
      ).forEach(x -> hints.reflection().registerType(x, MemberCategory.values()));
      Stream.of(
          "org/apache/ibatis/builder/xml/*.dtd",
          "org/apache/ibatis/builder/xml/*.xsd"
      ).forEach(hints.resources()::registerPattern);
    }
  }

  static class MyBatisBeanFactoryInitializationAotProcessor
      implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter {

    private final Set<Class<?>> excludeClasses = new HashSet<>();

    MyBatisBeanFactoryInitializationAotProcessor() {
      excludeClasses.add(MapperScannerConfigurer.class);
    }

    @Override public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
      return excludeClasses.contains(registeredBean.getBeanClass());
    }

    @Override
    public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
      String[] beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class);
      if (beanNames.length == 0) {
        return null;
      }
      return (context, code) -> {
        RuntimeHints hints = context.getRuntimeHints();
        for (String beanName : beanNames) {
          BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1));
          PropertyValue mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface");
          if (mapperInterface != null && mapperInterface.getValue() != null) {
            Class<?> mapperInterfaceType = (Class<?>) mapperInterface.getValue();
            if (mapperInterfaceType != null) {
              registerReflectionTypeIfNecessary(mapperInterfaceType, hints);
              hints.proxies().registerJdkProxy(mapperInterfaceType);
              hints.resources()
                  .registerPattern(mapperInterfaceType.getName().replace('.', '/').concat(".xml"));
              registerMapperRelationships(mapperInterfaceType, hints);
            }
          }
        }
      };
    }

    private void registerMapperRelationships(Class<?> mapperInterfaceType, RuntimeHints hints) {
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType);
      for (Method method : methods) {
        if (method.getDeclaringClass() != Object.class) {
          ReflectionUtils.makeAccessible(method);
          registerSqlProviderTypes(method, hints, SelectProvider.class, SelectProvider::value, SelectProvider::type);
          registerSqlProviderTypes(method, hints, InsertProvider.class, InsertProvider::value, InsertProvider::type);
          registerSqlProviderTypes(method, hints, UpdateProvider.class, UpdateProvider::value, UpdateProvider::type);
          registerSqlProviderTypes(method, hints, DeleteProvider.class, DeleteProvider::value, DeleteProvider::type);
          Class<?> returnType = MyBatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method);
          registerReflectionTypeIfNecessary(returnType, hints);
          MyBatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method)
              .forEach(x -> registerReflectionTypeIfNecessary(x, hints));
        }
      }
    }

    @SafeVarargs
    private <T extends Annotation> void registerSqlProviderTypes(
        Method method, RuntimeHints hints, Class<T> annotationType, Function<T, Class<?>>... providerTypeResolvers) {
      for (T annotation : method.getAnnotationsByType(annotationType)) {
        for (Function<T, Class<?>> providerTypeResolver : providerTypeResolvers) {
          registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), hints);
        }
      }
    }

    private void registerReflectionTypeIfNecessary(Class<?> type, RuntimeHints hints) {
      if (!type.isPrimitive() && !type.getName().startsWith("java")) {
        hints.reflection().registerType(type, MemberCategory.values());
      }
    }

  }

  static class MyBatisMapperTypeUtils {
    private MyBatisMapperTypeUtils() {
      // NOP
    }

    static Class<?> resolveReturnClass(Class<?> mapperInterface, Method method) {
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      return typeToClass(resolvedReturnType, method.getReturnType());
    }

    static Set<Class<?>> resolveParameterClasses(Class<?> mapperInterface, Method method) {
      return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface))
          .map(x -> typeToClass(x, x instanceof Class ? (Class<?>) x : Object.class)).collect(Collectors.toSet());
    }

    private static Class<?> typeToClass(Type src, Class<?> fallback) {
      Class<?> result = null;
      if (src instanceof Class<?>) {
        if (((Class<?>) src).isArray()) {
          result = ((Class<?>) src).getComponentType();
        } else {
          result = (Class<?>) src;
        }
      } else if (src instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) src;
        int index = (parameterizedType.getRawType() instanceof Class
            && Map.class.isAssignableFrom((Class<?>) parameterizedType.getRawType())
            && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0;
        Type actualType = parameterizedType.getActualTypeArguments()[index];
        result = typeToClass(actualType, fallback);
      }
      if (result == null) {
        result = fallback;
      }
      return result;
    }

  }

  static class MyBatisMapperFactoryBeanPostProcessor implements MergedBeanDefinitionPostProcessor, BeanFactoryAware {

    private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(
        MyBatisMapperFactoryBeanPostProcessor.class);

    private static final String MAPPER_FACTORY_BEAN = "org.mybatis.spring.mapper.MapperFactoryBean";

    private ConfigurableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
      if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) {
        resolveMapperFactoryBeanTypeIfNecessary(beanDefinition);
      }
    }

    private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) {
      if (!beanDefinition.hasBeanClass() || !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) {
        return;
      }
      if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) {
        Class<?> mapperInterface = getMapperInterface(beanDefinition);
        if (mapperInterface != null) {
          // Exposes a generic type information to context for prevent early initializing
          beanDefinition
              .setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface));
        }
      }
    }

    private Class<?> getMapperInterface(RootBeanDefinition beanDefinition) {
      try {
        return (Class<?>) beanDefinition.getPropertyValues().get("mapperInterface");
      }
      catch (Exception e) {
        LOG.debug("Fail getting mapper interface type.", e);
        return null;
      }
    }

  }
}

        至此,基本的配置已完成。

        如果出现报错如:

        则需要在@MapperScan加上属性值:sqlSessionFactoryRef = "sqlSessionFactory" 。如:

@SpringBootApplication
@MapperScan(basePackages = "cn.mindray.showtime.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class ShowtimeApplication {

	public static void main(String[] args) {
		SpringApplication.run(ShowtimeApplication.class, args);
	}

}

生成Native文件

运行如下命令,用于生成 Native文件

 mvn -Pnative -DskipTests clean native:compile

 

        如果是Windows环境,可能会有如下报错,不用管,直接把蓝色框内的命令copy到命令行再执行即可:

        然后就是耗时耗内存的Native文件生成了(官方推荐在MacOS构建Native镜像需要给Docker 8G内存,我们直接生成Native文件可能消耗小一些,但是最好预留8G可用内存):

 启动测试Native文件

        启动:

GitHub

GitHub - github-sky33/spring-native-showtime: 云原生相关云原生相关. Contribute to github-sky33/spring-native-showtime development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/github-sky33/spring-native-showtime

Spring Boot整合MyBatis有几个步骤。首先,你需要在项目的pom文件中添加MyBatis的依赖项。具体来说,你需要添加以下依赖项: ```xml <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> ``` 接下来,你需要创建一个配置文件来配置MyBatis和数据库连接。你可以在application.yml或application.properties文件中添加以下配置项: ```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:/mappers/*.xml type-aliases-package: com.buba.pojo ``` 然后,在启动类上添加`@MapperScan`注解,指定Mapper接口的位置。例如,如果你的Mapper接口在`com.buba.mappers`包下,你可以在启动类上添加以下注解: ```java @MapperScan("com.buba.mappers") @SpringBootApplication public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 接着,你需要创建Mapper接口和对应的Mapper XML文件。在XML文件中,你可以使用`<mapper>`标签指定命名空间和SQL语句。例如,你可以创建一个名为`TestMapper`的Mapper接口和对应的XML文件,并在XML文件中编写查询语句: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.buba.mappers.TestMapper"> <select id="selectNameAll" resultType="string"> select name from user </select> </mapper> ``` 最后,你可以在Service层中注入Mapper接口,并在Controller层中调用Service方法来使用MyBatis进行数据库操作。例如,你可以创建一个名为`TestServiceImpl`的Service实现类,并在其中注入`TestMapper`接口: ```java @Service public class TestServiceImpl implements TestService { @Autowired private TestMapper testMapper; @Override public List<String> selectNameAll() { return testMapper.selectNameAll(); } } ``` 然后,你可以创建一个名为`TestController`的Controller类,并调用Service中的方法来处理请求: ```java @RestController @RequestMapping("/test") public class TestController { @Autowired private TestService testService; @GetMapping("/selectNameAll") public List<String> selectNameAll() { return testService.selectNameAll(); } } ``` 请根据你的具体需求修改以上代码,并确保在启动应用程序之前完成数据库的配置和表的创建。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Springboot整合3.整合mybatis](https://blog.csdn.net/zhangchen124/article/details/124208920)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringBoot(3)整合Mybatis](https://blog.csdn.net/weixin_38380811/article/details/129863316)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值