Build a Code Generator from Scratch: Boost Development Efficiency 10×

This article walks through creating a fully automated Spring Boot and MyBatis code generator using Freemarker templates, detailing each step from extracting table metadata to generating DAO, service, controller, and entity classes, and shows how a single-table CRUD can be produced in seconds.

Pan Zhi's Tech Notes
Pan Zhi's Tech Notes
Pan Zhi's Tech Notes
Build a Code Generator from Scratch: Boost Development Efficiency 10×

Background

When developing a new feature in a typical MVC project, developers repeatedly write dao, service, controller and the associated dto, entity, vo classes for each table. The core operations are always the same four CRUD actions (Create, Delete, Update, Retrieve), which leads to a lot of boiler‑plate code.

Open‑source code generators exist to reduce this manual effort, allowing developers to focus on business logic.

Implementation Idea

We build a simple generator for a Spring Boot project that uses MyBatis and MySQL. The generator consists of three stages:

Obtain table metadata (column names, types, comments) from information_schema.

Write Freemarker templates for mapper, dao, service, controller, entity, dto and vo.

Render the templates with the metadata to produce Java source files.

1. Get Table Structure

Example SQL to create a demo table:

CREATE TABLE test_db (
  id bigint(20) unsigned NOT NULL COMMENT '主键ID',
  name varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
  is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 1:已删除;0:未删除',
  create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (id),
  KEY idx_create_time (create_time) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';

Metadata is fetched with:

SELECT column_name, data_type, column_comment
FROM information_schema.columns
WHERE table_schema = 'yjgj_base' AND table_name = 'test_db';

SELECT TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = 'yjgj_base' AND table_name = 'test_db';

2. Write Templates

Key templates (excerpt):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${daoPackageName}.${daoName}">
  <!-- BaseResultMap -->
  <resultMap id="BaseResultMap" type="${entityPackageName}.${entityName}">
    <#list columns as pro>
      <#if pro.proName == primaryId>
        <id column="${primaryId}" property="${primaryId}" jdbcType="${pro.fieldType}"/>
      <#else>
        <result column="${pro.fieldName}" property="${pro.proName}" jdbcType="${pro.fieldType}"/>
      </#if>
    </#list>
  </resultMap>
  <!-- Insert batch example -->
  <insert id="insertList" parameterType="java.util.List">
    INSERT INTO ${tableName} (
      <#list columns as pro>
        <#if pro_index == 0>${pro.fieldName}<#else>, ${pro.fieldName}</#if>
      </#list>
    ) VALUES
    <#list columns as pro>
      <#if pro_index == 0>(${"#{obj."+pro.proName+"}"})
      <#else>, (${"#{obj."+pro.proName+"}"})
    </#list>
  </insert>
</mapper>

Similar Freemarker files are created for dao, service, serviceImpl, controller, entity, dto and vo. The templates use placeholders such as ${entityName}, ${packageNamePre}, ${primaryId}, etc.

3. Core Utility Classes

The generator relies on a few shared abstractions: BaseMapper<T> – defines generic CRUD methods (insert, update, delete, select). BaseService<T> – declares service‑level operations. BaseServiceImpl<M extends BaseMapper<T>, T> – implements BaseService using the mapper. BaseDTO, Pager<T>, IdRequest – common request/response objects.

Example of BaseMapper:

public interface BaseMapper<T> {
  int insertList(@Param("list") List<T> list);
  int insertPrimaryKeySelective(T entity);
  int updatePrimaryKeySelective(T entity);
  int updateBatchByIds(@Param("list") List<T> list);
  int deleteByPrimaryKey(Serializable id);
  T selectByPrimaryKey(Serializable id);
  List<T> selectByPrimaryKeySelective(T entity);
  List<T> selectByIds(@Param("ids") List<? extends Serializable> ids);
  List<T> selectByMap(Map<String, Object> columnMap);
}

And BaseServiceImpl implements the methods with transaction handling:

public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T> {
  @Autowired protected M baseMapper;
  @Transactional public boolean insert(T entity) { return returnBool(baseMapper.insertPrimaryKeySelective(entity)); }
  @Transactional public boolean insertList(List<T> list) { return returnBool(baseMapper.insertList(list)); }
  @Transactional public boolean updateById(T entity) { return returnBool(baseMapper.updatePrimaryKeySelective(entity)); }
  @Transactional public boolean deleteById(Serializable id) { return returnBool(baseMapper.deleteByPrimaryKey(id)); }
  public T selectById(Serializable id) { return baseMapper.selectByPrimaryKey(id); }
  protected boolean returnBool(Integer result) { return result != null && result >= 1; }
}

4. Configuration and Main Execution

A simple application.properties defines variables such as package prefix, module name, table name, entity name, database connection, and output directory:

# package prefix
packageNamePre=com.example.generator
# module name
moduleName=test
# table
tableName=test_db
# entity class
entityName=TestEntity
# primary key
primaryId=id
# author
authorName=pzblog
# DB connection
ipName=127.0.0.1
portName=3306
userName=root
passWord=123456
# output path (empty uses src/main/java)
outUrl=

The SystemConstant class loads these properties, and GeneratorMain builds a Map<String,Object> with all values, then calls CodeService.generate() to render every file.

public class GeneratorMain {
  public static void main(String[] args) {
    System.out.println("生成代码 start......");
    Map<String, Object> templateData = new HashMap<>();
    templateData.put("tableName", SystemConstant.tableName);
    templateData.put("entityName", SystemConstant.entityName);
    // ... other properties ...
    CodeService dataService = new CodeService();
    dataService.generate(templateData);
    System.out.println("生成代码 end......");
  }
}

Running the main method produces a full set of Java files (entity, DAO, mapper XML, service, service implementation, controller, DTO, VO) under the configured output directory. The generated controller for the example looks like:

@RestController
@RequestMapping("/testEntity")
public class TestEntityController {
  @Autowired private TestEntityService testEntityService;
  @PostMapping("/getPage") public Pager<TestEntityVO> getPage(@RequestBody TestEntityDTO request) {
    return testEntityService.getPage(request);
  }
  @PostMapping("/save") public void save(TestEntityDTO request) {
    TestEntity entity = new TestEntity();
    BeanUtils.copyProperties(request, entity);
    testEntityService.insert(entity);
  }
  // other CRUD endpoints omitted for brevity
}

Conclusion

The tutorial demonstrates that, with Freemarker and a few utility classes, a complete CRUD code base for a single table can be generated in seconds, reducing manual effort by an order of magnitude. The approach is easily extensible to multiple tables and more complex business logic.

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 generationBackend Developmentspring-bootMyBatisFreemarker
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.