使Mybatis開發變得更加輕鬆的增強工具 — Ourbatis
Mybatis是一款優秀的及其靈活的持久層框架,通過XML配置並對映到Mapper介面為Service層提供基礎資料操作入口。
這麼優秀的框架竟然還有不足之處?
俗話說人無完人,因為Mybatis實在是太靈活了,靈活到每個Mapper介面都需要定製對應的XML,所以就會引發一些問題。
問題一:配置檔案繁多
假如一個系統中DB中涉及100張表,我們就需要寫 100
個Mapper介面,還沒完,最可怕的是,我們要為這 100
個Mapper介面定製與之對應的 100
套XML。而每個Mapper都必不可少的需要增刪改查功能,我們就要寫 100
遍增刪改查,作為高貴的Java開發工程師,這是不能容忍的,於是 Mybatis Generator
誕生了,然而又會引發另一個問題!
問題二:維護困難
我們使用 Mybatis Generator
解決了問題一,再多的檔案生成就是了,簡單粗暴,貌似解決了所有的問題,Mybatis完美了!
不要高興的太早,在系統剛剛建立起來時,我們使用 Mybatis Generator
生成了一堆XML,在開發過程中,產品忽然提了一個新的需求,專案經理根據這個需求在某張表中增加或變動了一個欄位,這時,我猜你的操作是這樣:
Mybatis Generator
在這個過程中,如果我們在第2步時漏複製了一段標籤,等整個操作完成之後,又別是一番滋味在心頭~
問題三:編寫XML困難
假如肝不錯,問題二也是小CASE,那麼問題又來了,我們如何在繁長的XML中去編寫和修改我們的XML呢。
當我們開啟要編輯的XML,映入眼簾的就是1000多行的XML,其中900行都是通用的增刪改查操作,要新增一個標籤,我們需要拉至檔案底部編寫新的資料操作,要更新一個標籤,我們需要通過 Ctrl + F
尋找目標標籤再進行修改。
如何避免這些問題呢?
如何讓Mybatis增強通用性又不失靈活呢?
二、使用Ourbatis輔助Mybatis
Ourbatis是一款Mybatis開發增強工具,小巧簡潔,專案地址:
- Github: ofollow,noindex">github.com/ainilili/ou…
- Gitee: gitee.com/ainilili/ou…
- Wiki: github.com/ainilili/ou…
- Demo: github.com/ainilili/ou…
特性:
- 1 、簡潔方便,可以讓Mybatis無XML化開發。
- 2 、優雅解耦,通用和自定義的SQL標籤完全隔離,讓維護更加輕鬆。
- 3 、無侵入性,Mybatis和Ourbatis可同時使用,配置簡潔。
- 4 、靈活可控,通用模板可自定義及擴充套件。
- 5 、部署快捷,只需要一個依賴,兩個配置,即可直接執行。
- 6 、多資料來源,在多資料來源環境下也可以照常使用。
關於Ourbatis使用的一個小Demo
環境:
- Spring Boot 2.0.5.RELEASE
- Ourbatis 1.0.5
- JAVA 8
- Mysql
以 Spring Boot 2.0.5.RELEASE
版本為例,在可以正常使用Mybatis的專案中, pom.xml
新增如下依賴:
<dependency> <groupId>com.smallnico</groupId> <artifactId>ourbatis-spring-boot-starter</artifactId> <version>1.0.5</version> </dependency> 複製程式碼
在配置檔案中增加一下配置:
ourbatis.domain-locations=實體類所在包名 複製程式碼
接下來,Mapper介面只需要繼承 SimpleMapper
即可:
import org.nico.ourbatis.domain.User; public interface UserMapper extends SimpleMapper<User, Integer>{ } 複製程式碼
至此,一個使用Ourbatis的簡單應用已經部署起來了,之後,你就可以使用一些Ourbatis預設的通用操作方法:
public T selectById(K key); public T selectEntity(T condition); public List<T> selectList(T condition); public long selectCount(Object condition); public List<T> selectPage(Page<Object> page); default PageResult<T> selectPageResult(Page<Object> page){ long total = selectCount(page.getEntity()); List<T> results = null; if(total > 0) { results = selectPage(page); } return new PageResult<>(total, results); } public K selectId(T condition); public List<K> selectIds(T condition); public int insert(T entity); public int insertSelective(T entity); public int insertBatch(List<T> list); public int update(T entity); public int updateSelective(T entity); public int updateBatch(List<T> list); public int delete(T condition); public int deleteById(K key); public int deleteBatch(List<K> list); 複製程式碼
Mapper自定義方法
在很多場景中,我們使用以上的自帶的通用方法遠遠不能滿足我們的需求,我們往往需要額外擴充套件新的Mapper方法、XML標籤,我們使用了Ourbatis之後該如何實現呢?
首先看一下我們的需求,在上述Demo中,我們在UserMapper中增加一個方法 selectNameById
:
import org.nico.ourbatis.domain.User; public interface UserMapper extends SimpleMapper<User, Integer>{ public String selectNameById(Integer userId); } 複製程式碼
和Mybatis一樣,需要在 resources
資源目錄下新建一個資料夾 ourbatis-mappers
,然後在其中新建一個XML檔案,命名規則為:
DomainClassSimpleName + Mapper.xml 複製程式碼
其中 DomainClassSimpleName
就是我們實體類的類名,這裡是為 User
,那麼新建的XML名為 UserMapper.xml
。
src/main/resources - ourbatis-mappers - UserMapper.xml 複製程式碼
之後,開啟 UserMapper.xml
,開始編寫Mapper中 selectNameById
方法對應的標籤:
<select id="selectNameById" resultType="java.lang.String"> select name from user where id = #{userId} </select> 複製程式碼
注意,整個檔案中只需要寫標籤就行了,其他的什麼都不需要,這是為什麼呢?深入之後你就會明白,這裡先不多說!
接下來,就沒有接下來了,可以直接使用 selectNameById
方法了。
深入瞭解Ourbatis

