Mybatis代码生成系列(二) - mybatis-generator-gui使用原理

前言

在软件开发的世界中,自动化工具的使用可以极大地提高我们的工作效率。特别是在数据库操作方面,手动编写SQL语句和实体类、映射文件等不仅耗时,而且容易出错。MyBatis作为一款广泛使用的持久层框架,其代码生成器mybatis-generator可以帮助我们自动生成这些繁琐的代码。然而,mybatis-generator默认只支持MySQL数据库,对于其他数据库(如Oracle、PostgreSQL等)的支持并不完善。因此,本文将通过分析现有的开源项目mybatis-generator-gui,来探索如何实现一个支持多种数据库的MyBatis代码生成器。

本系列博客将分为以下几个部分:

  1. mybatis-generator-gui 使用介绍:首先,我们将介绍如何使用现有的开源项目mybatis-generator-gui进行可视化代码生成。我们将详细讲解如何配置项目,选择数据库,以及生成代码的过程。
  2. mybatis 代码生成原理探究:在这一部分,我们将深入mybatis-generator的内部,探究其代码生成的原理。我们将分析其配置文件的解析过程,以及如何根据配置信息生成对应的实体类、映射文件和XML配置文件。
  3. 自定义代码生成器实现:在前两部分的基础上,我们将实现自己的代码生成器。我们将重写mybatis-generator的核心代码,使其支持其他数据库。同时,我们也将优化生成的代码,使其更符合我们的项目需求。

通过本系列博客的学习,你将能够掌握MyBatis代码生成器的使用方法和原理,同时也能够根据自己的需求定制代码生成器。无论你是MyBatis初学者,还是有一定经验的开发者,我相信这都将对你的学习和工作有所帮助。让我们一起开始这个旅程吧!

其他文章

Mybatis代码生成系列(一)- mybatis-generator-gui开源项目介绍 - 掘金 (juejin.cn)

正文

本文会简要分析一下生成代码的源码,并且修改源码从而使得生成的代码满足我的要求:

  1. Mapper包含基本insert、delete、update和select

  2. 生成的mapper、model类文件需要添加上类注释 --未实现,后面完善

  3. 生成service代码

先验知识

JavaFX

mybatis-generator-gui是一个JavaFX项目,我们可以在idea中直接新建一个,对比一下,这样我们就大概了解源码的文件代码结构了

其中resources下面的fxml文件就是定义gui页面的源文件,style.css就是页面的样式源码, 在fxml通过下面这种方式引用

Controller文件夹下面的控制层类是用户处理鼠标和键盘输入的控制器文件

MainUI类就是启动类,其继承了javafx.application.Application, 通过@FXML注解关联fxml中的变量和响应事件。

代码生成

源码解析

MainUI中代码生成对应的响应事件是:generateCode

分析下con troller下对应的generateCode方法:

主要是用于生成代码的过程。让我来梳理一下主要逻辑:

  1. 首先进行页面控制,如果tableName为空,则显示一个警告对话框,提示用户在左侧选择数据库表,然后返回。
  2. 进行参数校验,如果validateConfig返回的结果不为空,则显示一个错误对话框,提示用户出现了错误,然后返回。
  3. 获取用户在UI上的选择和输入的配置信息,然后检查目录是否存在,如果不存在,则返回。
  4. 创建一个MybatisGeneratorBridge对象,并设置相关的配置信息。
  5. 创建一个UIProgressCallback对象,用于在代码生成过程中显示进度和状态的对话框。
  6. 将UIProgressCallback对象设置为MybatisGeneratorBridge的进度回调。
  7. 调用UIProgressCallback的show方法,显示一个非阻塞的对话框,不会等待用户响应。
  8. 进行一系列操作,包括SSH会话的建立、端口转发的处理、生成代码等。
  9. 在异常处理中,如果出现异常,则打印异常信息并显示一个错误对话框,然后关闭图片处理状态控制器,并显示相应的错误状态。 总的来说,这段代码主要是用于生成代码的过程,并在过程中显示对话框、处理异常等。同时也涉及到了一些并发处理,比如使用Task来延迟关闭图片处理状态控制器。

其中的核心就是8, 生成代码操作:

