preface

background

Before developing a SpringBoot project, it is worth considering the generation of the initial code. After we complete the table design according to the business requirements, we need to generate the relevant code according to the table. In the SpringBoot project, we need the following parts:

  • Entity layer, used to store our entity class, basically consistent with the attribute values in the database, implement set and GET methods;
  • Mapper, the database data persistence operation, its method statement is directly for the database operation, mainly to achieve some add, delete, change and check operation, in Mybatis method is mainly with XXX. XML mutual mapping;
  • Service, the business Service layer, provides interfaces to controller layer classes to make calls. It is usually wrapped in a self-written method, which is declared and implemented in serviceImpl;
  • Controller, the control layer, is responsible for the business process control of specific modules. It needs to call the interface of service logic design layer to control the business process. Because the methods in the service are used by us, the controller performs business operations by receiving the parameters sent from the front-end H5 or App, and then returns the processing results to the front-end.

In addition to the basic files in the above project architecture, we have added the following layers to better manage the project:

  • Dto file, which is used to share the utility of entity class, can encapsulate a single class of query criteria, and the entity class of the front and back end interaction (sometimes we may pass in fields that do not exist in the Entity class);
  • Vo file, the background returned to the foreground data structure, can also customize the field;
  • Struct file, used to handle conversion between DTO, Entity and VO files.

The ORM frameworks used in the project are mostly Mybatis and Mybatis Plus. Although their official documents have code generator configurations, they are too simple to meet the actual needs. Therefore, it is imperative to sort out a set of general code generators.

Before I start this article, let me introduce some of the general knowledge points to use.

knowledge

FreeMarker

FreeMarker is a template engine: a generic tool for generating output text (HTML web pages, emails, profiles, source code, etc.) based on templates and data to change. It is not intended for the end user, but is a Java class library, a component that programmers can embed in the product they are developing.

The Template is written as FreeMarker Template Language (FTL). It is a simple, dedicated language, not a mature programming language like PHP. That means preparing data to be displayed in a real programming language, such as database queries and business operations, and then templates to display the prepared data. In the template, you can focus on how to present the data, and outside the template you can focus on what data to present.

Mybatis

MyBatis is an excellent persistence layer framework that supports custom SQL, stored procedures, and advanced mapping. MyBatis eliminates almost all of the JDBC code and the work of setting parameters and fetching result sets. MyBatis can configure and map primitive types, interfaces, and Java POJOs (Plain Old Java Objects) to records in the database via simple XML or annotations.

Mybatis Plus

MyBatis-Plus (Opens New Window) (MP) is a new enhancement tool for MyBatis (Opens New Window), which is designed to simplify development and improve efficiency.

This article mainly describes the generation process of relevant code files when choosing to use Mybatis and Mybatis Plus.

JCommander

JCommander is a Java framework for parsing command line arguments. It supports parsing all basic data types as well as command line parsing to user-defined types by writing a conversion function.

The next step is code practice.

In actual combat

Start by creating a new Maven project named Mybatis – Generator.

Basic Environment Configuration

Import dependence


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>

    <groupId>com.msdn.generator</groupId>
    <artifactId>mybatis-generator</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <logback.version>1.2.3</logback.version>
        <slf4j.version>1.7.30</slf4j.version>
        <fastjson.version>1.2.73</fastjson.version>
        <hutool.version>5.5.8</hutool.version>
        <mysql.version>8.0.19</mysql.version>
        <swagger.version>1.9.1. RELEASE</swagger.version>
        <mybatis.version>2.1.4</mybatis.version>
        <mapper.version>4.1.5</mapper.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>swagger-spring-boot-starter</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${mapper.version}</version>
        </dependency>
        <! --JCommander parses command line arguments -->
        <dependency>
            <groupId>com.beust</groupId>
            <artifactId>jcommander</artifactId>
            <version>1.78</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>2.4.6</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2 rainfall distribution on 10-12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

The configuration file

The application.yml file contains the following contents:

server:
  port: 8525

# Has no practical significance, the actual project specific configuration
spring:
  datasource:
    username: root
    password: xxxx
    url: jdbc:mysql://localhost:3306/mybatis? useUnicode=true&useSSL=true&serverTimezone=UTC&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
Copy the code

Code generation

Entity class

To receive configuration parameters, we parse command line parameters through JCommander, creating the corresponding entity class GenerateParameter to receive these parameters.

@Getter
@Setter
@apiModel (" Use help ")
@parameters (commandDescription = "Use help ")
public class GenerateParameter {

    @apiModelProperty ("mysql host name ")
    @parameter (names = {"--host", "-h"}, description = "mysql hostname ")
    private String host;

    @ ApiModelProperty (" mysql port ")
    @parameter (names = {"--port", "-p "}, description = "mysql port")
    private Integer port;

