tkMapper的使用和原理(笔记)

4,928 阅读6分钟

1.tkMapper与springboot整合

springboot的版本为2.1.7.RELEASE,tkmapper的pom为

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
</dependency>

application.yml中mybatis的配置为

mybatis:
    # 如果有自定义SQL,mapper接口对应的.xml文件
    mapper-locations: classpath:mapper/*.xml

定义Mapper接口要实现的父接口

/**
 * Mapper包含通用SQL,MySqlMapper是对针MySQL的补充
 */
public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T> {}

mapper接口要使用@Mapper修饰并继承BaseMapper

@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {

    /**
     * 自定义的SQL,写在.xml中
     * @return
     */
    List<SysUser> mySelect();
}

2.tkMapper支持的SQL

tkMapper支持单表查询的所有场景,常见方法有

public void test() {
    // 插入
    SysUser sysUser = new SysUser();
    int count = mapper.insert(sysUser);
    // 批量插入,count为操作行数
    List<SysUser> userList = new ArrayList<>();
    userList.add(new SysUser());
    userList.add(new SysUser());
    count = mapper.insertList(userList);
    // 通过主键查询
    sysUser = mapper.selectByPrimaryKey(10000);
    // 查询匹配且只返回一个,很多匹配时报错
    sysUser = mapper.selectOne(sysUser);
    // 查询所有匹配
    sysUser.setName("张三");
    userList = mapper.select(sysUser);
    // 自定义的SQL
    userList = mapper.mySelect();
    // Selective表示有这个值的话就更新,否则不更新
    count = mapper.updateByPrimaryKey(sysUser);
    count = mapper.updateByPrimaryKeySelective(sysUser);
    // QBC查询
    Example example = new Example(SysUser.class);
    // 模糊查询
    Example.Criteria criteria1 = example.createCriteria();
    criteria1.andLike("name", "%n%");
    // 精确查询
    Example.Criteria criteria2 = example.createCriteria();
    criteria2.andEqualTo("code", "123");
    // 2个查询条件
    example.or(criteria2);
    // 排序
    example.orderBy("name").asc();
    // 去重
    example.setDistinct(true);
    // 返回字段
    example.selectProperties("id", "name", "code");
    userList = mapper.selectByExample(example);
}

对应的model应该有注解修饰

@Table(name = "sys_user")
public class SysUser {

   /**
    * 主键,@GeneratedValue为自增规则
    */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

   /**
    * 姓名
    */
    private String name;

   /**
    * 员工编码
    */
    private String code;
}

3.tkMapper的源码解析

Mybatis支持@SelectProvider注解,tkMapper正是运用了该技术,通过解析持久类,动态生成SQL,重新为MappedStatment设置SqlSource。

我们看到在执行时每个SQL都已转换成MappedStatment,tkMapper是怎么做到的呢?这里可以分2步,第一步通过解析注解生成SQL,第二步是为MappedStatment赋值。

这些都是从MapperScannerConfigurer开始的,因为该类会扫描所有被@Mapper修饰的接口,由于继承了BeanDefinitionRegistryPostProcessor接口,可以动态的对Bean的内容进行修改。

我们先看一下它的核心代码:

    
    private MapperHelper mapperHelper = new MapperHelper();

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        
        // 调用mybatis的后置注册处理,扫描mapper类注册为MapperFactoryBean对象
        super.postProcessBeanDefinitionRegistry(registry);
        this.mapperHelper.ifEmptyRegisterDefaultInterface();
        String[] names = registry.getBeanDefinitionNames();
        GenericBeanDefinition definition;
        for (String name : names) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(name);
            if (beanDefinition instanceof GenericBeanDefinition) {
                definition = (GenericBeanDefinition) beanDefinition;
                // 判断注册的bean是MapperFactoryBean,设置beanClass为 tk.mybatis.spring.mapper.MapperFactoryBean
                // 设置MapperHelper属性
                if (StringUtil.isNotEmpty(definition.getBeanClassName())
                        && definition.getBeanClassName().equals("org.mybatis.spring.mapper.MapperFactoryBean")) {
                    definition.setBeanClass(MapperFactoryBean.class);
                    definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
                }
            }
        }
    }

其中MapperHelper是将所有用mapper接口的父类中被@SelectProvider修饰的类,也就是SQL模板作为MapperTemplate存在registerMapper中。到这里我们的第一步就完成了。

我们先看一下它的核心代码:

public class MapperHelper {
    private List<Class<?>> registerClass;

    private Map<Class<?>, MapperTemplate> registerMapper;
    private Config config;

    public void registerMapper(String mapperClass) {
        try {
            // 实例化Mapper接口
            registerMapper(Class.forName(mapperClass));
        } catch (ClassNotFoundException e) {
            throw new MapperException("注册通用Mapper[" + mapperClass + "]失败,找不到该通用Mapper!");
        }
    }
    
    public void registerMapper(Class<?> mapperClass) {
        if (!registerMapper.containsKey(mapperClass)) {
            registerClass.add(mapperClass);
            // 注册Mapper的Class和通过通用Mapper接口获取对应的MapperTemplate,后文详解#2
            registerMapper.put(mapperClass, fromMapperClass(mapperClass));
        }
        // 自动注册继承的接口
        Class<?>[] interfaces = mapperClass.getInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            for (Class<?> anInterface : interfaces) {
                // 递归注册Mapper接口继承的其他接口如BaseMapper接口
                registerMapper(anInterface);
            }
        }
    }
    
    private MapperTemplate fromMapperClass(Class<?> mapperClass) {
        // 获得接口中的方法,BaseMapper接口中没有方法,SelectOneMapper接口中有selectOne方法
        Method[] methods = mapperClass.getDeclaredMethods();
        Class<?> templateClass = null;
        Class<?> tempClass = null;
        Set<String> methodSet = new HashSet<String>();
        for (Method method : methods) {
            // 判断接口的方法中是否有SelectProvider注解修饰
            if (method.isAnnotationPresent(SelectProvider.class)) {
                SelectProvider provider = method.getAnnotation(SelectProvider.class);
                tempClass = provider.type();
                methodSet.add(method.getName());
            } else if (method.isAnnotationPresent(InsertProvider.class)) {
                InsertProvider provider = method.getAnnotation(InsertProvider.class);
                tempClass = provider.type();
                methodSet.add(method.getName());
            } else if (method.isAnnotationPresent(DeleteProvider.class)) {
                DeleteProvider provider = method.getAnnotation(DeleteProvider.class);
                tempClass = provider.type();
                methodSet.add(method.getName());
            } else if (method.isAnnotationPresent(UpdateProvider.class)) {
                UpdateProvider provider = method.getAnnotation(UpdateProvider.class);
                tempClass = provider.type();
                methodSet.add(method.getName());
            }
            if (templateClass == null) {
            // 将注解中的type属性值赋值给templateClass变量,如BaseSelectProvider
                templateClass = tempClass;
            } else if (templateClass != tempClass) {
                throw new MapperException("一个通用Mapper中只允许存在一个MapperTemplate子类!");
            }
        }
        // 判断templateClass变量是否为null或者判断templateClass变量值是否继承自MapperTemplate如BaseSelectProvider
        if (templateClass == null || !MapperTemplate.class.isAssignableFrom(templateClass)) {
            templateClass = EmptyProvider.class;
        }
        MapperTemplate mapperTemplate = null;
        try {
            // 通过构造反射实例化MapperTemplate如BaseSelectProvider,构造参数为Mapper的Class(如SelectOneMapper.class)、MapperHelper.class
            mapperTemplate = (MapperTemplate) templateClass.getConstructor(Class.class, MapperHelper.class).newInstance(mapperClass, this);
        } catch (Exception e) {
            throw new MapperException("实例化MapperTemplate对象失败:" + e.getMessage());
        }
        // 注册方法
        for (String methodName : methodSet) {
            try {
            // 注册templateClass中的方法如BaseSelectProvider中的selectOne方法
                mapperTemplate.addMethodMap(methodName, templateClass.getMethod(methodName, MappedStatement.class));
            } catch (NoSuchMethodException e) {
                throw new MapperException(templateClass.getCanonicalName() + "中缺少" + methodName + "方法!");
            }
        }
        // 返回Mapper模板对象
        return mapperTemplate;
    }
}

MapperScannerConfigurer在后置处理时,注册了tk.mybatis.spring.mapper.MapperFactoryBean,为每个mapper接口设置了MappedStatment。到这里我们的第二步就完成了。