它包含了一系列操作,主要是用于生成MyBatis的配置文件和相关代码。让我来梳理一下这段代码的主要逻辑:

  1. 创建一个MyBatis Generator的Configuration对象,并添加一个Context对象。
  2. 设置一些属性,比如设置java文件编码格式为UTF-8,设置连接数据库的类路径,设置自动限定关键字等。
  3. 针对不同的数据库类型进行特定的配置,比如设置数据库的schema、catalog、是否使用表名前缀等。
  4. 添加GeneratedKey主键生成的配置,设置实体类的包名和生成路径等。
  5. 配置JDBC连接信息,包括驱动类、连接URL、用户名和密码等。
  6. 设置Java模型生成器、SQL映射文件生成器、Java客户端生成器的配置信息。
  7. 配置注释生成器,设置生成的Java文件编码格式。
  8. 添加一些自定义的插件配置,比如序列化插件、Lombok插件、toString/hashCode/equals插件、分页插件等。
  9. 根据配置生成MyBatis的配置文件和相关代码。
  10. 如果选择覆盖XML文件,删除旧的XML文件并生成新的XML文件。 总的来说,这段代码主要是用于根据配置生成MyBatis的配置文件和相关代码,包括实体类、映射文件、DAO接口等。

配置信息

java 复制代码
public class GeneratorConfig {

    /**
     * 本配置的名称
     */
    private String name;

    // 驱动类路径
    private String connectorJarPath;

    // 项目所在目录
    private String projectFolder;

    // 实体类包名
    private String modelPackage;

    // 生成的实体类存放路径
    private String modelPackageTargetFolder;

    // mapper接口包名
    private String daoPackage;

    // 生成的mapper接口存放路径

    private String daoTargetFolder;

    // 自定义接口名称, 即生成的mapper接口文件名称
    private String mapperName;

    // 映射xml文件包名
    private String mappingXMLPackage;

    // 生成的映射xml文件存放目录
    private String mappingXMLTargetFolder;

    // 表名,即数据库源表名
    private String tableName;

    // 生成的实体类名
    private String domainObjectName;

    // 是否使用分页插件
    private boolean offsetLimit;

    // 生成实体域注释(来自表注释)
    private boolean comment;

    // 覆盖原XML
    private boolean overrideXML;

    // 生成toString/hashCode/equals方法
    private boolean needToStringHashcodeEquals;

    // LombokPlugin
    private boolean useLombokPlugin;

    // select 增加ForUpdate
    private boolean needForUpdate;

    // DAO使用 @Repository 注解
    private boolean annotationDAO;

    // 生成JPA注解
    private boolean annotation;

    // 使用实际的列名
    private boolean useActualColumnNames;

    // 使用Example
    private boolean useExample;

    // 主键(选填)
    private String generateKeys;

    // 生成文件的编码
    private String encoding;

    // 启用as别名查询
    private boolean useTableNameAlias;

    // DAO方法抽出到公共父接口
    private boolean useDAOExtendStyle;

    // 使用Schema前缀
    private boolean useSchemaPrefix;

    // SR310: Date and Time API
    private boolean jsr310Support;
    }

适配

Mapper中添加select、delete和update

生成的mapper文件中包含基本的insert、delete、update和select

配置是在org.mybatis.generator.config.TableConfiguration中配置的,源码中可以看到,有8个可配置项, 默认都是开启,其中

  • insertStatementEnabled配置为true, 会生成insert,insertSelective
  • selectByPrimaryKeyStatementEnabled配置为true, 会生成selectByPrimaryKey
  • selectByExampleStatementEnabled配置为true, 会生成selectByExample
  • updateByPrimaryKeyStatementEnabled 配置为true, 会生成updateByPrimaryKey, updateByPrimaryKeySelective
  • updateByExampleStatementEnabled配置为true, 会生成updateByExample,updateByExampleSelective
  • deleteByPrimaryKeyStatementEnabled 配置为true,会生成deleteByPrimaryKey
  • deleteByExampleStatementEnabled 配置为true, 会生成:deleteByExample
  • countByExampleStatementEnabled 配置为true, 会生成countByExample

而我不需要example,所以将example配置都设为false

然而源码中已经设置了,那么为什么之前生成的只有insert和insert

Selective呢?原来是因为我的源表没有设置主键,导致对应的select、update和delete没有生成!!最后添加主键后,最后生成我需要的mapper接口文件啦。

Model中添加字段注解

Model中添加字段注解, gui中选择生成JPA注解

生成service和controller代码

在plugin新增自定义生成service和controller代码插件

代码如下:

java 复制代码
package com.zzg.mybatis.generator.plugins;

import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;