    @apiModelProperty ("mysql username ")
    @parameter (names = {"--username", "-u"}, description = "mysql username")
    private String username;

    @ ApiModelProperty (" mysql password ")
    @parameter (names = {"--password", "-p"}, description = "mysql password")
    private String password;

    @apiModelProperty ("mysql database name ")
    @parameter (names = {"--database", "-d"}, description = "mysql database name ")
    private String database;

    @apiModelProperty ("mysql database table ")
    @parameter (names = {"--table", "-t"}, description = "mysql database table")
    private List<String> table;

    @APIModelProperty (" Business module name ")
    @parameter (names = {"--module", "-m"}, description = "service module name ")
    private String module;

    @APIModelProperty (" Business group, currently base and Business ")
    @parameter (names = {"--group", "-g"}, description = "business group, currently base and business")
    private String group;

    @APIModelProperty (" Whether to separate directories by table name ")
    @parameter (names = {"--flat"}, description = "whether to separate directories by table name ")
    private boolean flat;

    @APIModelProperty (" ORM framework selection ")
    @parameter (names = {"--type"}, description = "ORM framework selection ")
    private String type;

    @apiModelProperty (" View help ")
    @parameter (names = "--help", help = true, description = "view help")
    private boolean help;
    
    @apiModelProperty (" The table name intercepts the initial index, for example t_sale_contract_detail, if the generated entity class is ContractDetail, this field is 7")
    @parameter (names = {"--tableStartIndex", "-tsi"}, description = "table name truncates start index ")
    private String tableStartIndex;
}
Copy the code

After connecting to the database, we need to parse the table structure, including retrieving table fields, field remarks, and field types, which correspond to the Column class created here.

/** * parse the contents of the table */
@Data
public class Column {
    /** * is the primary key */
    private Boolean isPrimaryKey;

    /** * The primary key type required by Mybatis Plus entities. The default is ASSIGN_ID(3) */
    private String primaryKeyType = "ASSIGN_ID";
    /** * Database table name */
    private String tableName;
    /** * table description */
    private String tableDesc;
    /** * Database field name **/
    private String fieldName;
    /** * Database field type **/
    private String fieldType;
    /** * Java type */
    private String javaType;
    /**
     * 是否是数字类型
     */
    private Boolean isNumber;
    /** * Database field hump name, saleBooke **/
    private String camelName;
    /** * Database field Pascal named, SaleBook **/
    private String pascalName;
    /** * database field comment **/
    private String comment;

    private String field;

    private String key;

    private Boolean isConfig;
}
Copy the code

Finally, create a constant class Config to store constant information.

public class Config {

    public static final String OutputPath = "." + File.separator + "output";

    public static final String Author = "hresh";
}
Copy the code

Service class

Start by defining the FreeMarker usage code:

/** * Use FreeMarker to generate related files from the specified file template */
@Service
public class FreemarkerService {
    private static final Logger logger = LoggerFactory.getLogger(FreemarkerService.class);

    @Autowired
    private Configuration configuration;

    /** * output file template **@paramThe templateName in the templateName resources folder, such as model.ftl, is the module that generates the entity class *@paramDataModel Set of table names, field names, etc. *@paramFilePath Specifies the output file name, including path *@param generateParameter
     * @throws Exception
     */
    public void write(String templateName, Map<String, Object> dataModel, String filePath, GenerateParameter generateParameter) throws Exception {
        // Freemarker Templete Language (FTL) template file name
        Template template = configuration.getTemplate(dataModel.get("type") + File.separator + templateName + ".ftl");
        File file;
        // Determine if there are multiple tables, if so, generate their respective folder directories according to the table name
        if (generateParameter.isFlat()) {
            file = new File(Config.OutputPath + File.separator + dataModel.get("tempId") + File.separator + filePath);
        } else {
            file = new File(Config.OutputPath + File.separator + dataModel.get("tempId") + File.separator + dataModel.get("tableName") + File.separator + filePath);
        }
        if(! file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); } FileOutputStream fileOutputStream =new FileOutputStream(file);
        OutputStreamWriter outputStreamWriter = newOutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8); template.process(dataModel, outputStreamWriter); fileOutputStream.flush(); fileOutputStream.close(); }}Copy the code

Next comes the core code of the project, which reads the data table, gets the definition information for the table, and then uses FreeMarker to read Ftl template files to generate the basic code for the table.

Basic service class BaseService

public class BaseService {


    public String getUrl(GenerateParameter generateParameter) {
        return "jdbc:mysql://" + generateParameter.getHost() + ":" + generateParameter.getPort() + "/" + generateParameter.getDatabase() + "? useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8";
    }

