一刺桃、Mybatis的不足之處
Mybatis是一款優(yōu)秀的及其靈活的持久層框架,通過XML配置并映射到Mapper接口為Service層提供基礎數(shù)據(jù)操作入口镣屹。
這么優(yōu)秀的框架竟然還有不足之處?
俗話說人無完人喳挑,因為Mybatis實在是太靈活了味赃,靈活到每個Mapper接口都需要定制對應的XML掀抹,所以就會引發(fā)一些問題。
問題一:配置文件繁多
假如一個系統(tǒng)中DB中涉及100張表心俗,我們就需要寫100
個Mapper接口傲武,還沒完,最可怕的是另凌,我們要為這100
個Mapper接口定制與之對應的100
套XML谱轨。而每個Mapper都必不可少的需要增刪改查功能戒幔,我們就要寫100
遍增刪改查吠谢,作為高貴的Java開發(fā)工程師,這是不能容忍的诗茎,于是Mybatis Generator
誕生了工坊,然而又會引發(fā)另一個問題!
問題二:維護困難
我們使用Mybatis Generator
解決了問題一敢订,再多的文件生成就是了王污,簡單粗暴,貌似解決了所有的問題楚午,Mybatis完美了昭齐!
不要高興的太早,在系統(tǒng)剛剛建立起來時矾柜,我們使用Mybatis Generator
生成了一堆XML阱驾,在開發(fā)過程中,產(chǎn)品忽然提了一個新的需求怪蔑,項目經(jīng)理根據(jù)這個需求在某張表中增加或變動了一個字段里覆,這時,我猜你的操作是這樣:
- 1缆瓣、找到對應表的XML
- 2喧枷、將該XML中自定義的一段標簽復制出來,保存在本地
- 3弓坞、使用
Mybatis Generator
重新生成該表的XML - 4隧甚、將之覆蓋當前的XML
- 5、將自定義的一段標簽再粘貼進新的XML中
在這個過程中渡冻,如果我們在第2步時漏復制了一段標簽戚扳,等整個操作完成之后,又別是一番滋味在心頭~
問題三:編寫XML困難
假如肝不錯菩帝,問題二也是小CASE咖城,那么問題又來了茬腿,我們?nèi)绾卧诜遍L的XML中去編寫和修改我們的XML呢。
當我們打開要編輯的XML宜雀,映入眼簾的就是1000多行的XML切平,其中900行都是通用的增刪改查操作,要新增一個標簽辐董,我們需要拉至文件底部編寫新的數(shù)據(jù)操作悴品,要更新一個標簽,我們需要通過Ctrl + F
尋找目標標簽再進行修改简烘。
如何避免這些問題呢苔严?
如何讓Mybatis增強通用性又不失靈活呢?
二孤澎、使用Ourbatis輔助Mybatis
Ourbatis是一款Mybatis開發(fā)增強工具届氢,小巧簡潔,項目地址:
- Github:https://github.com/ainilili/ourbatis
- Gitee:https://gitee.com/ainilili/ourbatis
- Wiki:https://github.com/ainilili/ourbatis/wiki
- Demo:https://github.com/ainilili/ourbatis-simple
特性:
- 1覆旭、簡潔方便退子,可以讓Mybatis無XML化開發(fā)。
- 2型将、優(yōu)雅解耦寂祥,通用和自定義的SQL標簽完全隔離,讓維護更加輕松七兜。
- 3丸凭、無侵入性,Mybatis和Ourbatis可同時使用腕铸,配置簡潔惜犀。
- 4、靈活可控恬惯,通用模板可自定義及擴展向拆。
- 5、部署快捷酪耳,只需要一個依賴浓恳,兩個配置,即可直接運行碗暗。
- 6颈将、多數(shù)據(jù)源,在多數(shù)據(jù)源環(huán)境下也可以照常使用言疗。
關于Ourbatis使用的一個小Demo
環(huán)境:
- 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的簡單應用已經(jīng)部署起來了人乓,之后,你就可以使用一些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之后該如何實現(xiàn)呢戳护?
首先看一下我們的需求,在上述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文件榛瓮,命名規(guī)則為:
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
配置包下的所有實體類,將之映射為與之對應的表結構數(shù)據(jù):
然后通過ourbatis.xml
的渲染橘蜜,生成一個又一個的XML文件菊匿,最后將之重新Build到Mybatis容器中!
整個過程分為兩個核心點:
- 1计福、映射實體類為元數(shù)據(jù)
- 2跌捆、使用
ourbatis.xml
渲染元數(shù)據(jù)為XML文件
我會一一介紹之~
映射實體類為元數(shù)據(jù)
在映射時,我們要根據(jù)自己數(shù)據(jù)庫字段命名的風格去調整映射規(guī)則象颖,就需要在第1個核心點中去做處理佩厚,Ourbatis使用包裝器來完成:
public interface Wrapper<T> {
public String wrapping(T value);
}
對于需要映射的字段,如表名和表字段名说订,它們都將會經(jīng)過一個包裝器鏈條的處理之后再投入到ourbatis.xml
中做渲染抄瓦,這樣就使得我們可以自定義包裝器出更換映射的字段格式潮瓶,具體方式可以參考官方Wiki:Wrapper包裝器
使用ourbatis.xml
渲染元數(shù)據(jù)為XML文件
而在于第2個核心點中,Ourbatis通過自定義標簽做模板渲染钙姊,我們可以先看一下官方默認的ourbatis.xml
內(nèi)部構造:
<?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
內(nèi)容類似于原生的Mybatis的XML,不同的是晒他,有兩個陌生的標簽:
- ourbatis:foreach 對元數(shù)據(jù)中的列表進行循環(huán)渲染
- ourbatis:ref 引入外界文件內(nèi)容
這是Ourbatis中獨有的標簽璧南,Ourbatis也提供著對應的入口讓我們?nèi)プ远x標簽:
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
類中的靜態(tài)參數(shù)ASSIST_ADAPTERS
去刪除、更新和添加自定義標簽立镶,需要實現(xiàn)一個標簽適配器壁袄,我們可以看一下最簡單的RefAdapter
適配器的實現(xiàn):
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
標簽媚媒,將元數(shù)據(jù)中的集合遍歷渲染嗜逻,通過ref
標簽引入外界資源,也就是我們之前所說的對Mapper接口中方法的擴展缭召!
<ourbatis:ref path="classpath:ourbatis-mappers/@{domainSimpleClassName}Mapper.xml" />
其中的path就是當前項目classpath路徑的相對路徑栈顷,而@{domainSimpleClassName}
就代表著實體類的類名,更多的系統(tǒng)參數(shù)可以參考Wiki:元數(shù)據(jù)映射
通過這種模板渲染的機制嵌巷,Ourbatis是相當靈活的萄凤,我們不僅可以通過引入外部文件進行擴展,當我們需要添加或修改通用方法時搪哪,我們可以可以自定義ourbatis.xml
的內(nèi)容靡努,如何做到呢?復制一份將之放在資源目錄下就可以了晓折!
看到這里惑朦,相信大家已經(jīng)知道Ourbatis的基本原理已經(jīng)使用方式,我就再次不多說了漓概,更多細節(jié)可以去官方Wiki中閱讀:Ourbtis Wiki