Hands‑On Guide: Build Your Own Code Generator (Can You Keep Up?)

This article walks through why repetitive CRUD code wastes development time, reviews existing generators like MyBatis‑Generator and MyBatis‑Plus, and then demonstrates step‑by‑step how to create a custom Java code generator using Freemarker, Maven, and SpringBoot, complete with template and demo code.

Pan Zhi's Tech Notes
Pan Zhi's Tech Notes
Pan Zhi's Tech Notes
Hands‑On Guide: Build Your Own Code Generator (Can You Keep Up?)

1. Background

In real‑world projects, developers with more than five years of experience often find writing basic CRUD (Create, Read, Update, Delete) operations tedious. Most business features ultimately boil down to single‑table CRUD, but as requirements grow, multiple tables or databases are needed, making the codebase increasingly complex.

When a new table is added, the author used to rewrite MVC‑style CRUD code manually, which took 10–20 minutes per table. Adding ten tables could therefore consume over 100 minutes, most of which is wasted on repetitive work.

The author asks whether all these CRUD codes can be standardized and generated automatically. The answer is yes.

Existing tools such as MyBatis‑Generator and MyBatis‑Plus can already generate MyBatis XML and SQL, while JPA provides dynamic proxy classes that generate SQL automatically. However, some teams dislike MyBatis‑Plus because its generated SQL is fully dynamic and hard to read, and it may not fit micro‑service stacks like SpringBoot + Dubbo, where generated controllers become useless.

Therefore, the author proposes building a custom code generator that fits the project's specific conventions.

2. Code Practice

2.1 Add Freemarker Dependency

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.23</version>
</dependency>

2.2 Create a Template File

The following entity.java.ftl template defines a Java entity class. Variables wrapped in ${} are filled at generation time.

package ${package};

import java.io.Serializable;

/**
 * ${tableComment}
 *
 * @author ${author}
 * @since ${date}
 */
public class ${entityClass} implements Serializable {
    private static final long serialVersionUID = 1L;

    <#-- iterate over columns -->
    <#list columns as pro>
    /**
     * ${pro.comment}
     */
    private ${pro.propertyType} ${pro.propertyName};
    </#list>

    <#-- generate getters and setters -->
    <#list columns as pro>
    public ${pro.propertyType} get${pro.propertyName?cap_first}() {
        return this.${pro.propertyName};
    }

    public ${entityClass} set${pro.propertyName?cap_first}(${pro.propertyType} ${pro.propertyName}) {
        this.${pro.propertyName} = ${pro.propertyName};
        return this;
    }
    </#list>
}

2.3 Generate Target Code

The demo class CodeGeneratorDemo builds a data map, loads the template, and writes the rendered file.

public class CodeGeneratorDemo {
    public static void main(String[] args) throws IOException, TemplateException {
        Map<String, Object> objectMap = new HashMap<>();
        // package name
        objectMap.put("package", "com.example.test");
        // entity class name
        objectMap.put("entityClass", "Student");

        // define columns
        List<Map<String, Object>> columns = new ArrayList<>();
        Map<String, Object> column1 = new HashMap<>();
        column1.put("propertyType", "String");
        column1.put("propertyName", "name");
        column1.put("comment", "Name");
        columns.add(column1);

        Map<String, Object> column2 = new HashMap<>();
        column2.put("propertyType", "Integer");
        column2.put("propertyName", "age");
        column2.put("comment", "Age");
        columns.add(column2);

        objectMap.put("columns", columns);
        objectMap.put("author", "Zhang San");
        objectMap.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
        objectMap.put("tableComment", "Student information");

        Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
        configuration.setDefaultEncoding(Charset.forName("UTF-8").name());
        configuration.setClassForTemplateLoading(CodeGeneratorDemo.class, "/");
        Template template = configuration.getTemplate("/templates/entity.java.ftl");
        FileOutputStream fos = new FileOutputStream(new File("../src/main/java/com/example/generator/Student.java"));
        template.process(objectMap, new OutputStreamWriter(fos, Charset.forName("UTF-8").name()));
        fos.close();
        System.out.println("File created successfully");
    }
}

Running the program produces the following Student.java file, which matches the expected output:

package com.example.test;

import java.io.Serializable;

/**
 * <p>
 * Student information
 * </p>
 *
 * @author Zhang San
 * @since 2021-08-22
 */
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * Name
     */
    private String name;

    /**
     * Age
     */
    private Integer age;

    public String getName() {
        return this.name;
    }

    public Student setName(String name) {
        this.name = name;
        return this;
    }

    public Integer getAge() {
        return this.age;
    }

    public Student setAge(Integer age) {
        this.age = age;
        return this;
    }
}

The generated code aligns with expectations, confirming that the template‑driven approach works.

The core idea is to define a Freemarker template that contains placeholders for package name, class name, fields, comments, and metadata, then supply a map of values at runtime. The template engine fills the placeholders and writes the final source file.

In practice, the author used this method to generate all layers (controller, service, entity, DAO, unit tests) for a SpringBoot + Dubbo project. The generated code can be compiled and run directly, dramatically speeding up development.

Because the full generator source is extensive, the author provides a GitHub repository for those who need it:

https://github.com/pzblog/springboot-example-generator

3. Summary

For developers focused on business logic, a custom code generator is a powerful productivity boost. It eliminates repetitive CRUD coding, reduces development time, and lets engineers concentrate on core features. While mature generators like MyBatis‑Plus exist, building a tailored generator ensures compatibility with specific project conventions and micro‑service stacks.

4. References

1. MyBatis‑Plus documentation

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javacode generationMyBatisSpringBootFreemarker
Pan Zhi's Tech Notes
Written by

Pan Zhi's Tech Notes

Sharing frontline internet R&D technology, dedicated to premium original content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.