    / / database connections, similar to: DriverManager getConnection (" JDBC: mysql: / / localhost: 3306 / test_demo? useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC","root","password");
    public Connection getConnection(GenerateParameter generateParameter) throws Exception {
        return DriverManager.getConnection(getUrl(generateParameter), generateParameter.getUsername(), generateParameter.getPassword());
    }

    /** * Obtain specific information about the fields in the table, including the field name, field type, and remarks **@param tableName
     * @param parameter
     * @return
     * @throws Exception
     */
    public List<Column> getColumns(String tableName, GenerateParameter parameter, String[] commonColumns) throws Exception {
        // Database connection
        Connection connection = getConnection(parameter);
        // Get the field information defined by the table
        ResultSet resultSet = connection.createStatement().executeQuery("SHOW FULL COLUMNS FROM " + tableName);
        List<Column> columnList = new ArrayList<>();
        while (resultSet.next()) {
            String fieldName = resultSet.getString("Field");
            Column column = new Column();
            // Check whether it is the primary key
            column.setIsPrimaryKey("PRI".equals(resultSet.getString("Key")));
            // Get the field name
            column.setFieldName(fieldName);

            // Mybatis Plus specific fields are retrieved from the core class
            if (Objects.nonNull(commonColumns) && Arrays.asList(commonColumns).contains(fieldName)) {
                column.setIsCommonField(true);
            } else {
                column.setIsCommonField(false);
            }
            // Get the field type
            column.setFieldType(resultSet.getString("Type").replaceAll(\ \ \ \ "(. *)".""));
            switch (column.getFieldType()) {
                case "json":
                case "longtext":
                case "char":
                case "varchar":
                case "text":
                    column.setJavaType("String");
                    column.setIsNumber(false);
                    break;
                case "date":
                case "datetime":
                    column.setJavaType("Date");
                    column.setIsNumber(false);
                    break;
                case "bit":
                    column.setJavaType("Boolean");
                    column.setIsNumber(false);
                    break;
                case "int":
                case "tinyint":
                    column.setJavaType("Integer");
                    column.setIsNumber(true);
                    break;
                case "bigint":
                    column.setJavaType("Long");
                    column.setIsNumber(true);
                    break;
                case "decimal":
                    column.setJavaType("BigDecimal");
                    column.setIsNumber(true);
                    break;
                case "varbinary":
                    column.setJavaType("byte[]");
                    column.setIsNumber(false);
                    break;
                default:
                    throw new Exception(tableName + "" + column.getFieldName() + "" + column.getFieldType() + "Type not resolved");
            }
            Receipt_sign_name = receiptSignName
            column.setCamelName(StringUtils.underscoreToCamel(column.getFieldName()));
            // Uppercase
            column.setPascalName(StringUtils.capitalize(column.getCamelName()));
            // Field annotations in the database
            column.setComment(resultSet.getString("Comment"));
            columnList.add(column);
        }
        return columnList;
    }

    /** * get the table description **@param tableName
     * @param parameter
     * @return
     * @throws Exception
     */
    public String getTableComment(String tableName, GenerateParameter parameter) throws Exception {
        Connection connection = getConnection(parameter);
        ResultSet resultSet = connection.createStatement().executeQuery("SELECT table_comment FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '" + parameter.getDatabase()
                + "' AND table_name = '" + tableName + "'");
        String tableComment = "";
        while (resultSet.next()) {
            tableComment = resultSet.getString("table_comment");
        }
        returntableComment; }}Copy the code

GenerateService takes the table information and generates the relevant code

@Service
public class GenerateService extends BaseService{

    private static final Logger logger = LoggerFactory.getLogger(GenerateService.class);
    @Autowired
    private FreemarkerService freemarkerService;

