本文介绍如何用 Java 编写高度自定义的代码生成器
MyBatis 是一款优秀的持久层框架, 它支持定制化 SQL, 存储过程以及高级映射. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyBatis 可以使用简单的 xml 或注解来配置和映射原生信息.
上面这一段话来自 Mybatis 官网的介绍, 初用 Mybatis 时感觉这个框架相比于 JDBC 优雅多了, 用起来也如官网说的非常简单. 但是用了一段时间之后, 弊端就慢慢凸显出来了
使用 Mybatis 时不得不为每个表创建一个 Entity.java,Mapper.xml(Mapper 可以融合入 Dao 中),Dao.java,Service.java 层次很清晰, 但是太多重复性的工作了, 费时间且易于出错
并且当数据库发生一点改动的时候... 苦不堪言
后来出现了自动生成代码的插件, 但是总是不尽人意, 不能随心所欲地控制, 毕竟每个人的需求都不一样
本文就来介绍如何简单的编写一个自己的代码生成器
项目源码
https://github.com/GitHub-Laziji/mybatis-generator
代码实现
实现的思路很简单, 首先查询数据库的表结构, 得到列名, 列类型... 等信息
创建文件模版, 将这些信息插入模版中, 最后打包模版进压缩包导出
代码实现 一共五个 Java 类
- TableDO
- ColumnDO
- GeneratorMapper
- GeneratorUtils
- GeneratorService
首先来看两个实体类
TableDO 和 ColumnDO
TableDO 存放表名, 对于的类名, 以及列信息
完整类代码
- public class TableDO {
- private String tableName;
- private List<ColumnDO> columns;
- private String className;
- private String suffix;
- // get()... set()...
- }
ColumnDO 存放列名, 数据库字段类型, 以及对应 Java 中的属性名和类型
完整类代码
- public class ColumnDO {
- private String columnName;
- private String dataType;
- private String attrName;
- private String attrLowerName;
- private String attrType;
- // get()... set()...
- }
GeneratorMapper
在 GeneratorMapper 中, 我们通过表名查询表自动的信息
完整类代码
- @Mapper
- public interface GeneratorMapper {
- @Select("select column_name columnName, data_type dataType from information_schema.columns where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position")
- List<ColumnDO> listColumns(String tableName);
- }
GeneratorUtils
在 GeneratorUtils 中进行类信息与模版之间的转换
完整类代码
将表信息放入 Velocity 模版的上下文中
- Map<String, Object> map = new HashMap<>();
- map.put("tableName", table.getTableName());
- map.put("className", table.getClassName());
- map.put("pathName", getPackageName().substring(getPackageName().lastIndexOf(".") + 1));
- map.put("columns", table.getColumns());
- map.put("package", getPackageName());
- map.put("suffix", table.getSuffix());
- Properties prop = new Properties();
- prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
- Velocity.init(prop);
- VelocityContext context = new VelocityContext(map);
添加模版
- List<String> templates = new ArrayList<>();
- templates.add("mybatis/Model.java.vm");
- templates.add("mybatis/Query.java.vm");
- templates.add("mybatis/Dao.java.vm");
- templates.add("mybatis/Mapper.xml.vm");
- templates.add("mybatis/Service.java.vm");
编译模版
- StringWriter sw = new StringWriter();
- Template tpl = Velocity.getTemplate(template, "UTF-8");
- tpl.merge(context, sw);
Utils 类完成了生成代码的主要工作, 但是代码也是比较简单的
GeneratorService
在 Service 中注入 Mapper 查询列信息, 并用 Utils 生成代码, 然后导出压缩包
完整类代码
- @Service
- public class GeneratorService {
- @Resource
- private GeneratorMapper generatorMapper;
- @Resource
- private Environment environment;
- public void generateZip(String[] tableNames, String zipPath) throws IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- ZipOutputStream zip = new ZipOutputStream(outputStream);
- for (String tableName : tableNames) {
- TableDO table = new TableDO();
- table.setTableName(tableName);
- table.setColumns(generatorMapper.listColumns(tableName));
- GeneratorUtils.generatorCode(table, zip,getConfig());
- }
- IOUtils.closeQuietly(zip);
- FileOutputStream file = new FileOutputStream(zipPath);
- file.write(outputStream.toByteArray());
- file.close();
- }
- // getConfig ...
- }
VM 模版
自己写代码生成器的好处就是, 可以根据需求定制自己的模版, 下面是我的几个模版可以供参考
- Mapper.xml.vm
- Dao.java.vm
- Service.java.vm
- Model.java.vm
- Query.java.vm
生成的代码是在 https://github.com/GitHub-Laziji/commons-mybatis 架构下使用的
- Dao.java.vm
- package ${
- package
- }.database.dao;
- import ${
- package
- }.database.model.${
- className
- }${
- suffix
- };
- import org.apache.ibatis.annotations.Mapper;
- import org.laziji.commons.mybatis.dao.${
- suffix
- }Dao;
- @Mapper
- public interface ${
- className
- }Dao extends ${
- suffix
- }Dao<${
- className
- }${
- suffix
- }> {
- }
...
其余模版
使用
配置文件
在 resources 下创建 application-${name}.YAML 文件, ${name} 随意, 例如: application-example.YAML, 可创建多个
配置文件内容如下, 填入数据库配置, 以及生成代码的包名, 源文件路径
- spring:
- datasource:
- url: jdbc:MySQL://xxx.xxx.xxx.xxx:3306/xxxx?characterEncoding=utf-8
- username: xxxxxx
- password: xxxxxx
- generator:
- package: com.xxx.xxx
- resources: mapper
Test
在 test 文件下创建测试类
@ActiveProfiles("example")
中填入刚才配置文件名的 name
tableNames 需要生成的表, 可以多个
zipPath 代码导出路径 运行测试方法即可
- package pg.laziji.generator;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.ActiveProfiles;
- import org.springframework.test.context.junit4.SpringRunner;
- import pg.laziji.generator.mybatis.GeneratorService;
- import javax.annotation.Resource;
- import java.io.IOException;
- @ActiveProfiles("example")
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class ExampleTest {
- @Resource
- private GeneratorService generatorService;
- @Test
- public void test() throws IOException {
- String[] tableNames = new String[]{"example_table1", "example_table2"};
- String zipPath = "/home/code.zip";
- generatorService.generateZip(tableNames,zipPath);
- }
- }
来源: https://juejin.im/post/5bfbe4d9f265da6118016309