原博地址https://laboo.top/2018/11/26/a-db/#more
本文介绍如何用 Java 编写高度自定义的代码生成器
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息。
上面这一段话来自Mybatis 官网
的介绍, 初用 Mybatis 时感觉这个框架相比于 JDBC 优雅多了, 用起来也如官网说的非常简单。但是用了一段时间之后, 弊端就慢慢凸显出来了
使用 Mybatis 时不得不为每个表创建一个Entity.java
、Mapper.xml(Mapper 可以融合入 Dao 中)
、Dao.java
,Service.java
层次很清晰, 但是太多重复性的工作了, 费时间且易于出错
并且当数据库发生一点改动的时候... 苦不堪言
后来出现了自动生成代码的插件, 但是总是不尽人意, 不能随心所欲地控制, 毕竟每个人的需求都不一样
本文就来介绍如何简单的编写一个自己的代码生成器
实现的思路很简单, 首先查询数据库的表结构, 得到列名, 列类型...
等信息
创建文件模版, 将这些信息插入模版中, 最后打包模版进压缩包导出
代码实现 一共五个 Java 类
首先来看两个实体类
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.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.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 类完成了生成代码的主要工作, 但是代码也是比较简单的
在 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 ...
}
自己写代码生成器的好处就是, 可以根据需求定制自己的模版, 下面是我的几个模版可以供参考
生成的代码是在commons-mybatis架构下使用的
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 文件下创建测试类
@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);
}
}
欢迎关注我的博客公众号
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.