    / * * *@paramTableName specifies the database tableName *@paramParameter Specifies the module name *@param uuid
     * @throws Exception
     */
    public void generate(String tableName, GenerateParameter parameter, String uuid) throws Exception {

        // Package name of each module, such as com.msdn.sale or com.msdn.finance
        String packagePrefix = "com.msdn." + parameter.getModule();
        / / group
        if(! StringUtils.isEmpty(parameter.getGroup())) { packagePrefix = packagePrefix +"." + parameter.getGroup();
        }

        // Get the table name based on the name of the table designed by the project, for example, t_sale_contract_detail
        // Now the table name intercepts the start index
// int index = tableName.indexOf("_", 2);
        Integer index = new Integer(parameter.getTableStartIndex());
        // The hump name is lowercase, for example, contractDetail
        String camelName = StringUtils.underscoreToCamel(tableName.substring(index));

        Map<String, Object> dataModel = new HashMap<>();
        // Get specific information about the fields in the table, including the field name, field type, remarks, etc
        List<Column> columns = getColumns(tableName, parameter,Config.COMMON_COLUMNS);
        Column primaryColumn = columns.stream().filter(Column::getIsPrimaryKey).findFirst().orElse(null);
        dataModel.put("package", packagePrefix);
        dataModel.put("camelName", camelName);
        // Capitalize the first letter as the entity class name, etc
        dataModel.put("pascalName", StringUtils.capitalize(camelName));
        dataModel.put("moduleName", parameter.getModule());
        dataModel.put("tableName", tableName);
        / / table
        dataModel.put("tableComment", getTableComment(tableName, parameter));
        dataModel.put("columns", columns);
        dataModel.put("primaryColumn", primaryColumn);
        dataModel.put("tempId", uuid);
        dataModel.put("author", Config.Author);
        dataModel.put("date", DateUtil.now());
        dataModel.put("type", parameter.getType());

        logger.info("The name of the table from which the template code is to be generated:" + tableName + ", the table is described as: + dataModel.get("tableComment"));

        // Generate template code
        logger.info("********** Start generating Model template file **********");
        generateModel(dataModel, parameter);
        logger.info("********** Start generating the VO view template file **********");
        generateVO(dataModel, parameter);
        logger.info("********** Start generating DTO template file **********");
        generateDTO(dataModel, parameter);
/ / logger. The info (" * * * * * * * * * * to start generating Struct template file * * * * * * * * * * ");
// generateStruct(dataModel, parameter);
        logger.info("********** Start generating Mapper template file **********");
        generateMapper(dataModel, parameter);
        logger.info("********** Start generating Service template file **********");
        generateService(dataModel, parameter);
        logger.info("********** Start generating Controller template file **********");
        generateController(dataModel, parameter);
    }

    /** * Generate controller template code **@param dataModel
     * @param generateParameter
     * @throws Exception
     */
    private void generateController(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "controller" + File.separator + dataModel.get("pascalName") + "Controller.java";
        freemarkerService.write("controller", dataModel, path, generateParameter);
    }

    private void generateDTO(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "dto" + File.separator + dataModel.get("pascalName");
        freemarkerService.write("dto", dataModel, path + "DTO.java", generateParameter);
        freemarkerService.write("dto-page", dataModel, path + "QueryPageDTO.java", generateParameter);
    }

    //
    private void generateModel(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "model" + File.separator + dataModel.get("pascalName") + ".java";
        freemarkerService.write("model", dataModel, path, generateParameter);
    }

    private void generateStruct(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "struct" + File.separator + dataModel.get("pascalName") + "Struct.java";
        freemarkerService.write("struct", dataModel, path, generateParameter);
    }

    private void generateMapper(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "mapper" + File.separator + dataModel.get("pascalName") + "Mapper.java";
        freemarkerService.write("mapper", dataModel, path, generateParameter);

        path = "resources" + File.separator + dataModel.get("pascalName") + "Mapper.xml";
        freemarkerService.write("mapper-xml", dataModel, path, generateParameter);
    }

    private void generateService(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "service" + File.separator + dataModel.get("pascalName") + "Service.java";
        freemarkerService.write("service", dataModel, path, generateParameter);

        path = "java" + File.separator + "service" + File.separator + "impl" + File.separator + dataModel.get("pascalName") + "ServiceImpl.java";
        freemarkerService.write("service-impl", dataModel, path, generateParameter);
    }

    private void generateVO(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "java" + File.separator + "vo" + File.separator + dataModel.get("pascalName") + "VO.java";
        freemarkerService.write("vo", dataModel, path, generateParameter); }}Copy the code

The controller

To make it easier to use the code generator, we invoke the Rest service interface through Swagger.

@RestController
public class GeneratorController {

    private static final Logger logger = LoggerFactory.getLogger(GeneratorController.class);
    @Autowired
    private GenerateService generateService;

    {/ * / / request parameters "database" : "db_tl_sale", "flat" : true, "type" : "mybatis", "group" : "the base", "the host" : "127.0.0.1", "the module" : "sale", "password": "123456", "port": 3306, "table": [ "t_xs_sale_contract" ], "username": "root", "tableStartIndex":"5" } */
    @PostMapping("/generator/build")
    public void build(@RequestBody GenerateParameter parameter, HttpServletResponse response) throws Exception {
        logger.info("********** Welcome to freemarker-based template file generator **********");
        logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
        String uuid = UUID.randomUUID().toString();
        for (String table : parameter.getTable()) {
            generateService.generate(table, parameter, uuid);
        }
        logger.info("********** template file generated, ready to download **********");
        String path = Config.OutputPath + File.separator + uuid;
        // Set the response header to control the behavior of the browser
        response.setHeader("Content-disposition"."attachment; filename=code.zip");
        response.setHeader("Access-Control-Expose-Headers"."Content-disposition");
        // Zip the files in the output stream in Response
        ZipDirectory(path, response.getOutputStream());
        // Delete directories recursively
        FileSystemUtils.deleteRecursively(new File(path));
        logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
        logger.info("********** template file downloaded, thank you for using **********");
    }

