Mybatis source analysis – Mapper creation and Spring management

When we analyze their first guess to achieve the way to compare mybatis source code implementation

Mapper created

  • Since MyBatis can be used without Spring itself, mapper bean creation is done by MyBatis
  • The method of creation, depending on the Mapper, is the method that corresponds to the name of the annotation or configuration file, so we assume that we are using spring’s dynamic proxy creation method

We implemented mapper ourselves to create factory agent classes:

public class MySessionFactoryProxy { public static Object getMapper(Class c){ Class[] classes = new Class[]{c}; / / dynamic Proxy for mapper Object o = Proxy. NewProxyInstance (MySessionFactoryProxy. Class. GetClassLoader (), classes, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Parse SQL // execute SQL Select annotation = method.getannotation (self.class); String sql = annotation.value()[0]; System.out.println(" SQL :"+ SQL); return null; }}); return o; }}Copy the code

A factoryBean is used to call the method. Each time you call it, you create a factoryBean and pass in a Mapper class to create the mapper.

  • MyMapperFactoryBean
Public class MyMapperFactoryBean<T> implements FactoryBean<T> { mapperInterface) { this.mapperInterface = mapperInterface; } private Class<T> mapperInterface; private Class<T> mapperInterface; public T getObject() throws Exception { System.out.println("get mapper"); return (T) MySessionFactoryProxy.getMapper(mapperInterface); } public Class<? > getObjectType() { return this.mapperInterface; }}Copy the code

Look at the implementation of Mybatis

  • Mapper The registration class obtains the mapper
public class MapperRegistry { public <T> T getMapper(Class<T> type, SqlSession SqlSession) {/ / main call return mapperProxyFactory. NewInstance (SqlSession); }Copy the code

  • Mybatis implements the agent factory class:
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; Public T newInstance(SqlSession SqlSession) {MapperProxy<T> MapperProxy = new MapperProxy(SqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }}Copy the code

  • Call the MapperFactoryBean of the getMapper method
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); } public Class<T> getObjectType() { return this.mapperInterface; } public void setMapperInterface(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; }}Copy the code

It’s basically the same as what we created

After creating mapper, we need to submit mapper to IOC management

Create a Mapper and put it in Spring IOC

Please add annotations or configuration differently, submit the class to Spring management, by Spring to create objects for us, Mapper is created by Mybatis through dynamic proxy

You create your own objects and give them to Spring to manage

  • Idea 1: Created by a configuration class
@Configuration @ComponentScan("top.dzou.mybatis") public class Appconfig { @Bean public UserMapper userMapper(){ return (UserMapper) MySessionFactoryProxy.getMapper(UserMapper.class); }}Copy the code

Creating an @bean annotation for each one is impractical. How does Spring implement this?

  • We remembered that beans can be configured through spring configuration files, and we thought of configuring Mapper beans in XML format

Something like this:

Create the Mapper using our own Mapper factory agent class

    <bean id="roleMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
        <property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.RoleMapper"/>
    </bean>
    <bean id="userMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
        <property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.UserMapper"/>
    </bean>
</beans>Copy the code

This code is not familiar, but there are still a lot of beans to configure, and we are reminded of the Mapperscan annotation used in SpringBoot

  • Idea 3: Implement a note yourselfMyMapperScanInclude a package, import the Mapper import class on the MapperScan annotation, create everything below the package and put it in Spring

When Spring creates a bean, it loads the class and creates a BeanDefinition. When Spring creates a bean from a BeanDefinition, it creates a bean from mapper

We implement ourselves:

  • MyMapperScan

Import mapper according to basePackages

/ / Import ImportBeanDefinitionRegister @ Import (MyBeanDefinitionRegister. Class) @ Retention (RetentionPolicy. RUNTIME) to the public @interface MyMapperScan { String[] basePackages() default {}; }Copy the code

  • MyBeanDefinitionRegister

Using Spring registration of bean ImportBeanDefinitionRegistrar registered mapper

public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar { //class->beanDefinition->map->bean public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry BeanDefinitionRegistry) {// Get package information and add all classes registered in the package dynamically to beanDefinition {// pseudo-code basePackages = Get the mapper class used for the package name, Save to collection basePackages baseName = Get beanName} BeanDefinitionBuilder BeanDefinitionBuilder = from mapper class BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class); BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(basePackages); beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition); }}Copy the code

  • Configuration class instead of main startup class (for testing)
@Configuration @ComponentScan("top.dzou.mybatis.mapper_to_spring") @MyMapperScan(basePackages = "Top.dzou. Mapper_to_spring.mapper ")// Custom mapper scan annotation public class Appconfig {}Copy the code

How to implement mybatis

  • MapperScan

It imports a MapperScannerRegistrar to scan the classes to register Mapper

@Import({MapperScannerRegistrar.class})
public @interface MapperScan {Copy the code

  • MapperScannerRegistrar

We can see that it implements ImportBeanDefinitionRegistrar bean definitions of import registration class, implement the specific registerBeanDefinitions registered bean definition method, all the package under the mapper added to a collection, Then register this collection in IOC, which is basically consistent with our idea

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); List<String> basePackages = new ArrayList(); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Co llectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).col lect(Collectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageN ame).collect(Collectors.toList())); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }Copy the code

It holds all mapper classes in a collection and registers them in a beanDefinition

conclusion

The whole process is mainly divided into

  1. Mybatis registers the entire Mybatis factory bean to IOC through MapperScan
  2. Spring is responsible for mapper injection IOC created by Mybatis

Key points:

  • MapperScan ImportBeanDefinitionRegistrar registerBeanDefinitions beanMapperFactoryBean encapsulated into the plant in the beanDefinition and add mapper under the package The constructor parameters are passed in and registered with Spring
  • After registration, Spring will call Mybatis to create mapper based on constructor parameters and factory beans and register mapper with Spring-managed beans