Spring整合Mybatis源码分析(mybatis-spring-2.0.6)

简介: 本篇文章从以下几个方面分析Spring整合Mybatis的原理1.例模拟Spring整合Mybatis2.mybatis-spring.jar包的整合原理
示例
  • 首先引入Mybatis的使用示例,其中两个关键变量:SqlSessionFactory ,SqlSession
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper1 mapper = sqlSession.getMapper(Mapper1.class);
String result = mapper.select1();
  • 问题引入,以下代码中(系统中存在Mapper1,Mapper2,Mapper3接口),如果直接运行main函数,后台会报错,mapper注入错误,此时可以引入FactoryBean机制,生成mapper的代理对象:
@Service
public class MybatisAndSpringService {

    @Autowired
    private Mapper1 mapper1;
    
    // ......多个Mapper接口(省略)

    @Autowired
    private Mappern mappern;

    public void test() {
        String result1 = mapper1.select1();
        System.out.println("result1 = " + result1);
        String result2 = mapper2.select2();
        System.out.println("result2 = " + result2);
        //……
    }

}

// 主函数
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 引入FactoryBean接口,此时如果直接运行main函数依然会报错,因为自定义FactoryBean不在Spring容器中,所以需要采用声明式编程的方式,修改main函数:
public class ArtistFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        // JDK动态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{Mapper1.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("当前调用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理逻辑
        // return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return Mapper1.class;
    }
}

// main函数
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 到了这一步,直接运行是没有问题的,继续思考:FactoryBean的getObject方法,生成代理对象的接口是在代码中写死的,所以还需要通过构造函数传参的方式修改
// 修改FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }

    @Override
    public Object getObject() throws Exception {
        // JDK动态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{mapperClazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("当前调用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理逻辑
        // return sqlSession.getMapper(mapperClazz);
    }

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

// 同时在main函数中也要修改
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 经过上述修改之后,FactoryBean中mapper接口的传入方式得到了解决,但是,如果此时要添加其他的mapper,比如:Mapper2,Mapper3……,就会出现下面的编码:
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    // Mapper1
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);
    
    // ……很多Mapper(省略)
    
    // Mappern
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mappern.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 显然这种编码方式非常不优雅,那么接下来可以考虑包扫描的方式,把Mapper接口扫描出来保存到一个集合中,然后遍历集合,就可以替换掉这么一大片冗余的代码,可以利用Spring提供的ImportBeanDefinitionRegistrar接口,实现它的registerBeanDefinitions方法
  • 同时结合Spring的类扫描器ClassPathBeanDefinitionScanner,但是Spring只扫描类,而这里我们只扫描接口,所以需要自定义一个类扫描器,需要修改isCandidateComponent的逻辑,只返回接口,否则Mapper接口无法扫描到
  • 重写doScan逻辑,因为我们最终需要的是Mapper接口的代理对象,所以需要把之前定义的FactoryBean设置进去
  • 需要注意,在配置类上需要加入注解:@Import(ArtistImportBeanDefinitionRegistrar.class),使Bean定义注册器成为Spring容器中的Bean
//自定义类扫描器
public class ArtistScanner extends ClassPathBeanDefinitionScanner {

    public ArtistScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    // 需要扫描到接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);

        for (BeanDefinitionHolder holder : holders) {
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(holder.getBeanDefinition().getBeanClassName());
            // 为了获取被扫描到的Mapper接口的代理对象
            beanDefinition.setBeanClass(ArtistFactoryBean.class);
        }
        return holders;
    }
}


// 批量注册Bean定义
public class ArtistImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String scanPath = "com.xxxx.mapper";
        // 此时要自定义包扫描器,因为Spring的包扫描器只扫描类,不扫描接口;而这里要扫描接口
        ArtistScanner scanner = new ArtistScanner(registry);
        // match方法返回true,保证接口能被扫描到
        scanner.addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
        scanner.scan(scanPath);
    }
}
  • 经过上述的修改,就可以把main方法中冗余的代码替换掉,到此还有一个最大的问题,getObject中生成代理对象是手工操作的,而代理对象应该由Mybatis框架生成,对应到文章开头提到的SqlSessionFactory
// 修改后的FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }
    
    @Autowired
    public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = sqlSessionFactory.openSession();
    }

    @Override
    public Object getObject() throws Exception {
        // Mybatis的代理逻辑
        return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperClazz;
    }
}
  • 可以看到,通过setter注入SqlSessionFactory对象,获取到SqlSession
  • 在配置类中也要添加SqlSessionFactory的Bean对象
@Configuration
@ComponentScan("com.mybatisandspring")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}
  • 继续看,我们的扫描路径是在代码中写死的,可以进一步作修改
  • 自定义一个注解,把mapper接口所在的包路径作为value,传入注解,然后在registerBeanDefinitions方法中,通过参数AnnotationMetadata获取该注解的value值,这样就可以灵活地配置了。
  • 首先自定义注解(这里把配置类中的@Import移到此处,为了代码的可读性,这里不影响逻辑的描述)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}
  • 接着在配置类上加上自定义注解,最终的配置类如下:
@Configuration
@ComponentScan("com.mybatisandspring")
@ArtistMapperScan("com.mybatisandspring.mapper")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}
  • 到此为止,基本上mybatis-spring框架的核心原理基本都体现出来了,概括地说:就是把Mybatis生成的代理对象注入到Spring容器中,mybatis-spring.jar包起到了整合的桥梁的作用
  • 下面对比自己写的类和mybatis-spring.jar中的核心类
mybatis-spring包的整合源码分析
  • 对比Mapper扫描注解:
// @MapperScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
}

// 自定义扫描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}
  • MapperScannerRegistrar的registerBeanDefinitions方法会先解析MapperScan注解,然后再注册Bean定义
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    // MapperScannerConfigurer执行扫描逻辑
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // 注册Bean定义
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}
  • 在MapperScannerConfigurer中有一个属性mapperFactoryBeanClass,类型是MapperFactoryBean,它的getObject方法就是返回Mapper接口的代理对象,mapperInterface也是作为被扫描的属性传入
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}
  • 总结:

    • MapperFactoryBean完成的功能是生成Mapper接口的代理对象
    • MapperScannerConfigurer扫描Mapper接口
    • MapperScannerRegistrar完成的功能是把扫描到的Mapper接口的代理对象转为Bean定义,存入Spring容器
    • SqlSessionFactory负责解析配置的mybatis配置文件,包括数据源、类型别名、映射文件的扫描路径等等信息
目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
75 1
|
2天前
|
前端开发 Java 数据库连接
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
13 2
|
14天前
|
SQL JavaScript Java
Spring Boot 3 整合 Mybatis-Plus 实现数据权限控制
本文介绍了如何在Spring Boot 3中整合MyBatis-Plus实现数据权限控制,通过使用MyBatis-Plus提供的`DataPermissionInterceptor`插件,在不破坏原有代码结构的基础上实现了细粒度的数据访问控制。文中详细描述了自定义注解`DataScope`的使用方法、`DataPermissionHandler`的具体实现逻辑,以及根据用户的不同角色和部门动态添加SQL片段来限制查询结果。此外,还展示了基于Spring Boot 3和Vue 3构建的前后端分离快速开发框架的实际应用案例,包括项目的核心功能模块如用户管理、角色管理等,并提供Gitee上的开源仓库
123 11
|
28天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
99 4
|
1月前
|
SQL Java 数据库连接
spring和Mybatis的各种查询
Spring 和 MyBatis 的结合使得数据访问层的开发变得更加简洁和高效。通过以上各种查询操作的详细讲解,我们可以看到 MyBatis 在处理简单查询、条件查询、分页查询、联合查询和动态 SQL 查询方面的强大功能。熟练掌握这些操作,可以极大提升开发效率和代码质量。
108 3
|
2月前
|
Java 数据库连接 数据库
spring和Mybatis的逆向工程
通过本文的介绍,我们了解了如何使用Spring和MyBatis进行逆向工程,包括环境配置、MyBatis Generator配置、Spring和MyBatis整合以及业务逻辑的编写。逆向工程极大地提高了开发效率,减少了重复劳动,保证了代码的一致性和可维护性。希望这篇文章能帮助你在项目中高效地使用Spring和MyBatis。
58 1
|
3月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
790 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
44 1
|
3月前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析