之前写过两篇关于MyBatis的文章,旨在探讨基于MyBatis开发时如何精简和加快后端的开发,分别是: MaBatis 的 Mapper 模板通用书写 分享 和 只是加个字段而已——如何解决程序员之痛 ,有兴趣有时间的不妨先看这两篇文章。
两篇文章显得过于分散,并且存在某些地方描述得不够清楚的问题,所以本文主要是做一个更深入的总结和阐述,还有花点文字说一下写这些文章的意义——毕竟没有意义的东西做得再好也只能是0。
首先要说就是写这些文字的用意,众所周知,MyBatis有很多好用的工具或插件,比如自动生成这些东西早已烂大街,我也使用过,虽然我对它们嗤之以鼻。甚至还有MyBatis Plus增强工具——这个工具是我发现的MyBatis体系中最好用的了,热衷于使用这些工具进行快速开发的朋友建议直接使用它,可以少走很多弯路。MyBatis Plus虽然很好用,甚至于它的设计其实打动了我了,但是我还是决定不去使用它,原因如下:
一、我认为手写SQL能够锻炼一个人的能力,写多了,潜移默化中就得到了积累。而使用工具,虽然获得了眼前的甜头,却失去了更大的好处。
二、MyBatis为自由而生。MyBatis Plus这些增强性工具,难道MyBatis官方开发不出来吗?它为什么不在框架中直接集成?私以为那不是MyBatis的初衷,它的初衷是让大家更好的管理数据层代码。这些工具跟MyBatis的初衷是背道而驰的。
三、我发现了开发速度不亚于这些工具的开发方式,直接基于MyBatis,兼具手写SQL的自由与速度,只要你学会了它,不仅不再被速度拖累,而且能够大大锻炼你的能力。这个方式唯一的缺点就是:它不是一个让你随手拿来就能用的东西,它更像是一种思想,你需要理解它,然后才能掌握它。我坚信的是:“技术就是思想,思想就是技术”,如果你也是这样的人,那么请往下看,否则,便不必浪费时间往下看了。
再来说说那些工具做的是什么事?帮助你快速解决单表操作的问题。也就是一些简单的crud的事情,其他的还是需要自己动手——那么不使用那些工具能不能解决crud问题呢?当然可以。
之所以需要解决这个问题,很大程度上是因为需求经常变动,增加字段无可避免,而使用实体映射库表字段的方式,添加一个字段需要更改实体、库表、resultMap映射和具体SQL等,这里面最大的问题就是——大家都习惯了使用实体来映射库表字段,MyBatis官方也提倡这样的做法。
思考一个问题如果无法解决,那么很有可能那个问题本身就是不存在的,于是我往前一步思考,去掉实体后会是什么样的?——直到这时候我才打开了新世界的大门,一个实体里的成员变量和setter、getter完全没有必要存在。那么使用什么来承装数据呢?Map或JSON。
于是我的Mapper.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="com.xxx.mapper.Mapper">
<sql id="table_name">
serviceStatistics
</sql>
<sql id="column">
id, `type`, `count`, createTime
</sql>
<sql id="values">
#{id}, #{type}, #{count}, #{createTime}
</sql>
<sql id="set_values">
id=#{id}, `type`=#{type}, `count`=#{count}, createTime=#{createTime}
</sql>
<sql id="condition">
<if test="id != null and id != ''">
AND id = #{id}
</if>
<if test="type != null and type != ''">
AND `type` = #{type}
</if>
<if test="from != null and from != '' and to != null and to != ''">
AND createTime BETWEEN #{from} AND #{to}
</if>
</sql>
<insert id="add">
INSERT INTO <include refid="table_name"/>
(
<include refid="column"/>
)VALUES(
<include refid="values"/>
)
</insert>
<select id="query" resultType="Map">
SELECT `count`
FROM <include refid="table_name"/>
WHERE 1=1 <include refid="condition"/>
</select>
</mapper>
对应的Mapper接口则是这样的:
public interface Mapper {
int add(Map<String, Object> entityMap);
List<Map<String, Object>> query(Map<String, Object> parameter);
}
这里面的代码作了很大的删减,但是足以具备代表性。
首先注意到在Mapper接口中的add方法中,直接使用Map传递参数,可以把它看作一个适配所有实体的“抽象实体”,但其实就是一个简单的Map而已,我什么都没做。接着,查询方法的参数是Map,返回的参数是一个Map的List。
然后,重点在于xml文件中的设计:使用<sql></sql>标签自定义一些重复性高的代码:表名、新增修改的设值字段和查询条件。
为了达到简化的目标,我去掉了jdbcType这个属性,去掉的理由是:它仅仅是定义了空值时MyBatis如何提供默认值而已。比如说,int类型和date类型的默认值是不一样的。也许有的人认为这个属性非常重要,但我依然决定去掉,因为只要开发者在代码中做了正确的空值处理就不会有问题,而且我有一个疑惑:空真的重要吗?空的东西我不想处理它,如果空值导致了错误,我觉得代码已经存在问题了,最好的处理方式是:如果空值:返回错误给调用方,必须传值,如果是非必传的值,那么应该设值默认值,如果默认值也没有,那么数据库应该放开检验,不要让sql出现错误。
常用的curd,每次只需要复制粘贴这个模板的代码(有缺少,自行补充即可)就over了,那些字段全部都是数据库里的字段,自行更改即可。以后产品姐姐跑过来跟你说要加字段的话,你再也不会像以前那样惊慌失措了(脑海里冒出所有需要添加的地方),只需要淡定地说:OK!我这就加上去!
这个方式比MyBatis的代码生成工具好用得多,需要添加字段时,重复生成一遍可能行不通,因为你已经在里面加过其他的代码了,不能直接覆盖。
同时,这种方式比MyBatis Plus要慢一点点,因为它直接用注解来映射,crud封装起来了,不需要手写这部分的代码,加字段时只需要在实体中加上即可。我们这里需要加以下三个地方:
<sql id="column">
id, `type`, `count`, createTime
</sql>
<sql id="values">
#{id}, #{type}, #{count}, #{createTime}
</sql>
<sql id="set_values">
id=#{id}, `type`=#{type}, `count`=#{count}, createTime=#{createTime}
</sql>
而在crud以外的sql中,我们跟MyBatis Plus相同,同时也跟JFinal的相同,但是——需要再次强调一次但是:我们是完全的手写SQL,便于维护。同时这种方式更加简洁明了,便于理解。
所以,这么做的意义还有最后的这一点:我觉得有更好的或者相近的解决方案的话,尽量不使用工具,这不仅是锻炼自己的问题,而且还是项目管理的问题——使用的插件越多,未知的问题就越多。老子说,大道至简。好用的东西不一定好用,好用的思想一定好用。
以上为此次总结,在这方面的探讨也告一段落了,希望能够帮助到志同道合的朋友。