使Mybatis開發變得更加輕鬆的加強工具 — Ourbatis

1、Mybatis的不足之處

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,在開發過程當中,產品突然提了一個新的需求,項目經理根據這個需求在某張表中增長或變更了一個字段,這時,我猜你的操做是這樣:數據庫

  • 一、找到對應表的XML
  • 二、將該XML中自定義的一段標籤複製出來,保存在本地
  • 三、使用Mybatis Generator從新生成該表的XML
  • 四、將之覆蓋當前的XML
  • 五、將自定義的一段標籤再粘貼進新的XML中

在這個過程當中,若是咱們在第2步時漏複製了一段標籤,等整個操做完成以後,又別是一番滋味在心頭~bash

問題三:編寫XML困難

假如肝不錯,問題二也是小CASE,那麼問題又來了,咱們如何在繁長的XML中去編寫和修改咱們的XML呢。mybatis

當咱們打開要編輯的XML,映入眼簾的就是1000多行的XML,其中900行都是通用的增刪改查操做,要新增一個標籤,咱們須要拉至文件底部編寫新的數據操做,要更新一個標籤,咱們須要經過Ctrl + F尋找目標標籤再進行修改。app

如何避免這些問題呢?框架

如何讓Mybatis加強通用性又不失靈活呢?

2、使用Ourbatis輔助Mybatis

Ourbatis是一款Mybatis開發加強工具,小巧簡潔,項目地址:

特性:

  • 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首先會掃描ourbatis.domain-locations配置包下的全部實體類,將之映射爲與之對應的表結構數據:

ourbatis Mapping

而後經過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: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

相關文章
相關標籤/搜索