    /** * compress multiple files at one time, save the files to a folder */
    public static void ZipDirectory(String directoryPath, ServletOutputStream outputStream) {
        InputStream input = null;
        ZipOutputStream output = null;
        try {
            output = new ZipOutputStream(outputStream);
            List<File> files = getFiles(new File(directoryPath));
            for (File file : files) {
                input = new FileInputStream(file);
                output.putNextEntry(new ZipEntry(file.getPath().substring(directoryPath.length() + 1)));
                int temp = 0;
                while((temp = input.read()) ! = -1) {
                    output.write(temp);
                }
                input.close();
            }
            output.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(input ! =null) {
                try {
                    input.close();
                } catch(IOException e) { e.printStackTrace(); }}if(output ! =null) {
                try {
                    output.close();
                } catch(IOException e) { e.printStackTrace(); }}}}public static List<File> getFiles(File file) {
        List<File> files = new ArrayList<>();
        for (File subFile : file.listFiles()) {
            if (subFile.isDirectory()) {
                List<File> subFiles = getFiles(subFile);
                files.addAll(subFiles);
            } else{ files.add(subFile); }}returnfiles; }}Copy the code

Start the class

@EnableSwagger2Doc
@SpringBootApplication
public class GeneratorApplication {

    /** * Add the parameter -h 127.0.0.1 -p 3306 -d db_tl_sale -u root -p 123456 -m sale -g base -t t_xs_sale_contract,t_xs_sale_contract_detail * *@param args
     * @throws Exception
     */
    public static void main(String[] args) { SpringApplication.run(GeneratorApplication.class, args); }}Copy the code

Template file

The template file is defined as follows:

Other code

In addition to the above code, there are also some utility classes and common components, which are not described here, but can be seen on Github if you are interested.

The effect

Start the project, direct access to http://localhost:8525/swagger-ui.html#/.

The incoming parameters are arranged in the following format according to individual requirements:

{" database ", "db_tl_sale", "flat" : true, "type" : "mybatis", "group" : "the base", "the host" : "127.0.0.1", "the module" : "sale", "password": "123456", "port": 3306, "table": [ "t_xs_sale_contract" ], "username": "root", "tableStartIndex":"5" }Copy the code

Then click Execute and click Download to download the generated code to the local. The file structure is as follows:

Here’s a snippet of code, starting with the entity class:

Then query the entity class:

Next comes the Service interface:

And the corresponding implementation class:

Finally, controller:

extension

One-to-many associated query

The resultMap element is the most important and powerful element in MyBatis. It frees you from 90% of the JDBC ResultSets data extraction code, and in some cases allows you to do things that ARE not supported by JDBC. In fact, when writing mapping code for complex statements such as joins, a resultMap can replace thousands of lines of code that do the same thing. ResultMap is designed to achieve zero configuration for simple statements, while complex statements only need to describe the relationship between statements.

Requirements:

At present, in the results returned by the order class details query, in addition to containing all the information of the order class, but also need to return the data of multiple order sub-items, that is, we often say the one-to-many relationship, so how to operate in the actual development?

First let’s take a look at the code example:

1. Order class

public class OmsOrder implements Serializable {

    private static final long serialVersionUID = 1L;
    
    @apiModelProperty (value = "order id")
    private Long id;

    private Long memberId;

    private Long couponId;

    @apiModelProperty (value = "order number ")
    private String orderSn;

    @apiModelProperty (value = "Commit time ")
    private Date createTime;

    @apiModelProperty (value = "user account ")
    private String memberUsername;

    @apiModelProperty (value = "total order amount ")
    private BigDecimal totalAmount;

    @apiModelProperty (value = "amount payable (actual amount paid) ")
    private BigDecimal payAmount;

    @apiModelProperty (value = "freight amount ")
    private BigDecimal freightAmount;

    @apiModelProperty (value = "promotion optimization amount (promotion price, full reduction, ladder price) ")
    private BigDecimal promotionAmount;

    @apiModelProperty (value = "value ")
    private BigDecimal integrationAmount;

    @apiModelProperty (value = "discount amount ")
    private BigDecimal couponAmount;

    @apiModelProperty (value = "discount amount used by administrator to adjust order ")
    private BigDecimal discountAmount;

    @apiModelProperty (value = "payment method: 0-> unpaid; 1-> Alipay; 2 - > WeChat ")
    private Integer payType;

    @apiModelProperty (value = "0->PC; 1 - > app order ")
    private Integer sourceType;

    @apiModelProperty (value = "order status: 0-> to be paid; 1-> Goods to be shipped; 2-> Shipped; 3-> Completed; 4-> Closed; 5-> Invalid order ")
    private Integer status;

