使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

相关文章
相关标签/搜索