拒绝一次性买卖MyBatis的mapper和repository可重复生成工具

3次阅读

共计 3209 个字符,预计需要花费 9 分钟才能阅读完成。

背景

MyBatis 的历史可谓久远了,码农们也在用着各式各样的代码生成工具。然而这些工具大部分都有一个缺点,那就是只能一次性生成文件。如果我们期间在生成的文件里做了修改,再次生成时,很多工具会覆盖我们的修改。

为什么会在生成文件后进行修改呢?因为工具只会帮我们生成通用的数据库访问方法(比如只生成基本的 CURD 操作),我们不可避免的要根据实际的业务需要,添加其他的操作方法。

同时,数据库也不是设计完之后就一成不变的了,我们也可能在开发的过程中,调整已经建好的表结构。这个时候问题就来了,利用工具再生成一次?那就要人肉合并修改;手动添加更改后的字段进去?太多了怕遗漏。

所以,我们需要找到一个方法,解决这个痛点。

原理

熟悉.NET 的同学可能知道,大名鼎鼎 Visual Studio 也会帮开发人员生成很多代码,比如 asp.net 中的 aspx 的后台代码,它是如何保证被工具所生成的代码片段和开发人员自己写的代码片段不冲突的呢?它实际上用到了 C# 的分部类(partial) 特性。

简单来说,分部类,就是把一个类的代码,放到多个文件中去写,C# 编译器负责把他们编译到一个类中。有了这个特性,代码生成器就只专注他负责的 partial 文件就可以了,开发人员的代码写到另外一个 partial 文件中,当年用 partial + T4,写了很多代码生成模板,屡试不爽。

但我们的 JAVA 不支持这个神器啊(这里说句题外话,几年前我从 C#转到 JAVA 的时候,就感觉 C#在语言层面比 JAVA 好太多了,现在好几年没碰 C# 了,不知道它又先进到什么程度了),怎么办呢?

只有用不是办法的办法了,那就是继承。实体类、Repository 接口,用继承的方式,把工具生成的代码和预留给开发人员人肉的代码,分割到两个文件中。

但 mapper.xml 怎么办?这个 MyBatis 帮我们想好了(赞一个),利用 namespace 即可做到。只要 namespace 指向同一个 Repository 接口,不论是不是在同一个 xml 文件里,MyBatis 都可以正确找到。

例如我们有一个 Repository 是这么定义的:

public interface UserRepository{User selectByPrimaryKey(@Param("id") Long id);

    User selectByAccount(@Param("account") String account);
}

那么以下的两个 mapper.xml 结合起来是完全可用的

UserMapper1.xml

<?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="com.abc.demo.repository.UserRepository">
    <sql id="TableName">
        jm_user
    </sql>

    <sql id="BaseColumnList">
     `id`, `account`, `email`, `is_active`
    </sql>

    <resultMap id="BaseResultMap" type="com.abc.demo.entity.User" autoMapping="false">
        <result column="id" property="id" jdbcType="BIGINT"/>
        <result column="account" property="account" jdbcType="VARCHAR"/>
        <result column="email" property="email" jdbcType="VARCHAR"/>
        <result column="is_active" property="isActive" jdbcType="BIT"/>
    </resultMap>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap">
        select
        <include refid="BaseColumnList"/>
        from
        <include refid="TableName"/>
        where
            `id` = #{id}
    </select>
</mapper>

UserMapper2.xml

<?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="com.abc.demo.repository.UserRepository">
    <select id="selectByAccount" resultMap="BaseResultMap">
        select
        <include refid="BaseColumnList"/>
        from
        <include refid="TableName"/>
        where
            `account` = #{account}
    </select>
</mapper>

你看,UserMapper2.xml 中只定义了 selectByAccount 方法,BaseColumnList、TableName、BaseResultMap 都没有重新定义,可以直接用 UserMapper1.xml 的。

jasmine —— 基于数据库模型和 velocity 模板的代码生成工具

利用以上原理,我写了一个代码生成工具,读取数据库模型,并基于 velocity 模板,生成代码。
可以命令行形式运行,也可以作为 IDEA 的插件运行。

项目地址:https://github.com/kongxiangx…
工具下载地址:https://github.com/kongxiangx…

源码里提供了一个示例 DEMO,里面包含了 MyBatis 的代码生成模板,支持如下特性:

  1. 一键生成实体类、MyBatis Repository、MyBatis Mapper
  2. 实体类、MyBatis Repository 和 MyBatis Mapper 均利用继承策略,划分出 XXXX 和 XXXXBase 两个文件,其中 XXXX 如果文件存在则不覆盖,XXXXBase 每次生成都会覆盖。如果在生成后你需要做一些代码上的调整,请在 XXXX 文件中修改,而不要在 XXXXBase 中修改。这样做的好处是一旦我们的表结构发生变化需要重新生成时,不会覆盖您手动改过的代码。
  3. 如果表存在 is_deleted 字段,生成的 delete 方法是逻辑删除而不是物理删除。
  4. 如果表存在 record_version 字段,update 语句带有乐观锁,即 update …. set record_version=record_version + 1 where …. and record_version=#{record_version}
  5. 如果表存在 create_time,insert 语句这一列的值是 now()
  6. 如果表存在 update_time, insert 和 update 语句这一列的值是 now()

Quick Start

  1. 去 releases 页面,下载最新的 jasmine-[version].zip,解压。
  2. clone 源码至本地,根据实际情况,修改 demo/jasmine.properties 中 jdbc 相关的配置(主要是数据库连接配置)
  3. 执行以下命令:
/path/to/jasmine-[version]/bin/jasmine /path/to/jasmine-src/demo/jasmine.properties

如果一切正常,会在 demo 下看到生成出来的文件

正文完
 0