    @apiModelProperty (value = "order type: 0-> Normal order; 1-> order ")
    private Integer orderType;

    @apiModelProperty (value = "logistics company (delivery method)")
    private String deliveryCompany;

    @apiModelProperty (value = "tracking number ")
    private String deliverySn;

    @APIModelProperty (value = "automatic confirmation time (days) ")
    private Integer autoConfirmDay;

    @apiModelProperty (value = "credits available ")
    private Integer integration;

    @apiModelProperty (value = "actionable growth value ")
    private Integer growth;

    @apiModelProperty (value = "active information ")
    private String promotionInfo;

    @apiModelProperty (value = "invoice type: 0-> no invoice; 1-> Electronic invoice; 2-> Paper invoice ")
    private Integer billType;

    @apiModelProperty (value = "invoice title ")
    private String billHeader;

    @apiModelProperty (value = "invoice content ")
    private String billContent;

    @apiModelProperty (value = "receiver phone number ")
    private String billReceiverPhone;

    @apiModelProperty (value = "recipient email ")
    private String billReceiverEmail;

    @apiModelProperty (value = "consignee name ")
    private String receiverName;

    @apiModelProperty (value = "consignee phone number ")
    private String receiverPhone;

    @apiModelProperty (value = "consignee zip code ")
    private String receiverPostCode;

    @apiModelProperty (value = "province/municipality ")
    private String receiverProvince;

    @apiModelProperty (value = "city ")
    private String receiverCity;

    @ApiModelProperty(value = "区")
    private String receiverRegion;

    @apiModelProperty (value = "address ")
    private String receiverDetailAddress;

    @apiModelProperty (value = "order notes ")
    private String note;

    @apiModelProperty (value = "confirm receipt status: 0-> unconfirmed; 1-> Confirmed ")
    private Integer confirmStatus;

    @apiModelProperty (value = "delete status: 0-> not deleted; 1-> Deleted ")
    private Integer deleteStatus;

    @apiModelProperty (value = "credits used when ordering ")
    private Integer useIntegration;

    @apiModelProperty (value = "payment time ")
    private Date paymentTime;

    @apiModelProperty (value = "delivery time ")
    private Date deliveryTime;

    @apiModelProperty (value = "confirm receipt time ")
    private Date receiveTime;

    @apiModelProperty (value = "evaluation time ")
    private Date commentTime;

    @apiModelProperty (value = "change time ")
    private Date modifyTime;
}
Copy the code

2. Order subitem class

public class OmsOrderItem implements Serializable {
	private static final long serialVersionUID = 1L;
    private Long id;

    @apiModelProperty (value = "order id")
    private Long orderId;

    @apiModelProperty (value = "order number ")
    private String orderSn;

    private Long productId;

    private String productPic;

    private String productName;

    private String productBrand;

    private String productSn;

    @apiModelProperty (value = "sales price ")
    private BigDecimal productPrice;

    @apiModelProperty (value = "purchase quantity ")
    private Integer productQuantity;

    @apiModelProperty (value = "skU number ")
    private Long productSkuId;

    @apiModelProperty (value = "skU bar code ")
    private String productSkuCode;

    @apiModelProperty (value = "class id")
    private Long productCategoryId;

    @apiModelProperty (value = "product promotion name ")
    private String promotionName;

    @apiModelProperty (value = "Product promotion amount decomposed ")
    private BigDecimal promotionAmount;

    @apiModelProperty (value = "Discount value ")
    private BigDecimal couponAmount;

    @apiModelProperty (value = "discount decomposition amount ")
    private BigDecimal integrationAmount;

    @apiModelProperty (value = "The decomposed amount of the product after the discount ")
    private BigDecimal realAmount;

    private Integer giftIntegration;

    private Integer giftGrowth;

