• Mybatis JPA-集成方案+源码


    2018-04-18 update

    当前文章已过时,请访问代码仓库查看当前版本wiki。

    github https://github.com/cnsvili/mybatis-jpa

    gitee https://gitee.com/svili/mybatis-jpa

    --------------------------------------

    源码地址(git):https://github.com/LittleNewbie/mybatis-jpa

    一、Mybatis简介

    mybatis中文官方文档:http://www.mybatis.org/mybatis-3/zh/index.html

    简介是为后面用到的内容做铺垫,熟悉mybatis的朋友可以直接跳过,到第二章节。

    关于mybatis-jpa的使用方式,请参见博文:http://www.cnblogs.com/svili/p/6828077.html

    1.1 SqlSession

    Mybatis中3个重要的概念:Configuration(容器),SqlSessionFactory(工厂),SqlSession;

    相对于Spring中的applicationContext,BeanFactory,Bean。

    不同之处在于SqlSession包含了所有的SQL方法,即这个SqlSession有且只有一个。SqlSession可以执行mybatis中注册的所有方法。官方示例说明

    <!-- SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
    你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:-->
    SqlSession session = sqlSessionFactory.openSession();
    try {
      Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
    } finally {
      session.close();
    }
    
    <!-- 映射器实例(Mapper Instances)-->
    SqlSession session = sqlSessionFactory.openSession();
    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      // do work
    } finally {
      session.close();
    }

    1.2 Namespace

    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--  xml中命名空间与java中Mapper接口一致  -->
    <mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    
    </mapper>

    1.3 ResultType 和 ResultMap

    ResultMap是myabtis最重要最强大的元素,是SQL列名columnName与POJO属性名filedName的高级结果映射。对ResultMap不熟悉的朋友可以阅读官方文档了解。

    ResultType可以理解为mybatis自动映射生成的简单的ResultMap,当POJO中的filedName与数据库ColumnName不一致时,无法完成映射。

    1.4 javaType和typeHandler

    ResultMap中使用

    <resultMap id="userResultMap" type="User">
      <!--  主键   -->
      <id property="id" column="user_id" />
      <!--  当column类型与field类型不一致时,需指定javaType或typeHandler,二者选其一即可。   -->
      <!--  简单的类型转换只需指定javaType即可,需要逻辑处理的使用typeHandler  -->
      <result property="remark" column="remark" javaType="string"/>
      <result property="effective" column="is_effective" typeHandler="BooleanTypeHandler"/>
    </resultMap>

    1.5 jdbcType

    官方说明:

    jdbcType JDBC 类型是 仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要,而不是 MyBatis 的。

    NOTE: 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。你可以自己通过阅读预处理语句的 setNull() 方法的 JavaDocs 文档来研究这种情况。java.sql.PreparedStatement.setNull()

    示例:

    <insert id="insertAuthor">
      insert into Author
        (id, username)
      values
        <!--  请注意,当sql中参数可能为null时,需要指定jdbcType,不然会出错  -->
        ('1', #{username,jdbcType=VARCHAR})
    </insert>

    1.6 MappedStatement

    Mapper中的方法(方法签名和可执行的sql语句)会被封装为MappedStatement注册到Configuration中。

    详见mybatis源码MapperBuilderAssistant.addMappedStatement(args);

    二、Mybatis JPA

    2.1 需求

    1)首先,我们希望能够与spring集成,使用spring的依赖注入。

    2)其次,我们希望能够兼容spring-mybatis集成的代码,拒绝污染。

    3)解析注册ResultMap和MappedStatement。

    2.2 主线

    1)参考spring data jpa,使用@RepositoryDefinition注解,标记需要自动生成sql的dao。

    我们使用@MapperDefinition和@StatementDefinition注解,标记需要自动生成sql的dao和method。

    这个是关键,既保证了不污染原有代码,又可以使用spring-mybatis已经实现的依赖注入。

    我们只需要在此基础上,对特定注解标注的mapper类和方法做处理即可。

    2)参考spring-mybatis

    MapperScannerConfigurer,扫描mapper并注册到mybatis Configuration中,继而生成代理类。

    MapperAnnotationBuilder实现java注解生成ResultMap和MappedStatement。

    2.3 入口-MapperEnhancerScaner

    在spring容器初始化后,对@MapperDefinition标注的mapper类进行扫描。

    2.4 解析POJO 生成ResultMap

    重点:columnName与fieldName映射,特殊字段的jdbcType和typeHandler。

    1)columnName与fieldName映射,使用JPA注解 @Cloumn即可,但是,我们希望能够自动转换驼峰与下划线风格,即对于符合规范命名的,不需要注解,直接映射。参见:PersistentUtil,ColumnNameUtil。

     /**
         * 将驼峰标识转换为下划线
         * 
         * @param text
         * @return camel
         */
        public static String camelToUnderline(String text) {
            if (text == null || "".equals(text.trim())) {
                return "";
            }
            StringBuilder result = new StringBuilder(text.length() + 1);
            result.append(text.substring(0, 1));
            for (int i = 1; i < text.length(); i++) {
                if (!Character.isLowerCase(text.charAt(i))) {
                    result.append('_');
                }
                result.append(text.substring(i, i + 1));
            }
            return result.toString().toLowerCase();
        }
    
        /**
         * 将下划线标识转换为驼峰
         * 
         * @param text
         * @return underline
         */
        public static String underlineToCamel(String text) {
            if (text == null || "".equals(text.trim())) {
                return "";
            }
            int length = text.length();
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < length; i++) {
                char c = text.charAt(i);
                if (c == '_') {
                    if (++i < length) {
                        result.append(Character.toUpperCase(text.charAt(i)));
                    }
                } else {
                    result.append(c);
                }
            }
            return result.toString();
        }        
    ColunNameUtil

    2)jdbcType和typeHandler

    处理了以下3种类型:POJO中的Enum,Boolean,以及数据库中的CLOB,代码见MybatisColumnMeta。

    需要强调说明的是,这里为所有的field都声明了jdbcType,是为了规避sql中参数为null时,产生异常。

    /** meta resolver */
        private static class ColumnMetaResolver {
    
            public static String resolveJdbcAlias(Field field) {
    
                Class<?> fieldType = field.getType();
                if (field.getType().isEnum()) {
                    if (field.isAnnotationPresent(Enumerated.class)) {
                        // 获取注解对象
                        Enumerated enumerated = field.getAnnotation(Enumerated.class);
                        // 设置了value属性
                        if (enumerated.value() == EnumType.ORDINAL) {
                            return "INTEGER";
                        }
                    }
                    return "VARCHAR";
                }
                if (field.isAnnotationPresent(Lob.class)) {
                    if (String.class.equals(fieldType)) {
                        return "CLOB";
                    }
                }
                if (Integer.class.equals(fieldType)) {
                    return "INTEGER";
                }
                if (Double.class.equals(fieldType)) {
                    return "DOUBLE";
                }
                if (Float.class.equals(fieldType)) {
                    return "FLOAT";
                }
                if (String.class.equals(fieldType)) {
                    return "VARCHAR";
                }
                // date类型需声明
                if (java.util.Date.class.isAssignableFrom(fieldType)) {
                    return "TIMESTAMP";
                }
                return null;
            }
    
            public static JdbcType resolveJdbcType(String alias) {
                if (alias == null) {
                    return null;
                }
                try {
                    return JdbcType.valueOf(alias);
                } catch (IllegalArgumentException e) {
                    throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
                }
            }
    
            @SuppressWarnings("unchecked")
            public static Class<? extends TypeHandler<?>> resolveTypeHandler(Field field) {
                Class<? extends TypeHandler<?>> typeHandlerClass = null;
                if (field.getType().isEnum()) {
                    typeHandlerClass = (Class<? extends TypeHandler<?>>) EnumTypeHandler.class;
                    if (field.isAnnotationPresent(Enumerated.class)) {
                        // 获取注解对象
                        Enumerated enumerated = field.getAnnotation(Enumerated.class);
                        // 设置了value属性
                        if (enumerated.value() == EnumType.ORDINAL) {
                            typeHandlerClass = (Class<? extends TypeHandler<?>>) EnumOrdinalTypeHandler.class;
                        }
                    }
                }
    
                if (field.getType().equals(Boolean.class)) {
                    typeHandlerClass = (Class<? extends TypeHandler<?>>) BooleanTypeHandler.class;
                }
                return typeHandlerClass;
            }
        }
    ColumnMetaResolver

    3)ResultMap注册

    见ResultMapAdapter.parseResultMap(args);

    2.5 MappedStatement注册

    分类处理,select需要用到ResultMap,默认为Pojo.getSimpleName() + "ResultMap";

    insert和insertSelective的区别:在于null值的处理,假设column_1在数据库设置了默认值,而参数中的field_1为null值,则insert 在数据库写入null,而insertSelective写入数据库默认值.

    需要特别说明的是,动态SQL需要使用"<script></script>"标签包围。

    对于各种sql方法的语句生成方法,详见com.mybatis.jpa.statement.builder包下的类。

    这里以InsertSelective和select为例

    public class InsertSelectiveBuilder implements StatementBuildable {
    
        @Override
        public String buildSQL(PersistentMeta persistentMeta, Method method) {
            // columns
            StringBuilder columns = new StringBuilder();
            columns.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");
            // values
            StringBuilder values = new StringBuilder();
            values.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");
            for (MybatisColumnMeta columnMeta : persistentMeta.getColumnMetaMap().values()) {
                // columns
                columns.append("<if test='" + columnMeta.getProperty() + "!= null'> ");
                columns.append(columnMeta.getColumnName() + ", ");
                columns.append("</if> ");
                // values
                values.append("<if test='" + columnMeta.getProperty() + "!= null'> ");
                values.append(SqlAssistant.resolveSqlParameter(columnMeta) + ", ");
                values.append("</if> ");
            }
    
            columns.append("</trim> ");
            values.append("</trim> ");
    
            return "<script>" + "INSERT INTO " + persistentMeta.getTableName() + columns.toString() + " VALUES "
                    + values.toString() + "</script>";
        }
    
        @Override
        public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {
            // 方法名
            adapter.setMethodName(method.getName());
            // 参数类型
            adapter.setParameterTypeClass(persistentMeta.getType());
            // sqlScript
            adapter.setSqlScript(buildSQL(persistentMeta, method));
            // 返回值类型
            adapter.setResultType(int.class);
            adapter.setResultMapId(null);
    
            adapter.setSqlCommandType(SqlCommandType.INSERT);
    
            // 主键策略
            adapter.setKeyGenerator(new NoKeyGenerator());
            adapter.setKeyProperty(null);
            adapter.setKeyColumn(null);
    
            adapter.parseStatement();
    
        }
    
    }
    InsertSelectiveBuilder
    public class SelectBuilder implements StatementBuildable {
    
        @Override
        public String buildSQL(PersistentMeta persistentMeta, Method method) {
            return "SELECT " + persistentMeta.getColumnNames() + " FROM " + persistentMeta.getTableName()
                    + SqlAssistant.buildSingleCondition(method, persistentMeta);
        }
    
        @Override
        public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {
            // 方法名
            adapter.setMethodName(method.getName());
            // 参数类型
            if (method.getParameterTypes().length > 0) {
                // Mybatis mapper 方法最多支持一个参数,先设置成Object.class,mybatis会在sql中解析
                adapter.setParameterTypeClass(Object.class);
            } else {
                adapter.setParameterTypeClass(void.class);
            }
    
            String orderBy = " ";
    
            if (method.isAnnotationPresent(OrderBy.class)) {
                orderBy = " order by " + method.getAnnotation(OrderBy.class).value();
            }
    
            // sqlScript
            adapter.setSqlScript(buildSQL(persistentMeta, method) + orderBy);
            // 返回值类型
            adapter.setResultType(persistentMeta.getType());
            adapter.setResultMapId(persistentMeta.getType().getSimpleName() + "ResultMap");
    
            adapter.setSqlCommandType(SqlCommandType.SELECT);
    
            // 主键策略
            adapter.setKeyGenerator(new NoKeyGenerator());
            adapter.setKeyProperty(null);
            adapter.setKeyColumn(null);
    
            adapter.parseStatement();
    
        }
    SelectBuilder

    ok,以上就是mybatis-jpa的主要设计思路了,具体的细节,我已经尽可能的在代码中增加注释。

    关于mybatis-jpa的代码构建使用方式,请参见博文:http://www.cnblogs.com/svili/p/6828077.html

    由于个人能力有限,代码可能有些简陋,如有不妥之处,欢迎指正交流。

  • 相关阅读:
    SpringCloudAlibaba
    wechat kill
    使用vscode调试 pomelo
    Terry的学习笔记ASP.NET MVC 4 HELLO WORLD 分析编辑页面添加搜索页面
    Terry的学习笔记ASP.NET MVC 4 HELLO WORLD添加视图(View)
    Terry的学习笔记ASP.NET MVC 4 简介
    正则表达式总结
    自己编写的 objectDataSource 配合 GridView 实现分页
    Terry的学习笔记ASP.NET MVC 4 HELLO WORLD 从控制器访问模型中的数据
    Terry的学习笔记ASP.NET MVC 4 HELLO WORLD添加控制器(Controller)
  • 原文地址:https://www.cnblogs.com/svili/p/7232323.html
Copyright © 2020-2023  润新知