springboot(4.4)集成mybatis原理分析

在springboot集成mybatis项目中,我们只需要在启动类上加上@MapperScan注解,让spring在启动时去扫描某路径下的所有mapper接口,然后就能实现mapper接口对应的增删改查功能,但mapper类,只是一个接口,我们并未为其写具体的实现,那么它是怎么被实例化的呢?本文就从源码的角度来解析这一过程。通过本文的学习,我们还能举一反三的让我们以后写自己的功能时,更好的和spring进行整合。

理论上而言,应该是这样的:
@MapperScan注解告诉spring去扫描路径下的所有mapper,然后根据mapper生成对应的实现类,并将实现类注入到spring容器中。但mapper接口的实现类真的需要多个吗?其实不需要,因为mapper实现类的功能总体来说都是获取sql执行sql。因此为mapper生成实现类时,可以生产一个代理类,在代理类中实现获取sql、执行sql的功能。

如果我们需要来实现这个功能应该如何做呢?以下代码为模拟代码。

实现步骤

定义一个基于jdk的动态代理类

为什么需要一个动态代理类?因为mapper是接口,接口本身是不能实例化的,因此需要为接口定义实现类,但如果只是一个简单的实现类,则我们需要为每一个mapper接口都定义实现类,这显然是不可能的;动态代理是解决这类问题的绝佳方案。但实现动态代理,可以有多种技术选型,如基于jdk动态代理、基于cglib的动态代理、基于原生字节码来实现动态代理等等。这里以jdk动态代理为例,进行模拟。

//第一个版本
public class MyProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("toString")){
            return "";
        }
        System.out.println("代理前"+method.getName());

        Object result = method.invoke(, args);
        
        System.out.println("代理后"+method.getName());
        return result;
    }

}

上述代码段中,Object result = method.invoke(, args);第一个参数是什么,本身应该是被代理对象实例。但我们的mapper只是接口,并没有实现类,哪怕是默认的实现类都没有,因此这里不能像普通的jdk代理那样做。

//第二个版本
public class MyProxy implements InvocationHandler {
    private Class target;
    
    public MyProxy(Class target){
        this.target= target;
    }
    //方便获取代理对象
    public  Object getObject(){
        return Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target},this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("toString")){
            return "";
        }
        System.out.println("代理前"+method.getName());

        //实现sql的增删改查,
        
        
        System.out.println("代理后"+method.getName());
        return result;
    }

}

实现sql的增删改查,主要包含如下步骤:

  1. 需要一个被代理的mapper接口的Class对象,因此需要使用构造方法传入进来
  2. 获取方法上的注解,假设@Select(“select id,user_name as userName from aimas_sys_user”)
  3. 使用相关技术来执行sql语句,如jdbc
  4. 返回sql执行结果

如何让自定义的动态代理类注入到spring容器中

一般将一个类注入到spring容器中,能想到的方法有很多,常用的有用@Component等注解、在config类中用@Bean注解等等。
但这些方法都只能一次注入一个bean到spring容器中。
这里我们使用另外一种日常开发过程中用得较少的FactoryBean接口来实现。
自定义类如MyFactoryBean实现FactoryBean接口,并重新方法。

public class MyFactoryBean implements FactoryBean {

    private Class target;
    private MyFactoryBean(Class target){
        this.target = target;
    }
    
    //factorybean产生对象的方法
    public Object getObject() throws Exception {
        MyProxy myProxy = new MyProxy(target);
        return myProxy.getObject();
    }

