V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
laziji
V2EX  ›  分享创造

Mybatis 自动代码生成器的实现

  •  1
     
  •   laziji · 2018-11-26 20:19:15 +08:00 · 2411 次点击
    这是一个创建于 2190 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原博地址https://laboo.top/2018/11/26/a-db/#more

    本文介绍如何用 Java 编写高度自定义的代码生成器

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息。

    上面这一段话来自Mybatis 官网的介绍, 初用 Mybatis 时感觉这个框架相比于 JDBC 优雅多了, 用起来也如官网说的非常简单。但是用了一段时间之后, 弊端就慢慢凸显出来了

    使用 Mybatis 时不得不为每个表创建一个Entity.javaMapper.xml(Mapper 可以融合入 Dao 中)Dao.java,Service.java 层次很清晰, 但是太多重复性的工作了, 费时间且易于出错

    并且当数据库发生一点改动的时候... 苦不堪言

    后来出现了自动生成代码的插件, 但是总是不尽人意, 不能随心所欲地控制, 毕竟每个人的需求都不一样

    本文就来介绍如何简单的编写一个自己的代码生成器

    项目源码

    mybatis-generator

    代码实现

    实现的思路很简单, 首先查询数据库的表结构, 得到列名, 列类型...等信息

    创建文件模版, 将这些信息插入模版中, 最后打包模版进压缩包导出

    代码实现 一共五个 Java 类

    • TableDO
    • ColumnDO
    • GeneratorMapper
    • GeneratorUtils
    • GeneratorService

    首先来看两个实体类

    TableDO 和 ColumnDO

    TableDO 存放表名, 对于的类名, 以及列信息

    完整类代码 TableDO.java

    public class TableDO {
    
        private String tableName;
        private List<ColumnDO> columns;
        private String className;
        private String suffix;
    
        // get()... set()...
    }
    

    ColumnDO 存放列名, 数据库字段类型, 以及对应 Java 中的属性名和类型

    完整类代码 ColumnDO.java

    public class ColumnDO {
    
        private String columnName;
        private String dataType;
        private String attrName;
        private String attrLowerName;
        private String attrType;
    
        // get()... set()...
    }
    

    GeneratorMapper

    在 GeneratorMapper 中, 我们通过表名查询表自动的信息

    完整类代码 GeneratorMapper.java

    @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 中进行类信息与模版之间的转换

    完整类代码 GeneratorUtils.java

    将表信息放入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 生成代码, 然后导出压缩包

    完整类代码 GeneratorService.java

    @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

    生成的代码是在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}.yml文件, ${name}随意, 例如: application-example.yml, 可创建多个

    配置文件内容如下, 填入数据库配置, 以及生成代码的包名, 源文件路径

    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);
        }
    }
    
    

    欢迎关注我的博客公众号 2018_11_16_0048241709.png

    1 条回复    2018-11-27 08:33:49 +08:00
    watzds
        1
    watzds  
       2018-11-27 08:33:49 +08:00 via Android
    不错啊,自己写一个
    我是直接改了点 mybatis-generator 代码
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1412 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 17:06 · PVG 01:06 · LAX 09:06 · JFK 12:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.