Mybatis是一款優秀的及其靈活的持久層框架,經過XML配置並映射到Mapper接口爲Service層提供基礎數據操做入口。java
這麼優秀的框架居然還有不足之處?git
俗話說人無完人,由於Mybatis實在是太靈活了,靈活到每一個Mapper接口都須要定製對應的XML,因此就會引起一些問題。github
假如一個系統中DB中涉及100張表,咱們就須要寫100
個Mapper接口,還沒完,最可怕的是,咱們要爲這100
個Mapper接口定製與之對應的100
套XML。而每一個Mapper都必不可少的須要增刪改查功能,咱們就要寫100
遍增刪改查,做爲高貴的Java開發工程師,這是不能容忍的,因而Mybatis Generator
誕生了,然而又會引起另外一個問題!spring
咱們使用Mybatis Generator
解決了問題一,再多的文件生成就是了,簡單粗暴,貌似解決了全部的問題,Mybatis完美了!sql
不要高興的太早,在系統剛剛創建起來時,咱們使用Mybatis Generator
生成了一堆XML,在開發過程當中,產品突然提了一個新的需求,項目經理根據這個需求在某張表中增長或變更了一個字段,這時,我猜你的操做是這樣:數據庫
Mybatis Generator
從新生成該表的XML在這個過程當中,若是咱們在第2步時漏複製了一段標籤,等整個操做完成以後,又別是一番滋味在心頭~bash
假如肝不錯,問題二也是小CASE,那麼問題又來了,咱們如何在繁長的XML中去編寫和修改咱們的XML呢。mybatis
當咱們打開要編輯的XML,映入眼簾的就是1000多行的XML,其中900行都是通用的增刪改查操做,要新增一個標籤,咱們須要拉至文件底部編寫新的數據操做,要更新一個標籤,咱們須要經過Ctrl + F
尋找目標標籤再進行修改。app
如何避免這些問題呢?框架
如何讓Mybatis加強通用性又不失靈活呢?
Ourbatis是一款Mybatis開發加強工具,小巧簡潔,項目地址:
特性:
環境:
以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方法、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.domain-locations
配置包下的全部實體類,將之映射爲與之對應的表結構數據:
而後經過ourbatis.xml
的渲染,生成一個又一個的XML文件,最後將之從新Build到Mybatis容器中!
整個過程分爲兩個核心點:
ourbatis.xml
渲染元數據爲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中獨有的標籤,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