    @Override
    public Class<?> getObjectType() {
        return target;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

上述factorybean不能使用@Component等注解修饰,因为需要为构造方法传参数;再者,我们需要使用这个factorybean创建N个代理对象,最好的方式就是类的路径扫描,就像@MapperScan一样。

定义scan

我们也可以参照@MapperScan来自定义scan,扫描某个路径下的所有mapper。代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyScannerRegistrar.class)
public @interface MyScan {
    String[] value() default {};
}

只定义了MyScan注解,还需要为该注解写一个解析类,如上述代码中提到的MyScannerRegistrar

定义MyScannerRegistrar类

该类的功能是解析MyScan注解修饰的路径,扫描该路径下的所有mapper接口,并结合自定义的FactoryBean生成自定义的代理对象,并将生成好的代理对象注入到spring容器中。

public class MyScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取MyScan注解类的属性值
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyScan.class.getName()));
        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }

        try{
            //扫描路径下的所有mapper接口名
            Set<String> scan = scan(basePackages);
            for(String s:scan){
                //为mapper接口生成BeanDefinition,并将BeanDefinition保存到spring的beanDefinitionMap中,
                // 这样在bean实例化的时候,会根据BeanDefinition实例化对应的bean
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class.getName());
                AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
                Class clazz = Class.forName(s);
                //为构造函数指定参数
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(clazz);
                //向beanDefinitionMap注册beanDefinition
                registry.registerBeanDefinition(clazz.getSimpleName().substring(0,1).toLowerCase()+clazz.getSimpleName().substring(1), beanDefinition);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public Set<String> scan(List<String> bases) throws Exception{
        Set<String> set = new HashSet<>();
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);

        for(String base:bases) {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(new StandardEnvironment().resolveRequiredPlaceholders(base)) + "/**.class";
            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);

            for (Resource r : resources) {
                MetadataReader reader = metaReader.getMetadataReader(r);
                System.out.println(reader.getClassMetadata().getClassName());
                set.add(reader.getClassMetadata().getClassName());
            }
        }
        return set;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

自定义MyScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法。在方法中需要自定义要引入对象的BeanDefinition对象,并将BeanDefinition对象注册到spring的beanFactory中去。

当然,mybatis集成spring的代码比上述代码要复杂得多,但核心逻辑基本一致,感兴趣的朋友可以去看看mybatis集成spring的代码。
核心类如下:

  1. @MapperScan,入口类
  2. MapperScannerRegistrar 负责扫描类路径,并将扫描到的类生成beanDefinition
  3. MapperFactoryBean:利用factorybean的方式生成代理类的bean,为生成beanDefinition做准备
  4. MapperProxyFactory:使用jdk动态代理生成代理类,为factorybean生成bean做准备
  5. MapperProxy:实现对mapper接口的动态代理,为jdk生成代理类做准备
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Spring Boot集成MyBatis,你需要进行以下几个步骤: 1. 添加MyBatisSpring Boot的依赖。你可以使用以下方式之一引入依赖: - 使用mybatis-spring-boot-starter:在pom.xml文件中添加如下依赖: ```xml <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> ``` - 使用单独的MyBatis依赖:在pom.xml文件中添加如下依赖: ```xml <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> ``` 注意,如果你使用mybatis-spring-boot-starter,它会自动引入jdbc依赖,所以不需要再额外添加spring-boot-starter-jdbc依赖。 2. 配置数据库连接信息。在application.properties(或application.yml)文件中添加以下配置,根据你的数据库信息进行修改: ```properties spring.datasource.username=your-username spring.datasource.password=your-password spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` 3. 配置MyBatis相关的属性。在application.properties(或application.yml)文件中添加以下配置: ```properties mybatis.type-aliases-package=com.example.pojo # 将你的POJO实体类所在的包路径指定为别名包路径 mybatis.mapper-locations=classpath:mybatis/mapper/*.xml # 指定Mapper配置文件的位置 ``` 以上就是在Spring Boot集成MyBatis的基本步骤。通过这些配置,你可以使用MyBatis进行数据库操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SpringBoot整合Mybatis](https://blog.csdn.net/junR_980218/article/details/124805813)[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^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [SpringBoot如何集成Mybatis呢?](https://blog.csdn.net/qq_25073223/article/details/127975950)[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^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值