    @ ApiModelProperty (value = "commodity sales attributes: [{' key ':' color ', 'value' : 'color'}, {' key ':' capacity ', 'value', '4 g'}]")
    private String productAttr;

 }
Copy the code

3. Front-end return classes

public class OmsOrderDetail extends OmsOrder {
    @Getter
    @Setter
    @apiModelProperty (" Order List ")
    private List<OmsOrderItem> orderItemList;
}
Copy the code

4. Customize SQL statements in omsordermapper. XML file

<resultMap id="orderDetailResultMap" type="com.macro.mall.dto.OmsOrderDetail" extends="com.macro.mall.mapper.OmsOrderMapper.BaseResultMap">
    <collection property="orderItemList" resultMap="com.macro.mall.mapper.OmsOrderItemMapper.BaseResultMap" columnPrefix="item_"/>
</resultMap>

<select id="getDetail" resultMap="orderDetailResultMap">
    SELECT o.*,
    oi.id item_id,
    oi.product_id item_product_id,
    oi.product_sn item_product_sn,
    oi.product_pic item_product_pic,
    oi.product_name item_product_name,
    oi.product_brand item_product_brand,
    oi.product_price item_product_price,
    oi.product_quantity item_product_quantity,
    oi.product_attr item_product_attr
    FROM
    oms_order o
    LEFT JOIN oms_order_item oi ON o.id = oi.order_id
    WHERE
    o.id = #{id}
    ORDER BY oi.id ASC DESC
</select>
Copy the code

The com. Macro. Mall. Mapper. OmsOrderItemMapper. BaseResultMap is quoted from OmsOrderItemMapper defined in the XML file,

<resultMap id="BaseResultMap" type="com.macro.mall.model.OmsOrderItem">
    <id column="id" property="id" />
    <result column="order_id"  property="orderId" />
    <result column="order_sn" property="orderSn" />
    <result column="product_id" jdbcType="BIGINT" property="productId" />
    <result column="product_pic" jdbcType="VARCHAR" property="productPic" />
    <result column="product_name" jdbcType="VARCHAR" property="productName" />
    <result column="product_brand" jdbcType="VARCHAR" property="productBrand" />
    <result column="product_sn" jdbcType="VARCHAR" property="productSn" />
    <result column="product_price" jdbcType="DECIMAL" property="productPrice" />
    <result column="product_quantity" jdbcType="INTEGER" property="productQuantity" />
    <result column="product_sku_id" jdbcType="BIGINT" property="productSkuId" />
    <result column="product_sku_code" jdbcType="VARCHAR" property="productSkuCode" />
    <result column="product_category_id" jdbcType="BIGINT" property="productCategoryId" />
    <result column="promotion_name" jdbcType="VARCHAR" property="promotionName" />
    <result column="promotion_amount" jdbcType="DECIMAL" property="promotionAmount" />
    <result column="coupon_amount" jdbcType="DECIMAL" property="couponAmount" />
    <result column="integration_amount" jdbcType="DECIMAL" property="integrationAmount" />
    <result column="real_amount" jdbcType="DECIMAL" property="realAmount" />
    <result column="gift_integration" jdbcType="INTEGER" property="giftIntegration" />
    <result column="gift_growth" jdbcType="INTEGER" property="giftGrowth" />
    <result column="product_attr" jdbcType="VARCHAR" property="productAttr" />
</resultMap>
Copy the code

5. Implementation effect

This query method reduces the I/O connection query times and is more efficient than querying sub-table information based on primary table fields.

A resultMap template is generated

From the above code, we know that the key to achieve one-to-many associated query is to define the resultMap definition of subitem data (more). Since we have generated the basic project code through the code generator, can we generate a resultMap? Here’s the code:

1. Define template FTL file


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package}.mapper.${pascalName}Mapper">

    <resultMap id="BaseResultMap" type="${package}.model.${pascalName}">
    <#list columns as column>
    <#if column.isPrimaryKey>
        <id column="${column.fieldName}" property="${column.camelName}" />
    <#else>
        <result column="${column.fieldName}" property="${column.camelName}" />
    </#if>
    </#list>
    </resultMap>

</mapper>
Copy the code

2. Write the service class XmlGenerateService

@Service
public class XmlGenerateService extends BaseService {

    private static final Logger logger = LoggerFactory.getLogger(XmlGenerateService.class);

    @Autowired
    private FreemarkerService freemarkerService;