import java.util.ArrayList;
import java.util.List;

import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;

public class ServiceAndControllerGeneratorPlugin extends PluginAdapter  {

    // 项目目录,一般为 src/main/java
    private String targetProject;

    // service包名,如:com.thinkj2ee.cms.service.service
    private String servicePackage;

    // service实现类包名,如:com.thinkj2ee.cms.service.service.impl
    private String serviceImplPackage;
    // Controlle类包名,如:com.thinkj2ee.cms.service.controller
    private String controllerPackage;
    // service接口名前缀
    private String servicePreffix;

    // service接口名后缀
    private String serviceSuffix;

    // service接口的父接口
    private String superServiceInterface;

    // service实现类的父类
    private String superServiceImpl;
    // controller类的父类
    private String superController;

    // dao接口基类
    private String superDaoInterface;

    // Example类的包名
    private String examplePacket;

    private String recordType;

    private String modelName;

    private FullyQualifiedJavaType model;

    private String serviceName;
    private String serviceImplName;
    private String controllerName;

    @Override
    public boolean validate(List<String> warnings) {
        boolean valid = true;

       /* if (!stringHasValue(properties
                .getProperty("targetProject"))) { //$NON-NLS-1$
            warnings.add(getString("ValidationError.18", //$NON-NLS-1$
                    "MapperConfigPlugin", //$NON-NLS-1$
                    "targetProject")); //$NON-NLS-1$
            valid = false;
        }
        if (!stringHasValue(properties.getProperty("servicePackage"))) { //$NON-NLS-1$
            warnings.add(getString("ValidationError.18", //$NON-NLS-1$
                    "MapperConfigPlugin", //$NON-NLS-1$
                    "servicePackage")); //$NON-NLS-1$
            valid = false;
        }
        if (!stringHasValue(properties.getProperty("serviceImplPackage"))) { //$NON-NLS-1$
            warnings.add(getString("ValidationError.18", //$NON-NLS-1$
                    "MapperConfigPlugin", //$NON-NLS-1$
                    "serviceImplPackage")); //$NON-NLS-1$
            valid = false;
        }
*/
        targetProject = properties.getProperty("targetProject");
        servicePackage = properties.getProperty("servicePackage");
        serviceImplPackage = properties.getProperty("serviceImplPackage");
        servicePreffix = properties.getProperty("servicePreffix");
        servicePreffix = stringHasValue(servicePreffix) ? servicePreffix : "";
        serviceSuffix = properties.getProperty("serviceSuffix");
        serviceSuffix = stringHasValue(serviceSuffix) ? serviceSuffix : "";
        superServiceInterface = properties.getProperty("superServiceInterface");
        superServiceImpl = properties.getProperty("superServiceImpl");
        superDaoInterface = properties.getProperty("superDaoInterface");
        controllerPackage = properties.getProperty("controllerPackage");
        superController = properties.getProperty("superController");

        return valid;
    }

    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        recordType = introspectedTable.getBaseRecordType();
        modelName = recordType.substring(recordType.lastIndexOf(".") + 1);
        model = new FullyQualifiedJavaType(recordType);
        serviceName = servicePackage + "." + servicePreffix + modelName + serviceSuffix;
        serviceImplName = serviceImplPackage + "." + modelName + serviceSuffix+"Impl";
        examplePacket=recordType.substring(0,recordType.lastIndexOf("."));
        controllerName=controllerPackage.concat(".").concat(modelName).concat("Controller");
        List<GeneratedJavaFile> answer = new ArrayList<>();
        GeneratedJavaFile gjf = generateServiceInterface(introspectedTable);
        GeneratedJavaFile gjf2 = generateServiceImpl(introspectedTable);
        GeneratedJavaFile gjf3 = generateController(introspectedTable);
        answer.add(gjf);
        answer.add(gjf2);
        answer.add(gjf3);
        return answer;
    }

    // 生成service接口
    private GeneratedJavaFile generateServiceInterface(IntrospectedTable introspectedTable) {
        FullyQualifiedJavaType service = new FullyQualifiedJavaType(serviceName);
        Interface serviceInterface = new Interface(service);
        serviceInterface.setVisibility(JavaVisibility.PUBLIC);
        // 添加父接口
        if(stringHasValue(superServiceInterface)) {
            String superServiceInterfaceName = superServiceInterface.substring(superServiceInterface.lastIndexOf(".") + 1);
            serviceInterface.addImportedType(new FullyQualifiedJavaType(superServiceInterface));
            serviceInterface.addImportedType(new FullyQualifiedJavaType(recordType));
            serviceInterface.addSuperInterface(new FullyQualifiedJavaType(superServiceInterfaceName + "<" + modelName + ">"));
        }
        GeneratedJavaFile gjf = new GeneratedJavaFile(serviceInterface, targetProject, context.getJavaFormatter());
        return gjf;
    }

    // 生成serviceImpl实现类
    private GeneratedJavaFile generateServiceImpl(IntrospectedTable introspectedTable) {
        FullyQualifiedJavaType service = new FullyQualifiedJavaType(serviceName);
        FullyQualifiedJavaType serviceImpl = new FullyQualifiedJavaType(serviceImplName);
        TopLevelClass clazz = new TopLevelClass(serviceImpl);
        //描述类的作用域修饰符
        clazz.setVisibility(JavaVisibility.PUBLIC);
        //描述类 引入的类
        clazz.addImportedType(service);
        //描述类 的实现接口类
        clazz.addSuperInterface(service);
        if(stringHasValue(superServiceImpl)) {
            String superServiceImplName = superServiceImpl.substring(superServiceImpl.lastIndexOf(".") + 1);
            clazz.addImportedType(superServiceImpl);
            clazz.addImportedType(recordType);
            clazz.setSuperClass(superServiceImplName + "<" + modelName + ">");
        }
        clazz.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Service"));
        clazz.addAnnotation("@Service");

        String daoFieldType = introspectedTable.getMyBatis3JavaMapperType();
        String daoFieldName = firstCharToLowCase(daoFieldType.substring(daoFieldType.lastIndexOf(".") + 1));
        //描述类的成员属性
        Field daoField = new Field(daoFieldName, new FullyQualifiedJavaType(daoFieldType));
        clazz.addImportedType(new FullyQualifiedJavaType(daoFieldType));
        clazz.addImportedType(new FullyQualifiedJavaType("org.springframework.beans.factory.annotation.Autowired"));
        //描述成员属性 的注解
        daoField.addAnnotation("@Autowired");
        //描述成员属性修饰符
        daoField.setVisibility(JavaVisibility.PRIVATE);
        clazz.addField(daoField);

        //描述 方法名
        Method method = new Method("getMapper");
        //方法注解
        method.addAnnotation("@Override");
        FullyQualifiedJavaType methodReturnType = new FullyQualifiedJavaType("Object");
        //返回值
        method.setReturnType(methodReturnType);
        //方法体,逻辑代码
        method.addBodyLine("return " + daoFieldName + ";");
        //修饰符
        method.setVisibility(JavaVisibility.PUBLIC);
        clazz.addMethod(method);


        Method method1 = new Method("getExample");
        method1.addAnnotation("@Override");
        FullyQualifiedJavaType methodReturnType1 = new FullyQualifiedJavaType("Object");
        clazz.addImportedType(new FullyQualifiedJavaType(examplePacket.concat(".").concat(modelName).concat("Example")));
        method1.setReturnType(methodReturnType1);
        method1.addBodyLine("return new " + modelName + "Example();");
        method1.setVisibility(JavaVisibility.PUBLIC);
        clazz.addMethod(method1);

        GeneratedJavaFile gjf2 = new GeneratedJavaFile(clazz, targetProject, context.getJavaFormatter());
        return gjf2;
    }


    // 生成controller类
    private GeneratedJavaFile generateController(IntrospectedTable introspectedTable) {

        FullyQualifiedJavaType controller = new FullyQualifiedJavaType(controllerName);
        TopLevelClass clazz = new TopLevelClass(controller);
        //描述类的作用域修饰符
        clazz.setVisibility(JavaVisibility.PUBLIC);

        //添加@Controller注解,并引入相应的类
        clazz.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RestController"));
        clazz.addAnnotation("@RestController");
        //添加@RequestMapping注解,并引入相应的类
        clazz.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestMapping"));
        clazz.addAnnotation("@RequestMapping("/"+firstCharToLowCase(modelName)+"")");
        //添加@Api注解,并引入相应的类
        clazz.addImportedType(new FullyQualifiedJavaType("io.swagger.annotations.Api"));
        String controllerSimpleName = controllerName.substring(controllerName.lastIndexOf(".") + 1);
        clazz.addAnnotation("@Api(tags = ""+controllerSimpleName+"", description = ""+controllerSimpleName+"")");

        //引入controller的父类和model,并添加泛型
        if(stringHasValue(superController)) {
            clazz.addImportedType(superController);
            clazz.addImportedType(recordType);
            FullyQualifiedJavaType superInterfac = new FullyQualifiedJavaType(superController+"<"+modelName+">");
            clazz.addSuperInterface(superInterfac);
        }

        //引入Service
        FullyQualifiedJavaType service = new FullyQualifiedJavaType(serviceName);
        clazz.addImportedType(service);

        //添加Service成员变量
        String serviceFieldName = firstCharToLowCase(serviceName.substring(serviceName.lastIndexOf(".") + 1));
        Field daoField = new Field(serviceFieldName, new FullyQualifiedJavaType(serviceName));
        clazz.addImportedType(new FullyQualifiedJavaType(serviceName));
        clazz.addImportedType(new FullyQualifiedJavaType("org.springframework.beans.factory.annotation.Autowired"));
        //描述成员属性 的注解
        daoField.addAnnotation("@Autowired");
        //描述成员属性修饰符
        daoField.setVisibility(JavaVisibility.PRIVATE);
        clazz.addField(daoField);


        //描述 方法名
        Method method = new Method("getService");
        //方法注解
        method.addAnnotation("@Override");
//        String simpleSuperServiceName = superServiceInterface.substring(superServiceInterface.lastIndexOf(".") + 1);
//        FullyQualifiedJavaType methodReturnType = new FullyQualifiedJavaType(simpleSuperServiceName+"<"+modelName+">");
        //返回类型
//        method.setReturnType(methodReturnType);
        //方法体,逻辑代码
        method.addBodyLine("return " + serviceFieldName + ";");
        //修饰符
        method.setVisibility(JavaVisibility.PUBLIC);
//        clazz.addImportedType(superServiceInterface);
//        clazz.addMethod(method);


        GeneratedJavaFile gjf2 = new GeneratedJavaFile(clazz, targetProject, context.getJavaFormatter());
        return gjf2;
    }


    private String firstCharToLowCase(String str) {
        char[] chars = new char[1];
        //String str="ABCDE1234";
        chars[0] = str.charAt(0);
        String temp = new String(chars);
        if(chars[0] >= 'A'  &&  chars[0] <= 'Z') {
            return str.replaceFirst(temp,temp.toLowerCase());
        }
        return str;
    }
}