當服務啟動的時候,Ourbatis首先會掃描 ourbatis.domain-locations
配置包下的所有實體類,將之對映為與之對應的表結構資料:

然後通過 ourbatis.xml
的渲染,生成一個又一個的XML檔案,最後將之重新Build到Mybatis容器中!
整個過程分為兩個核心點:
ourbatis.xml
我會一一介紹之~
對映實體類為元資料
在對映時,我們要根據自己資料庫欄位命名的風格去調整對映規則,就需要在第1個核心點中去做處理,Ourbatis使用包裝器來完成:
public interface Wrapper<T> { public String wrapping(T value); } 複製程式碼
對於需要對映的欄位,如 表名 和 表字段名 ,它們都將會經過一個包裝器鏈條的處理之後再投入到 ourbatis.xml
中做渲染,這樣就使得我們可以自定義包裝器出更換對映的欄位格式,具體方式可以參考官方Wiki: Wrapper包裝器
使用 ourbatis.xml
渲染元資料為XML檔案
而在於第2個核心點中,Ourbatis通過自定義標籤做模板渲染,我們可以先看一下官方預設的 ourbatis.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="@{mapperClassName}"> <resultMap id="BaseResultMap" type="@{domainClassName}"> <ourbatis:foreach list="primaryColumns" var="elem"> <id column="@{elem.jdbcName}" property="@{elem.javaName}" /> </ourbatis:foreach> <ourbatis:foreach list="normalColumns" var="elem"> <result column="@{elem.jdbcName}" property="@{elem.javaName}" /> </ourbatis:foreach> </resultMap> <sql id="Base_Column_List"> <ourbatis:foreach list="allColumns" var="elem" split=","> `@{elem.jdbcName}` </ourbatis:foreach> </sql> <select id="selectById" parameterType="java.lang.Object" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from @{tableName} where 1 = 1 <ourbatis:foreach list="primaryColumns" var="elem"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> </select> <select id="selectEntity" parameterType="@{domainClassName}" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from @{tableName} where 1 = 1 <ourbatis:foreach list="allColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> limit 1 </select> <select id="selectCount" parameterType="@{domainClassName}" resultType="long"> select count(0) from @{tableName} where 1 = 1 <ourbatis:foreach list="allColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> limit 1 </select> <select id="selectPage" parameterType="org.nico.ourbatis.entity.Page" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from @{tableName} where 1 = 1 <if test="entity != null"> <ourbatis:foreach list="allColumns" var="elem"> <if test="entity.@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{entity.@{elem.javaName}} </if> </ourbatis:foreach> </if> <if test="orderBy != null"> order by ${orderBy} </if> <if test="start != null and end != null"> limit ${start},${end} </if> </select> <select id="selectList" parameterType="@{domainClassName}" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from @{tableName} where 1 = 1 <ourbatis:foreach list="allColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> </select> <select id="selectId" parameterType="@{domainClassName}" resultType="java.lang.Object"> select <ourbatis:foreach list="primaryColumns" var="elem" split=","> `@{elem.jdbcName}` </ourbatis:foreach> from @{tableName} where 1 = 1 <ourbatis:foreach list="allColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> limit 1 </select> <select id="selectIds" parameterType="@{domainClassName}" resultType="java.lang.Object"> select <ourbatis:foreach list="primaryColumns" var="elem" split=","> `@{elem.jdbcName}` </ourbatis:foreach> from @{tableName} where 1 = 1 <ourbatis:foreach list="normalColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> </select> <delete id="deleteById" parameterType="java.lang.Object"> delete from @{tableName} where 1=1 <ourbatis:foreach list="primaryColumns" var="elem"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> </delete> <insert id="insert" keyProperty="@{primaryColumns.0.jdbcName}" useGeneratedKeys="true" parameterType="@{domainClassName}"> insert into @{tableName} ( <include refid="Base_Column_List" /> ) values ( <ourbatis:foreach list="allColumns" var="elem" split=","> #{@{elem.javaName}} </ourbatis:foreach> ) </insert> <insert id="insertSelective" keyProperty="@{primaryColumns.0.jdbcName}" useGeneratedKeys="true" parameterType="@{domainClassName}"> insert into @{tableName} ( <ourbatis:foreach list="primaryColumns" var="elem" split=","> `@{elem.jdbcName}` </ourbatis:foreach> <ourbatis:foreach list="normalColumns" var="elem"> <if test="@{elem.javaName} != null"> ,`@{elem.jdbcName}` </if> </ourbatis:foreach> ) values ( <ourbatis:foreach list="primaryColumns" var="elem"> #{@{elem.javaName}} </ourbatis:foreach> <ourbatis:foreach list="normalColumns" var="elem"> <if test="@{elem.javaName} != null"> , #{@{elem.javaName}} </if> </ourbatis:foreach> ) </insert> <insert id="insertBatch" keyProperty="@{primaryColumns.0.jdbcName}" useGeneratedKeys="true" parameterType="java.util.List"> insert into @{tableName} ( <include refid="Base_Column_List" /> ) values <foreach collection="list" index="index" item="item" separator=","> ( <ourbatis:foreach list="allColumns" var="elem" split=","> #{item.@{elem.javaName}} </ourbatis:foreach> ) </foreach> </insert> <update id="update" parameterType="@{domainClassName}"> update @{tableName} <set> <ourbatis:foreach list="normalColumns" var="elem" split=","> `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> </set> where 1=1 <ourbatis:foreach list="primaryColumns" var="elem"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> </update> <update id="updateSelective" parameterType="@{domainClassName}"> update @{tableName} <set> <ourbatis:foreach list="primaryColumns" var="elem" split=","> `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> <ourbatis:foreach list="normalColumns" var="elem"> <if test="@{elem.javaName} != null"> ,`@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> </set> where 1=1 <ourbatis:foreach list="primaryColumns" var="elem"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </ourbatis:foreach> </update> <update id="updateBatch" parameterType="java.util.List"> <foreach collection="list" index="index" item="item" separator=";"> update @{tableName} <set> <ourbatis:foreach list="normalColumns" var="elem" split=","> `@{elem.jdbcName}` = #{item.@{elem.javaName}} </ourbatis:foreach> </set> where 1=1 <ourbatis:foreach list="primaryColumns" var="elem"> and `@{elem.jdbcName}` = #{item.@{elem.javaName}} </ourbatis:foreach> </foreach> </update> <delete id="deleteBatch" parameterType="java.util.List"> delete from @{tableName} where @{primaryColumns.0.jdbcName} in <foreach close=")" collection="list" index="index" item="item" open="(" separator=","> #{item} </foreach> </delete> <delete id="delete" parameterType="@{domainClassName}"> delete from @{tableName} where 1 = 1 <ourbatis:foreach list="allColumns" var="elem"> <if test="@{elem.javaName} != null"> and `@{elem.jdbcName}` = #{@{elem.javaName}} </if> </ourbatis:foreach> </delete> <ourbatis:ref path="classpath:ourbatis-mappers/@{domainSimpleClassName}Mapper.xml" /> </mapper> 複製程式碼
可以看出來, ourbatis.xml
內容類似於原生的Mybatis的XML,不同的是,有兩個陌生的標籤:
- ourbatis:foreach 對元資料中的列表進行迴圈渲染
- ourbatis:ref 引入外界檔案內容
這是Ourbatis中獨有的標籤,Ourbatis也提供著對應的入口讓我們去自定義標籤:
Class: org.nico.ourbatis.Ourbatis Field: public static final Map<String, AssistAdapter> ASSIST_ADAPTERS = new HashMap<String, AssistAdapter>(){ private static final long serialVersionUID = 1L; { put("ourbatis:foreach", new ForeachAdapter()); put("ourbatis:ref", new RefAdapter()); } }; 複製程式碼
我們可以修改 org.nico.ourbatis.Ourbatis
類中的靜態引數 ASSIST_ADAPTERS
去刪除、更新和新增自定義標籤,需要實現一個標籤介面卡,我們可以看一下最簡單的 RefAdapter
介面卡的實現:
public class RefAdapter extends AssistAdapter{ @Override public String adapter(Map<String, Object> datas, NoelRender render, Document document) { String path = render.rending(datas, document.getParameter("path"), "domainSimpleClassName"); String result =StreamUtils.convertToString(path.replaceAll("classpath:", "")); return result == null ? "" : result.trim(); } } 複製程式碼
Ourbatis中只定義了上述兩個自定義標籤已足夠滿足需求,通過 foreach
標籤,將元資料中的集合遍歷渲染,通過 ref
標籤引入外界資源,也就是我們之前所說的對Mapper介面中方法的擴充套件!
<ourbatis:ref path="classpath:ourbatis-mappers/@{domainSimpleClassName}Mapper.xml" /> 複製程式碼
其中的path就是當前專案classpath路徑的相對路徑,而 @{domainSimpleClassName}
就代表著實體類的類名,更多的系統引數可以參考Wiki: 元資料對映
通過這種模板渲染的機制,Ourbatis是相當靈活的,我們不僅可以通過引入外部檔案進行擴充套件,當我們需要新增或修改通用方法時,我們可以可以自定義 ourbatis.xml
的內容,如何做到呢?複製一份將之放在資源目錄下就可以了!
看到這裡,相信大家已經知道Ourbatis的基本原理已經使用方式,我就再次不多說了,更多細節可以去官方Wiki中閱讀: Ourbtis Wiki