一、mybatis简介 Mybaits 是java语言一个ORM框架。官网介绍如下:
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
mybatis把SQL抽离到了xml中(此处不考虑使用注解声明SQL方式,因为违背了SQL可配置的原则,不推崇),如下所示:
1 2 3 4 5 6 7 8 9 <?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 ="org.mybatis.example.BlogMapper" > <select id ="selectBlog" resultType ="Blog" > select * from Blog where id = #{id} </select > </mapper >
使用时,有几种不同的方式:
1)直接通过XML中的命名空间直接通过
1 2 SqlSession session = sqlSessionFactory.openSession()Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog" , 101 );
2)定义一个与之相对应的java接口作为DAO层使用。
1 2 3 4 package org.mybatis.example;public interface BlogMapper { Blog selectBlog (int id) ; }
使用时通过接口执行操作:
1 2 3 SqlSession session = sqlSessionFactory.openSession()BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101 );
目前第二种方式比较常用,更符合ORM的使用习惯。
二、Spring通过MapperScan注解集成Mybatis
spring使用可以通过配置文件和注解来集成mybatis,此处只探究注解方式的实现机制。
1. MapperScan注解,设置扫描包 MapperScan使用如下,只需要在某个Bean上加伤@MapperScan即可:
1 2 3 4 5 6 7 @SpringBootApplication @MapperScan("pers.kivi.demo.mapper") public class App { public static void main (String[] args) { SpringApplication.run(App.class, args); } }
观察MapperScan注解发现其通过Import注解引入了MapperScannerRegistrar:
1 2 3 4 5 6 7 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) public @interface MapperScan { ... }
2.@Import的解析时机 在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 调用时,会调用ConfigurationClassPostProcessor 这个内置的BeanFactoryPostProcessor 对传入的配置类进行级联的解析,把符合条件的类解析为BeanDefinition 注册到beanDefinitionMap 中。
其中就包含对*@Import注解的解析,ConfigurationClassPostProcessor 会调用其 registerBeanDefinitions*方法注册bean定义。
3. MapperScannerRegistrar 该接口是ImportBeanDefinitionRegistrar 的子类,职责是负责注册BeanDefinition 到BeanFactory 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner (registry); List<String> basePackages = new ArrayList <String>(); for (String pkg : annoAttrs.getStringArray("value" )) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages" )) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses" )) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.doScan(StringUtils.toStringArray(basePackages)); } }
从源码可以发现,MapperScannerRegistrar 使用ClassPathMapperScanner 来扫描传入的package ,解析并注册BeanDefinition。
4.ClassPathMapperScanner ClassPathMapperScanner 继承了ClassPathBeanDefinitionScanner 来扫描BeanDefinition ,但是为了把Mybatis声明的Mapper接口扫描为BeanDefinition ,其重写了isCandidateComponent 方法。
然后把扫描到的Bean定义的class通过processBeanDefinitions 方法设置为MapperFactoryBeans 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package org.mybatis.spring.mapper;public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { @Override public Set<BeanDefinitionHolder> doScan (String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super .doScan(basePackages); processBeanDefinitions(beanDefinitions); return beanDefinitions; } private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(this .mapperFactoryBean.getClass()); } } @Override protected boolean isCandidateComponent (AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); } }
5. MapperFactoryBeans 不出意外MapperFactoryBean肯定是一个FactoryBean,负责对接口生成对应的代理实例。
1 2 3 4 5 6 7 8 9 10 public class MapperFactoryBean <T> extends SqlSessionDaoSupport implements FactoryBean <T> { ... @Override public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); } ... }
三、Spring创建Bean时机 Spring会在org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 方法中对非BeanFactoryPostProcessor ,ApplicationListener 类型等的普通Bean进行初始化。如果发现某BeanDefinitino 的类型是FactoryBean ,则会继续调用getObject 方法创建具体实例。
至此完成Mybatis的Mapper集成进Spring的Context管理。
四、总结 Spring内部提供了太多的扩展点供增强使用,例如:
1)BeanFactoryPostProcessor可以用来增强BeanDefinition的加载;
2)BeanPostProcessor可以用来增强Bean创建过程;
3)Aware可以使Bean织入Spring的一些Bean;
4)EventListener/ApplicationEventMulticaster可以用来实现广播自己的事件;
5)Xml配置文件直至自定义schema的NameSpaceHandler,来进行BeanDefinition注册;
等等。
我们实际开发的时候多了解里面的机制,就可以做到更低耦合,更优雅的功能扩展和实现。