美文网首页
Mybatis Generator插件 - 实践

Mybatis Generator插件 - 实践

作者: liaomengge | 来源:发表于2019-10-21 21:36 被阅读0次

    关于 mybatis generator 插件,官方提供了一些参考的third-party-tools,但是在实际开发的项目中,遇到了一些相关的问题,以下是写相关记录(主要是配合 gradle 使用)。

    1. LombokPlugin 插件

    这个很多博客上都有,也就不再重复论了,贴一下 code

    public class LombokPlugin extends PluginAdapter {
    
        private final Set<Annotations> annotations;
    
        /**
         * LombokPlugin constructor
         */
        public LombokPlugin() {
            annotations = new LinkedHashSet<>(Annotations.values().length);
        }
    
        /**
         * @param warnings list of warnings
         * @return always true
         */
        @Override
        public boolean validate(List<String> warnings) {
            return true;
        }
    
        /**
         * Intercepts base record class generation
         *
         * @param topLevelClass     the generated base record class
         * @param introspectedTable The class containing information about the table as
         *                          introspected from the database
         * @return always true
         */
        @Override
        public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
            addAnnotations(topLevelClass);
            return true;
        }
    
        /**
         * Intercepts primary key class generation
         *
         * @param topLevelClass     the generated primary key class
         * @param introspectedTable The class containing information about the table as
         *                          introspected from the database
         * @return always true
         */
        @Override
        public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
            addAnnotations(topLevelClass);
            return true;
        }
    
        /**
         * Intercepts "record with blob" class generation
         *
         * @param topLevelClass     the generated record with BLOBs class
         * @param introspectedTable The class containing information about the table as
         *                          introspected from the database
         * @return always true
         */
        @Override
        public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass,
                                                          IntrospectedTable introspectedTable) {
            addAnnotations(topLevelClass);
            return true;
        }
    
        /**
         * Prevents all getters from being generated.
         * See SimpleModelGenerator
         *
         * @param method             the getter, or accessor, method generated for the specified
         *                           column
         * @param topLevelClass      the partially implemented model class
         * @param introspectedColumn The class containing information about the column related
         *                           to this field as introspected from the database
         * @param introspectedTable  The class containing information about the table as
         *                           introspected from the database
         * @param modelClassType     the type of class that the field is generated for
         */
        @Override
        public boolean modelGetterMethodGenerated(
                Method method,
                TopLevelClass topLevelClass,
                IntrospectedColumn introspectedColumn,
                IntrospectedTable introspectedTable,
                ModelClassType modelClassType) {
            return false;
        }
    
        /**
         * Prevents all setters from being generated
         * See SimpleModelGenerator
         *
         * @param method             the setter, or mutator, method generated for the specified
         *                           column
         * @param topLevelClass      the partially implemented model class
         * @param introspectedColumn The class containing information about the column related
         *                           to this field as introspected from the database
         * @param introspectedTable  The class containing information about the table as
         *                           introspected from the database
         * @param modelClassType     the type of class that the field is generated for
         * @return always false
         */
        @Override
        public boolean modelSetterMethodGenerated(
                Method method,
                TopLevelClass topLevelClass,
                IntrospectedColumn introspectedColumn,
                IntrospectedTable introspectedTable,
                ModelClassType modelClassType) {
            return false;
        }
    
        /**
         * Adds the lombok annotations' imports and annotations to the class
         *
         * @param topLevelClass the partially implemented model class
         */
        private void addAnnotations(TopLevelClass topLevelClass) {
            for (Annotations annotation : annotations) {
                topLevelClass.addImportedType(annotation.javaType);
                topLevelClass.addAnnotation(annotation.asAnnotation());
            }
        }
    
        @Override
        public void setProperties(Properties properties) {
            super.setProperties(properties);
    
            //@Data,@Builder,@NoArgsConstructor,@AllArgsConstructor is default annotation
            annotations.add(Annotations.DATA);
            annotations.add(Annotations.BUILDER);
            annotations.add(Annotations.NO_ARGS_CONSTRUCTOR);
            annotations.add(Annotations.ALL_ARGS_CONSTRUCTOR);
    
            for (String annotationName : properties.stringPropertyNames()) {
                if (annotationName.contains(".")) {
                    // Not an annotation name
                    continue;
                }
                String value = properties.getProperty(annotationName);
                if (!Boolean.parseBoolean(value)) {
                    // The annotation is disabled, skip it
                    continue;
                }
                Annotations annotation = Annotations.getValueOf(annotationName);
                if (annotation == null) {
                    continue;
                }
                String optionsPrefix = annotationName + ".";
                for (String propertyName : properties.stringPropertyNames()) {
                    if (!propertyName.startsWith(optionsPrefix)) {
                        // A property not related to this annotation
                        continue;
                    }
                    String propertyValue = properties.getProperty(propertyName);
                    annotation.appendOptions(propertyName, propertyValue);
                    annotations.add(annotation);
                    annotations.addAll(Annotations.getDependencies(annotation));
                }
            }
        }
    
        private enum Annotations {
            DATA("data", "@Data", "lombok.Data"),
            BUILDER("builder", "@Builder", "lombok.Builder"),
            ALL_ARGS_CONSTRUCTOR("allArgsConstructor", "@AllArgsConstructor", "lombok.AllArgsConstructor"),
            NO_ARGS_CONSTRUCTOR("noArgsConstructor", "@NoArgsConstructor", "lombok.NoArgsConstructor"),
            ACCESSORS("accessors", "@Accessors", "lombok.experimental.Accessors"),
            TO_STRING("toString", "@ToString", "lombok.ToString");
    
    
            private final String paramName;
            private final String name;
            private final FullyQualifiedJavaType javaType;
            private final List<String> options;
    
    
            Annotations(String paramName, String name, String className) {
                this.paramName = paramName;
                this.name = name;
                javaType = new FullyQualifiedJavaType(className);
                options = new ArrayList<>();
            }
    
            private static Annotations getValueOf(String paramName) {
                for (Annotations annotation : Annotations.values()) {
                    if (String.CASE_INSENSITIVE_ORDER.compare(paramName, annotation.paramName) == 0) {
                        return annotation;
                    }
                }
                return null;
            }
    
            private static Collection<Annotations> getDependencies(Annotations annotation) {
                if (annotation == ALL_ARGS_CONSTRUCTOR) {
                    return Collections.singleton(NO_ARGS_CONSTRUCTOR);
                } else {
                    return Collections.emptyList();
                }
            }
    
            // A trivial quoting.
            // Because Lombok annotation options type is almost String or boolean.
            private static String quote(String value) {
                if (Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value)) {
                    return value;
                }
                // case of boolean, not passed as an array.
                return value.replaceAll("[\\w]+", "\"$0\"");
            }
    
            private void appendOptions(String key, String value) {
                String keyPart = key.substring(key.indexOf(".") + 1);
                String valuePart = value.contains(",") ? String.format("{%s}", value) : value;
                options.add(String.format("%s=%s", keyPart, quote(valuePart)));
            }
    
            private String asAnnotation() {
                if (options.isEmpty()) {
                    return name;
                }
                StringBuilder sb = new StringBuilder();
                sb.append(name);
                sb.append("(");
                boolean first = true;
                for (String option : options) {
                    if (first) {
                        first = false;
                    } else {
                        sb.append(", ");
                    }
                    sb.append(option);
                }
                sb.append(")");
                return sb.toString();
            }
        }
    }
    
    2. ServicePlugin 插件

    主要是为了便于自动生成 Service 类

    public class ServicePlugin extends PluginAdapter {
    
        private String targetProject;
        private String targetPackage;
    
        @Override
        public boolean validate(List<String> list) {
            return true;
        }
    
        @Override
        public void setProperties(Properties properties) {
            super.setProperties(properties);
            String targetProject = this.properties.getProperty("targetProject");
            if (StringUtility.stringHasValue(targetProject)) {
                this.targetProject = targetProject;
            } else {
                throw new RuntimeException("targetProject 属性不能为空!");
            }
            String targetPackage = this.properties.getProperty("targetPackage");
            if (StringUtility.stringHasValue(targetPackage)) {
                this.targetPackage = targetPackage;
            } else {
                throw new RuntimeException("targetPackage 属性不能为空!");
            }
        }
    
        /**
         * @param introspectedTable
         * @return
         */
        @Override
        public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
            FullyQualifiedJavaType entityType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
            String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();
            String service = targetPackage + "." + domainObjectName + "Service";
            TopLevelClass topLevelClass = new TopLevelClass(new FullyQualifiedJavaType(service));
            topLevelClass.addImportedType(entityType);
            topLevelClass.addImportedType(new FullyQualifiedJavaType(service));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Service"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.springframework.beans.factory.annotation" +
                    ".Autowired"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("cn.mwee.service.base_framework.mysql.service" +
                    ".BaseService"));
            topLevelClass.addAnnotation("@Service(\"" + firstLetterLowerCase(domainObjectName + "Service") + "\")");
            topLevelClass.setVisibility(JavaVisibility.PUBLIC);
            topLevelClass.setSuperClass(new FullyQualifiedJavaType("BaseService<" + entityType.getShortName() + ">"));
            setMapperField(introspectedTable, topLevelClass);
            ElementUtil.addAuthorTag(topLevelClass);
            return Arrays.asList(new GeneratedJavaFile(topLevelClass, targetProject, new DefaultJavaFormatter()));
        }
    
        /**
         * @param introspectedTable
         * @param clazz
         */
        private void setMapperField(IntrospectedTable introspectedTable, TopLevelClass clazz) {
            // 实体类的类名
            String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();
            // Mapper类所在包的包名
            String mapperPackage = introspectedTable.getContext().getJavaClientGeneratorConfiguration().getTargetPackage();
            Field mapperField = new Field();
            // 设置Field的注解
            mapperField.addAnnotation("@Autowired");
            mapperField.setVisibility(JavaVisibility.PRIVATE);
            // 设置Field的类型
            mapperField.setType(new FullyQualifiedJavaType(domainObjectName + "Mapper"));
            // 设置Field的名称
            mapperField.setName(firstLetterLowerCase(domainObjectName) + "Mapper");
            // 将Field添加到对应的类中
            clazz.addField(mapperField);
            // 对应的类需要import Mapper类(使用全限定类名)
            clazz.addImportedType(new FullyQualifiedJavaType(mapperPackage + "." + domainObjectName + "Mapper"));
        }
    
        private String firstLetterLowerCase(String name) {
            char c = name.charAt(0);
            if (c >= 'A' && c <= 'Z') {
                String temp = String.valueOf(c);
                return name.replaceFirst(temp, temp.toLowerCase());
            }
            return name;
        }
    }
    
    3. 解决数据库 Unsigned 类型字段

    使用官方 mybatis generator 1.3.7 版本自动生产的实体类映射,是不能区分 Unsigned 和 Signed mysql 8.x(针对 java 项目,很容易出现类型字段溢出),尤其实际项目中,接手老的项目,之前一些数据库字段的设置,留下来的一些“坑”;

    • 官方方案:(github 上也有一些小伙伴提了相应的 issue,好像没有 fixed)

      <columnOverride> 元素覆盖指定的字段
      缺点:在针对很多表的时候,都需要手动设置这些字段,而且很容易丢失(可能导致线上问题)

    • 解决方案:

      通过 download 官方 mybatis generator 源码,发现是可以自动识别 Unsigned 和 Signed 字段属性的,故修改了源码,打包,重新编译,达到了想要的目的,不需要针对指定的字段每一个修改(在 github 上已经和官方作者沟通,接受了 issue,将于下一个版本 1.4.0 做增强),由于官方作者一直没发布 1.4.0,故公司内部打包如下 Jar 版本:mybatis-generator-maven-plugin-1.3.7.2.jar

    4. 如何自动生成 jdk8 时间映射

    之前是打算直接写插件直接自动生成,但是考虑到有些项目还是用的 jdk8 以前的时间,老的项目也不能强制要求 jdk8 时间版本,故下面 👇 只是展示如何处理.

    在生成的实体类中加入下面处理

    <columnOverride column="created_time" property="createdTime"
                                typeHandler="org.apache.ibatis.type.LocalDateTimeTypeHandler"
                                jdbcType="OTHER" javaType="java.time.LocalDateTime"/>
    <columnOverride column="updated_time" property="updatedTime"
                                typeHandler="org.apache.ibatis.type.LocalDateTimeTypeHandler"
                                jdbcType="OTHER" javaType="java.time.LocalDateTime"/>
    

    注:在 mybatis 使用 jdk8 时间时,需要注意的是,mybatis 3.5.1+和 druid 1.1.20 是不支持 jdk8 的时间戳的druid-jdk8,不过,现在已经修复了,等下一个版本估计就能解决了(当然,也可以切换到 HikariCP 数据源)

    5. 集成 gradle 插件

    市场上已经有很多 gradle 集成 mybatis generator,选择了其中一个比较好用的插件mybatis-generator-plugin(尊重原创作者),当然在公司内部处理,也针对这个插件做了相应的修改定制

    6. 附录

    针对上面 👆 的一些处理,生成的结构图如下:


    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

          本文标题:Mybatis Generator插件 - 实践

          本文链接:https://www.haomeiwen.com/subject/ckbxvctx.html