    / * * *@paramTableName specifies the database tableName *@paramParameter Specifies the module name *@param uuid
     * @throws Exception
     */
    public void generate(String tableName, GenerateParameter parameter, String uuid) throws Exception {

        // Package name of each module, such as com.msdn.sale or com.msdn.finance
        String packagePrefix = "com.msdn." + parameter.getModule();
        / / group
        if(! StringUtils.isEmpty(parameter.getGroup())) { packagePrefix = packagePrefix +"." + parameter.getGroup();
        }

        // Get the table name based on the name of the table designed by the project, for example, t_sale_contract_detail
        // Now the table name intercepts the start index
// int index = tableName.indexOf("_", 2);
        Integer index = new Integer(parameter.getTableStartIndex());
        // The hump name is lowercase, for example, contractDetail
        String camelName = StringUtils.underscoreToCamel(tableName.substring(index));

        Map<String, Object> dataModel = new HashMap<>();
        // Get specific information about the fields in the table, including the field name, field type, remarks, etc
        List<Column> columns = getColumns(tableName, parameter, null);
        Column primaryColumn = columns.stream().filter(Column::getIsPrimaryKey).findFirst().orElse(null);
        dataModel.put("package", packagePrefix);
        dataModel.put("camelName", camelName);
        // Capitalize the first letter as the entity class name, etc
        dataModel.put("pascalName", StringUtils.capitalize(camelName));
        dataModel.put("moduleName", parameter.getModule());
        dataModel.put("tableName", tableName);
        / / table
        dataModel.put("tableComment", getTableComment(tableName, parameter));
        dataModel.put("columns", columns);
        dataModel.put("primaryColumn", primaryColumn);
        dataModel.put("tempId", uuid);
        dataModel.put("author", Config.Author);
        dataModel.put("date", DateUtil.now());
        dataModel.put("type", parameter.getType());

        logger.info("The name of the table from which the template code is to be generated:" + tableName + ", the table is described as: + dataModel.get("tableComment"));

        // Generate template code
        logger.info("********** Start generating Model template file **********");
        generateXML(dataModel, parameter);
    }

    /** * Generate controller template code **@param dataModel
     * @param generateParameter
     * @throws Exception
     */
    private void generateXML(Map<String, Object> dataModel, GenerateParameter generateParameter) throws Exception {
        String path = "resources" + File.separator + "xml" + File.separator + dataModel.get("pascalName") + "Mapper.xml";
        freemarkerService.write("mybatis-xml", dataModel, path, generateParameter); }}Copy the code

3. Service interface

@PostMapping("/generator/buildXml")
public void buildXml(@RequestBody GenerateParameter parameter, HttpServletResponse response) throws Exception {
    logger.info("********** Welcome to freemarker-based template file generator **********");
    logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
    String uuid = UUID.randomUUID().toString();
    for (String table : parameter.getTable()) {
        xmlGenerateService.generate(table, parameter, uuid);
    }
    logger.info("********** template file generated, ready to download **********");
    String path = Config.OutputPath + File.separator + uuid;
    // Set the response header to control the behavior of the browser
    response.setHeader("Content-disposition"."attachment; filename=code.zip");
    response.setHeader("Access-Control-Expose-Headers"."Content-disposition");
    // Zip the files in the output stream in Response
    ZipDirectory(path, response.getOutputStream());
    // Delete directories recursively
    FileSystemUtils.deleteRecursively(new File(path));
    logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
    logger.info("********** template file downloaded, thank you for using **********");
}
Copy the code

4. Call API with Swagger

5. Execution results


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.msdn.mall.mapper.OmsOrderItemMapper">

    <resultMap id="BaseResultMap" type="com.msdn.mall.model.OmsOrderItem">
        <id column="order_item_id" property="orderItemId" />
        <result column="order_id" property="orderId" />
        <result column="order_sn" property="orderSn" />
        <result column="product_id" property="productId" />
        <result column="product_pic" property="productPic" />
        <result column="product_name" property="productName" />
        <result column="product_brand" property="productBrand" />
        <result column="product_sn" property="productSn" />
        <result column="product_price" property="productPrice" />
        <result column="purchase_amount" property="purchaseAmount" />
        <result column="product_sku_id" property="productSkuId" />
        <result column="product_sku_code" property="productSkuCode" />
        <result column="product_category_id" property="productCategoryId" />
        <result column="sp1" property="sp1" />
        <result column="sp2" property="sp2" />
        <result column="sp3" property="sp3" />
        <result column="promotion_name" property="promotionName" />
        <result column="promotion_money" property="promotionMoney" />
        <result column="coupon_money" property="couponMoney" />
        <result column="integration_money" property="integrationMoney" />
        <result column="real_money" property="realMoney" />
        <result column="gift_integration" property="giftIntegration" />
        <result column="gift_growth" property="giftGrowth" />
        <result column="product_attr" property="productAttr" />
        <result column="is_deleted" property="isDeleted" />
        <result column="create_user_code" property="createUserCode" />
        <result column="create_user_name" property="createUserName" />
        <result column="create_date" property="createDate" />
        <result column="update_user_code" property="updateUserCode" />
        <result column="update_user_name" property="updateUserName" />
        <result column="update_date" property="updateDate" />
        <result column="version" property="version" />
    </resultMap>

</mapper>
Copy the code

subsequent

In production and development, if there is something interesting, we will update it irregularly, hoping that the tool will become more powerful.

conclusion

Although both Mybatis and Mybatis Plus have their own code generator configuration, the builder code is not easy to integrate, external calls are not convenient, and most importantly, it does not meet practical requirements. In order to be able to generate all the code at once, SpringBoot and FreeMarker were chosen to build our own code generator.

In addition to generating Java-related code, FreeMarker can also generate front-end code from template files, Word documents, etc. More features will be added as needed.

Interested friends can go to my Github to download the relevant code, if it is helpful to you, you might as well Star, thank you for your support!

reference

ZipOutputStream knowledge

Develop command-line interactive (CLI) style JAVA programs using JCommander