在生成代码的函数中添加如下代码:

java 复制代码
// 生成service和controller插件
PluginConfiguration pluginConfiguration = new PluginConfiguration();
pluginConfiguration.addProperty("service", "com.zzg.mybatis.generator.plugins.ServiceAndControllerGeneratorPlugin");
pluginConfiguration.addProperty("targetProject", "./src/main/java");
pluginConfiguration.addProperty("servicePackage", "com.heaven.service");
pluginConfiguration.addProperty("serviceImplPackage", "com.heaven.service.impl");
pluginConfiguration.addProperty("controllerPackage", "com.heaven.controller");
pluginConfiguration.addProperty("serviceSuffix", "Service");
pluginConfiguration.addProperty("serviceImplSuffix", "ServiceImpl");
pluginConfiguration.addProperty("controllerSuffix", "Controller");
pluginConfiguration.setConfigurationType("com.zzg.mybatis.generator.plugins.ServiceAndControllerGeneratorPlugin");
context.addPluginConfiguration(pluginConfiguration);

最后点击gui生成代码如下:

小结

本文会简要分析一下生成代码的源码,并且修改源码从而使得生成的代码满足我的要求:

  1. Mapper包含基本insert、delete、update和select

  2. 生成的mapper、model类文件需要添加上类注释 --未实现,后面完善

  3. 生成service代码

后续的文章会

  1. 改写gui,使得生成service和controller支持gui配置

  2. 改写gui,增加支持其他数据库

参考

JavaFX官方教程(七)之使用FXML创建用户界面_fxml如何创建页面-CSDN博客

mybatis-generator工具生成对应的自定Service和Controller_mybatis-generator-maven-plugin可以生成service和controll-CSDN博客

mybatis generator自动生成代码时 只生成了insert 而没有其他的_mybatis generate dowithblobs 只生成插入未生成update-CSDN博客

相关推荐
WaaTong13 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_7430484413 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries15 分钟前
Java字节码增强库ByteBuddy
java·后端
佳佳_29 分钟前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
小灰灰__35 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭38 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono