Open-Source Tool Generates Full-Stack Spring Boot CRUD Code from DB Schema

This article introduces an open-source code generator that automatically creates complete Spring Boot CRUD layers—entities, DAOs, MyBatis mappers, services, converters, and controllers—from database schemas or SQL, featuring customizable templates with dynamic parameters and reusable code blocks for team-specific conventions.

Architect's Guide
Architect's Guide
Architect's Guide
Open-Source Tool Generates Full-Stack Spring Boot CRUD Code from DB Schema

Motivation

The author faced a project requiring 20+ new tables. Manually creating each table's entity classes, CRUD interfaces, and SQL took roughly 2 hours per table — 40+ hours total. To eliminate this repetitive work, they built a code generation tool over five evenings and open-sourced it.

Tool Demo

The deployed tool is accessible at https://utilsbox.cn/. The workflow:

Enter table name and Chinese description.

Add fields (name, type, comment, primary key, etc.) via a UI similar to database tools.

Click "One-Click Generate Code" to produce all layers.

Generated artifacts include:

CREATE TABLE SQL

CRUD SQL (MyBatis XML)

Entity classes (DO/DTO)

DAO interfaces (MyBatis @Mapper)

Service interfaces and implementations

Model converters (DO ↔ DTO, list conversion)

Controllers with REST endpoints

For existing tables, the tool can parse a CREATE TABLE SQL statement, auto-populate fields, and generate code after adding the Chinese table name.

Template Configuration for Generality

Recognizing that project structures and coding conventions vary across teams, the tool provides a "Code Template Configuration" feature. Users can add/remove template categories and edit individual templates. Templates support import/export for team sharing.

Generation Principle: Template + Dynamic Parameters = Match & Replace

The engine combines user-defined templates with dynamic parameters. At generation time, each parameter placeholder (e.g., $table_name_hump_A$) is replaced with its computed value.

Example template:

/**
 * $table_desc$Model模型
 * Created by 创建人 on $current_time$.
 */
public class $table_name_hump_A$Model extends ToString {
}

With inputs table_name = goods_order, table_desc = 商品订单, the output becomes:

/**
 * 商品订单Model模型
 * Created by 创建人 on 2023-02-05 17:12:32.
 */
public class GoodsOrderModel extends ToString {
}

Theoretically, any language can be targeted by configuring appropriate templates.

Default Templates (Reference for Customization)

CREATE TABLE SQL Template

CREATE TABLE `$table_name$` (
  $create_table_field_list$
  PRIMARY KEY (`$primary_key$`)
) ENGINE=$db_engine$ DEFAULT CHARSET=$db_encoded$;

Entity Class Template

/**
 * $table_desc$DTO模型
 * Created by 创建人 on $current_time$.
 */
public class $table_name_hump_A$DO {
  $member_param_list$
  $get_set_method_list$
}

Member block (repeated per field):

/** $field_comment$ */
private $field_type_java$ $field_name_hump$;

Getter/Setter block (repeated per field):

public $field_type_java$ get$field_name_hump_A$() {
  return $field_name_hump$;
}
public void set$field_name_hump_A$($field_type_java$ $field_name_hump$) {
  this.$field_name_hump$ = $field_name_hump$;
}

DAO Interface Template (MyBatis)

@Mapper
public interface $table_name_hump_A$DAO {
  int insert($table_name_hump_A$DO data);
  int update($table_name_hump_A$DO data);
  List<$table_name_hump_A$DO> pageQuery(Map param);
  Long pageQueryCount(Map param);
  $table_name_hump_A$DO queryById(@Param("$primary_key_hump$") $primary_key_type_java$ $primary_key_hump$);
  $table_name_hump_A$DO queryByIdLock(@Param("$primary_key_hump$") $primary_key_type_java$ $primary_key_hump$);
}

MyBatis XML Mapper Template

<mapper namespace="com.xxx.$table_name_hump_A$DAO">
  <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO $table_name$($insert_field_name_list$)
    VALUES ($insert_field_value_list$);
  </insert>
  <update id="update">
    UPDATE $table_name$ SET
    $update_field_list$
    WHERE $primary_key$ = #{$primary_key_hump$};
  </update>
  <select id="pageQuery" resultType="com.xxx.$table_name_hump_A$DO">
    SELECT $select_field_list$ FROM $table_name$
    WHERE 1=1
    $where_field_list$
    ORDER BY $primary_key$ DESC
    <if test="pageIndex != null and pageSize != null">
      LIMIT #{offset},#{rows}
    </if>
  </select>
  <select id="pageQueryCount" resultType="java.lang.Long">
    SELECT COUNT(1) as total FROM $table_name$
    WHERE 1=1
    $where_field_list$
  </select>
  <select id="queryById" resultType="com.xxx.$table_name_hump_A$DO">
    SELECT $select_field_list$ FROM $table_name$
    WHERE $primary_key$ = #{$primary_key_hump$};
  </select>
  <select id="queryByIdLock" resultType="com.xxx.$table_name_hump_A$DO">
    SELECT $select_field_list$ FROM $table_name$
    WHERE $primary_key$ = #{$primary_key_hump$} FOR UPDATE;
  </select>
</mapper>

Model Converter Template

public class $table_name_hump_A$Converter {
  public static $table_name_hump_A$DO toDo($table_name_hump_A$DTO source) {
    $table_name_hump_A$DO target = new $table_name_hump_A$DO();
    $converter_source_to_target_params_list$
    return target;
  }
  public static $table_name_hump_A$DTO toDto($table_name_hump_A$DO source) {
    $table_name_hump_A$DTO target = new $table_name_hump_A$DTO();
    $converter_source_to_target_params_list$
    return target;
  }
  public static List<$table_name_hump_A$DTO> toDtoList(List<$table_name_hump_A$DO> data) {
    if (CollectionUtils.isEmpty(data)) return null;
    List<$table_name_hump_A$DTO> list = new ArrayList<>();
    for ($table_name_hump_A$DO item : data) {
      list.add($table_name_hump_A$Converter.toDto(item));
    }
    return list;
  }
}

Converter parameter block (repeated per field):

target.set$field_name_hump_A$(source.get$field_name_hump_A$());

Service Implementation Template

@Service
public class $table_name_hump_A$ServiceImpl implements $table_name_hump_A$Service {
  private static final Logger LOGGER = LoggerFactory.getLogger($table_name_hump_A$ServiceImpl.class);
  @Resource
  private $table_name_hump_A$DAO $table_name_hump$DAO;

  @Override
  public CommonResult create(JSONObject request) {
    CommonAssert.isNoEmptyObj(request, "请求参数不可空");
    $table_name_hump_A$DTO dto = JSON.toJavaObject(request, $table_name_hump_A$DTO.class);
    $biz_check_required_params$
    $table_name_hump_A$DO dataDo = $table_name_hump_A$Converter.toDo(dto);
    int count = $table_name_hump$DAO.insert(dataDo);
    CommonAssert.isTrue(count > 0, "创建失败,请重试");
    return new CommonResult(dataDo.get$primary_key_hump_A$());
  }
  // modify, pageQuery, queryById follow similar patterns
}

Required-params validation block (repeated per non-primary-key field):

CommonAssert.$java_type_adapter_assert_method$(dto.get$field_name_hump_A$(), "$field_comment$不可空");

For String fields, $java_type_adapter_assert_method$ resolves to isNoBlankStr; for others, isNoEmptyObj.

Controller Template

@CrossOrigin
@RestController
@RequestMapping(value = "/$table_name_hump$/")
public class $table_name_hump_A$Controller {
  private static final Logger LOGGER = LoggerFactory.getLogger($table_name_hump_A$Controller.class);
  @Resource
  private $table_name_hump_A$Service $table_name_hump$Service;

  @RequestMapping(value = "create.json", method = {RequestMethod.GET, RequestMethod.POST})
  @ResponseBody
  public Object create(@RequestBody JSONObject request) {
    return CommonTemplate.run(LOGGER, new CommonTemplate() {
      @Override
      protected Object business() {
        return $table_name_hump$Service.create(request);
      }
    }, request);
  }
  // modify, pageQuery, queryById endpoints follow the same pattern
}

Dynamic Parameters

The tool provides over 20 built-in dynamic parameters, categorized as:

Table-level: $table_name$ (raw), $table_name_hump$ (camelCase), $table_name_hump_A$ (PascalCase), $table_desc$ (Chinese name), $db_engine$, $db_encoded$, $current_time$ (yyyy-MM-DD hh:mm:ss).

Field-level (repeated per column): $field_name$, $field_name_hump$, $field_name_hump_A$, $field_comment$, $field_type_db$, $field_type_java$ (auto-mapped, e.g., VARCHAR → String).

Primary-key specific: $primary_key$, $primary_key_hump$, $primary_key_hump_A$, $primary_key_type_java$.

SQL fragment generators: $insert_field_name_list$ (excludes PK), $insert_field_value_list$ (excludes PK, uses #{camelCase}), $update_field_list$ (multi-line, excludes PK), $select_field_list$ (includes PK), $where_field_list$ (generates <if test="..."> blocks per field), $create_table_field_list$ (full DDL column definitions with auto-increment for PK).

Special: $java_type_adapter_assert_method$ (chooses assertion method by Java type).

Dynamic Code Blocks

Four predefined block types repeat their content for each field, substituting field-level parameters: $member_param_list$ — member variable declarations. $get_set_method_list$ — getter/setter pairs. $converter_source_to_target_params_list$ — field copy statements for converters. $biz_check_required_params$ — required-field validation calls.

Each block's internal template is user-editable, enabling project-specific conventions (e.g., Lombok annotations, custom validation messages).

Open Source & Links

Source code: https://github.com/GooseCoding/utilsbox Online demo: https://utilsbox.cn/

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-bootmybatistemplate-engineopen-source-toolscrud-automation
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.