背景
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{
//aaa
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>
<!--asdfdsfsdf-->
<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/kongxiangxin/jasmine
工具下載地址:https://github.com/kongxiangxin/jasmine/releases
源碼里提供了一個示例DEMO,里面包含了MyBatis的代碼生成模板,支持如下特性:
- 一鍵生成實體類、MyBatis Repository、MyBatis Mapper
- 實體類、MyBatis Repository和MyBatis Mapper均利用繼承策略,划分出XXXX和XXXXBase兩個文件,其中XXXX如果文件存在則不覆蓋,XXXXBase每次生成都會覆蓋。如果在生成后你需要做一些代碼上的調整,請在XXXX文件中修改,而不要在XXXXBase中修改。這樣做的好處是一旦我們的表結構發生變化需要重新生成時,不會覆蓋您手動改過的代碼。
- 如果表存在is_deleted字段,生成的delete方法是邏輯刪除而不是物理刪除。
- 如果表存在record_version字段,update語句帶有樂觀鎖,即update .... set record_version=record_version + 1 where .... and record_version=#{record_version}
- 如果表存在create_time,insert語句這一列的值是now()
- 如果表存在update_time, insert和update語句這一列的值是now()
Quick Start
- 去releases頁面,下載最新的jasmine-[version].zip,解壓。
- clone源碼至本地,根據實際情況,修改demo/jasmine.properties中jdbc相關的配置(主要是數據庫連接配置)
- 執行以下命令:
/path/to/jasmine-[version]/bin/jasmine /path/to/jasmine-src/demo/jasmine.properties
如果一切正常,會在demo下看到生成出來的文件