@Override
    protected void checkDaoConfig() {
        // 调用父类org.mybatis.spring.mapper.MapperFactoryBean,解析Mapper的注解,为Configuration注册Mapper
        // 如解析类注解注册Cache,解析方法注解注册MappedStatment(包括parameterType,SqlSource,ResultMap等)
        super.checkDaoConfig();
        // 判断Mapper接口是否继承了通用Mapper,getObjectType()为当前Mapper接口
        if (mapperHelper.isExtendCommonMapper(getObjectType())) {
            // 处理Configuration,为MappedStatment重新设置SqlSource
            mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
        }
    }

这里是MapperFactoryBean调用MapperHelper的核心代码:

    public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
        String prefix;
        if (mapperInterface != null) {
            prefix = mapperInterface.getCanonicalName();
        } else {
            prefix = "";
        }
        // 遍历所有的MappedStatment
        for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
                    // 判断是否解析注解生成的SqlSource
                    if (ms.getSqlSource() instanceof ProviderSqlSource) {
                        // 重新为MappedStatment设置SqlSource
                        setSqlSource(ms);
                    }
                }
            }
        }
    }
    
    public void setSqlSource(MappedStatement ms) {
        // 从缓存中获得MapperTemplate,如BaseSelectProvider
        MapperTemplate mapperTemplate = msIdCache.get(ms.getId());
        try {
            if (mapperTemplate != null) {
                mapperTemplate.setSqlSource(ms);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public void setSqlSource(MappedStatement ms) throws Exception {
        if (this.mapperClass == getMapperClass(ms.getId())) {
            throw new RuntimeException("请不要配置或扫描通用Mapper接口类:" + this.mapperClass);
        }
        // 从缓存中获得方法,如selectOne方法
        Method method = methodMap.get(getMethodName(ms));
        try {
            //第一种,直接操作ms,不需要返回值
            if (method.getReturnType() == Void.TYPE) {
                method.invoke(this, ms);
            }
            //第二种,返回SqlNode
            else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
                SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
                DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
                setSqlSource(ms, dynamicSqlSource);
            }
            //第三种,返回xml形式的sql字符串
            else if (String.class.equals(method.getReturnType())) {
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; // 方法反射调用,如selectOne方法,后文详解#3
                String xmlSql = (String) method.invoke(this, ms);
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; // 通过LanguageDriver创建SqlSource
                SqlSource sqlSource = createSqlSource(ms, xmlSql);
                // 替换原有的SqlSource,后文详解#4
                setSqlSource(ms, sqlSource);
            } else {
                throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!");
            }
            //cache
            checkCache(ms);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e);
        }
    }

这里还有一个细节,SQL是怎么动态生成的呢,每个MapperTemplate的注册方法都有xml格式的SQL,以BaseSelectProvider的selectOne为例,原理是通过解析model类上的注解,并缓存到entityClassMap。

    public String selectOne(MappedStatement ms) {
        // 从MappedStatment中拿到MapperClass,如com.wjz.StudentMapper.class
        // 再拿到StudentMapper的泛型类型,如Student.class
        Class<?> entityClass = getEntityClass(ms);
        // 修改返回值类型为实体类型
        setResultType(ms, entityClass);
        StringBuilder sql = new StringBuilder();
        // 拼接查询的字段
        sql.append(SqlHelper.selectAllColumns(entityClass));
        // 拼接表名,表名从EntityTable中的name属性中拿
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        // 拼接where条件
        sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }
    
    public Class<?> getEntityClass(MappedStatement ms) {
        String msId = ms.getId();
        if (entityClassMap.containsKey(msId)) {
            // 从缓存中获得
            return entityClassMap.get(msId);
        } else {
            Class<?> mapperClass = getMapperClass(msId);
            Type[] types = mapperClass.getGenericInterfaces();
            for (Type type : types) {
                if (type instanceof ParameterizedType) {
                    ParameterizedType t = (ParameterizedType) type;
                    if (t.getRawType() == this.mapperClass || this.mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
                        Class<?> returnType = (Class<?>) t.getActualTypeArguments()[0];
                        // 获取该类型后,第一次对该类型进行初始化
                        EntityHelper.initEntityNameMap(returnType, mapperHelper.getConfig());
                        // 放置到缓存中
                        entityClassMap.put(msId, returnType);
                        return returnType;
                    }
                }
            }
        }
        throw new RuntimeException("无法获取Mapper<T>泛型类型:" + msId);
    }