Spring Boot实现高质量的CRUD-2

(续前文)

5、Dao类 ​

​	Dao类提供操作访问数据库表的接口方法。常规的CRUD,将考虑下列接口方法: ​	1)插入单条对象记录; ​	2)批量插入对象记录; ​	3)修改单条对象记录; ​	4)批量修改对象记录; ​	5)删除单条对象记录; ​	6)批量删除对象记录; ​	7)查询多条对象记录; ​	8)查询指定key的对象记录; ​	9)查询记录条数; ​	10)根据唯一字段(或组合字段)查询单条对象记录。  ​	Dao类使用@Mapper注解,类中代码应仅实现接口方法定义,不建议使用@Select,@Insert等注解直接在Dao类中实现接口(特殊情况除外),考虑的后期接口功能扩展的可能性,在Mybatis xml脚本文件中实现接口书写更容易规范,功能也更强大。 

5.1、新增对象

​	新增对象和批量新增对象的方法名和形式如下所示。其中如果支持自增ID,则返回值为对象ID。 
	/** 	 * @methodName	: insertItem 	 * @description	: 新增一个XXX对象 	 * @param item	: XXX对象 	 * @return		: 受影响的记录数 	 * @history		: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int insertItem(XXX item);  	/** 	 * @methodName		: insertItems 	 * @description		: 批量新增XXX对象 	 * @param itemList	: XXX对象列表 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int insertItems(List<XXX> itemList); 
​	批量新增对象接口,在数据库表为关系表时,更需要使用;而数据库表为对象表时,往往因为复杂的数据库一致性校验和关联操作,不常使用。 ​	新增对象和批量新增对象的处理性能相差不大,即批量插入100条记录和插入单条记录的时间相差无几。因此需要大量新增对象时,应考虑批量新增对象的接口。  ​	下面分别是新增用户和批量新增用户角色关系的方法示例: 
	/** 	 * @methodName	: insertItem 	 * @description	: 新增一个用户对象 	 * @param item	: 用户对象 	 * @return		: 受影响的记录数 	 * @history		: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int insertItem(User item);  	/** 	 * @methodName		: insertItems 	 * @description		: 新增一批用户对象 	 * @param itemList	: 用户对象列表 	 * @return			: 受影响的记录数 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int insertItems(List<User> itemList); 

5.2、修改对象

​	修改单个对象和批量修改对象的方法名和形式如下所示。​	 
	/** 	 * @methodName		: updateItemByKey 	 * @description		: 根据key修改一个XXX对象 	 * @param params	: XXX对象相关属性字段字典,key字段必选,其它字段可选 	 * 	{ 	 * 		"keyPropName1"	: 0,	// keyPropName1说明,必选 	 * 		"keyPropName2"	: 0,	// keyPropName2说明,必选 	 * 		...... 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int updateItemByKey(Map<String, Object> params);  	/** 	 * @methodName		: updateItems 	 * @description		: 批量修改XXX对象 	 * @param params	: 请求参数,所有字段可选,但至少需要一个修改字段和一个条件字段 	 * 	{ 	 * 		修改字段: 	 * 		"setPropName1"	: 0,	// setPropName1字段说明,可选 	 * 		...... 	 * 		条件字段: 	 * 		"condPropName1"	: 0,	// condPropName1字段说明,可选 	 * 		...... 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int updateItems(Map<String, Object> params); 
​	修改单个对象需要所有的主键属性字段,从而确定唯一的对象。而其它字段都应允许修改,且可选。(记录操作字段中的create_time和update_time字段不在修改字段之列)。 ​	不建议使用下列接口形式来实现单个对象的修改: 
public int updateItem(XXX item); 
​	updateItem这种接口形式的问题是参数形式为实体类对象,需要整个实体类对象的属性,对于前端而言,需要将所有属性都赋值,如有遗漏,会造成信息被默认值覆盖;另外可能前端也未必能获取到对象的全部信息。因此,这种接口形式会提高处理的复杂度。  ​	批量修改由于影响多条记录,误操作造成的影响和损失是巨大的,因此,往往要控制至少一个条件字段。另外,此接口如果修改大量记录,与删除大量数据一样,可能会造成数据库死锁。此接口是否需要提供,视业务需求而定。 ​	批量修改,往往对状态字段进行修改,或存在父级对象(如主从表)的冗余字段进行修改,可修改的字段是极为有限的。 ​	另外批量修改,一般情况下,不建议使用多表联结,可以先将外表的条件转为本表字段的值的列表,使用in条件进行单表操作。  ​	下面分别是修改用户和批量修改用户的方法示例: 
	/** 	 * @methodName		: updateItemByKey 	 * @description		: 根据key修改一个用户对象 	 * @param params	: 用户对象相关属性字段字典,key字段必选,其它字段可选 	 * 	{ 	 * 		"userId"	: 0L,		// 用户ID,必选 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int updateItemByKey(Map<String, Object> params);  	/** 	 * @methodName		: updateItems 	 * @description		: 根据条件批量修改用户对象的相关属性字段的值 	 * @param params	: 用户对象相关字段字典,至少需要一个修改字段和一个条件字段,修改字段和条件字段均可选; 	 * 	{ 	 * 					  修改字段集如下: 	 * 		"orgId"			: 0,	// 组织机构ID,可选 	 * 		"userType"		: 3,	// 用户类型,1-系统管理员、2-公司内部用户、3-外部用户,可选 	 * 		"deleteFlag"	: 0,	// 记录删除标记,0-正常、1-禁用,可选 	 * 		"operatorName"	: "",	// 操作人账号,可选 	 * 					  条件字段如下: 	 * 		"userIdList"	: [],	// 用户ID列表,list,可选 	 * 		"userName"		: "",	// 用户名,可选 	 * 		"phoneNumber"	: "",	// 手机号码,可选 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * 2023/05/17	1.0.0		sheng.zheng		初版 	 * 	 */ 	public int updateItems(Map<String, Object> params); 

5.3、条件字段的命名规范

​	涉及批量记录,往往与条件有关,包括后面的批量删除记录,查询记录等,都需要使用条件。抛开需要使用Mysql内置函数,以及复杂AND和OR组合的个性化条件,大多数情况,多个条件使用AND进行联结。 ​	条件字段有下列可能:等于,不等于,大于,大于等于,小于,小于等于,IS NULL,IS NOT NULL,IN,NOT IN,LIKE(BETWEEN可被大于等于和小于等于替代,不必考虑),NOT BETWEEN AND(内置了OR条件)。 ​	同一个参数名,不能适用不同的条件,否则需要更多接口,增加维护的复杂度,因此需要对条件字段的名称进行规范。 ​	假设属性字段名称为propName,则条件字段的命名规则如下: 
	/** 		等于		: propName 		不等于		: propNameNeq	 		大于等于	: propNameGte 		大于		: propNameGt 		小于等于	: propNameLte 		小于		: propNameLt 		IS NULL		: propNameNull 		IS NOT NULL	: propNameNNull 		IN			: propNameList 		NOT IN		: propNameNList 		LIKE		: propNameLike 		NOT BETWEEN AND	: propNameNBtwl	// NOT BETWEEN AND的下边界值 						  propNameNBtwh	// NOT BETWEEN AND的上边界值,上下边界值必须同时出现。	  	 */ 
​	下面是一个条件参数命名示例: 
	/** 	 * @param params	: 查询参数,形式如下: 	 * 	{ 	 * 		"userType"		: 3,	// 用户类型,1-系统管理员、2-公司内部用户、3-外部用户,可选 	 * 		"userTypeList"	: [],	// 用户类型列表,in,可选 	 * 		"userTypeNList"	: [],	// 用户类型列表,not in,可选 	 * 		"sex"			: 1,	// 性别,1-无值、2-男、3-女、4-其它,可选 	 * 		"deleteFlag"	: 0,	// 记录删除标记,0-正常、1-禁用,可选 	 * 		"userName"		: "",	// 用户名,精确匹配,可选 	 * 		"userNameLike"	: "",	// 用户名,like,可选 	 * 		"phoneNumber"	: "",	// 手机号码,精确匹配,可选 	 * 		"phoneNumberLike": "",	// 手机号码,like,可选 	 * 		"realNameLike"	: "",	// 真实姓名,like,可选 	 * 		"emailLike"		: "",	// Email,like,可选 	 * 		"birthGte"		: "",	// 生日起始值,yyyy-MM-dd格式,gte,可选 	 * 		"birthLte"		: "",	// 生日终止值,yyyy-MM-dd格式,lte,可选 	 * 		"birthNull"		: "",	// 生日,is null,任意字符串值,可选 	 * 		"orgId"			: 1,	// 组织ID,可选 	 * 		"orgIdList"		: [],	// 组织ID列表,in,可选 	 * 	} 	 */ 
​	字符串类型字段,对于前端页面,常使用模糊匹配来查询记录,以扩大记录集;而内部调用时,往往使用精确匹配查询,以求缩小搜索记录集合和更高的查询性能。有了条件参数命名规则,同一个查询接口可以支持多种查询业务的需求,从而解除Dao层与Service层的紧耦合。 

5.4、删除对象

​	删除单个对象和批量删除对象的方法名和形式如下所示。​	 
	/** 	 * @methodName		: deleteItemByKey 	 * @description		: 根据key删除一个XXX对象 	 * @param keyPropName1	: 对象xxx的key1属性字段	   	 * ....					: 	  	   	 * @param keyPropNameN	: 对象xxx的keyn属性字段,如果只有一个key字段,则无此参数,数据类型依据具体key而定 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int deleteItemByKey(@Param("keyPropName1") Integer keyPropName1,...,@Param("keyPropNameN") Integer keyPropNameN);  	/** 	 * @methodName		: deleteItems 	 * @description		: 批量删除XXX对象 	 * @param params	: 请求参数,所有字段可选,但至少需要一个条件字段 	 * 	{ 	 * 		条件字段: 	 * 		"condPropName1"	: 0,	// condPropName1字段说明,可选 	 * 		...... 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int deleteItems(Map<String, Object> params); 
​	如果对象表,一般不支持物理删除,就无需提供这两个方法;对于关系表,一般支持物理删除,需要删除方法。  ​	下面分别是删除用户角色关系和批量删除用户角色关系的方法示例: 
	/** 	 * @methodName		: deleteItemByKey 	 * @description		: 根据key删除一个用户和角色关系对象 	 * @param userId	: 用户ID 	 * @param roleId	: 角色ID 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int deleteItemByKey(@Param("userId") Long userId, @Param("roleId") Integer roleId);  	/** 	 * @methodName		: deleteItems 	 * @description		: 批量删除相关用户和角色关系对象 	 * @param params	: 请求参数,要求至少有一个条件参数,形式如下: 	 * 	{ 	 * 		"roleId"	: 0,	// 角色ID,可选 	 * 		"userId"	: 0L,	// 用户ID,可选 	 * 	} 	 * @return			: 受影响的记录数 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public int deleteItems(Map<String, Object> params); 

5.5、查询对象

​	查询是使用最频繁的功能,受业务需求驱动,查询的要求也多种多样。此处仅考虑通用的查询需求,不考虑分组(GROUP)查询和联合(UNION)查询及子查询,这些特殊查询属于定制需求。 ​	建议一般情况使用单表查询,在主从表(Master-Detail)的情况下,子表可以关联主表查询。建议不要超过3个表的关联查询,否则由于搜索记录空间太过庞大(为笛卡尔乘积),即使有索引,性能也不会太好,何况有些查询条件会使得索引失效,如LIKE,大于等于等等。 ​	那么如果一个表的外键比较多,如何处理呢?建议使用分步查询以及缓存来处理,这样可以获取较好的查询性能。 ​	通用查询考虑提供下列4个方法: 
	/** 	 * 		selectItems		: 根据条件查询对象列表,此方法适用于API调用和内部调用 	 * 		selectItemByKey	: 根据key值查询一个对象 	 * 		selectCount		: 根据条件查询记录数 	 * 		selectItemByXXX	: 根据唯一键(或组合唯一键)查询一个对象 	 */ 

5.5.1、查询对象列表

​	查询对象列表的方法名和形式如下所示: 
	/** 	 * @methodName		: selectItems 	 * @description		: 根据条件查询XXX对象列表 	 * @param params	: 查询条件参数,形式如下: 	 * 	{ 	 * 		条件字段: 	 * 		"condPropName1"	: 0,	// condPropName1字段说明,可选 	 * 		...... 	 * 		"condPropNameN"	: 0,	// condPropNameN字段说明,可选 	 * 		"offset"		: 0,	// limit记录偏移量,可选 	 * 		"rows"			: 20,	// limit最大记录条数,可选 	 * 		"sortList"		: [],	// 排序选项,SortField对象列表,只支持数据表字段属性,可选 	 * 	} 	 * @return			: XXX对象列表 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public List<XXX> selectItems(Map<String, Object> params); ```	​	 	​	除了条件字段外,selectItems还有3个可选参数,其中offset和rows参数,一方面为配合单元测试辅助工具,获取一定数目的记录样本,另外也为代替分页插件提供了备选方案。分页插件使用了子查询来查询总记录数,导致有时性能很差,必要时,可以使用selectCount结合selectItems方法,取代分页插件。 	​	sortList,排序选项,支持多个字段的排序,一般的方案是支持一个字段的排序,且有sql注入的风险,不可取。排序选项是SortField对象的列表,SortField定义如下: ```java package com.abc.example.vo;  import lombok.Data;  /**  * @className	: SortField  * @description	: 排序字段信息对象  * @summary		: 用于SQL查询语句的ORDER BY  * @history		:  * ------------------------------------------------------------------------------  * date			version		modifier		remarks                     * ------------------------------------------------------------------------------  * yyyy/mm/dd	1.0.0		author		    初版  *  */ @Data public class SortField {  	// 字段属性名称 	private String fieldName = ""; 	 	// 排序次序,0-升序,1-降序 	private int sortOrder = 0; } ```	​	 	​	sortList,可以将多个字段按各自升序或降序要求,顺序设置,从而实现丰富的排序功能。 	​	没有sortList字段,常规的做法是根据业务特点将排序固化,这样处理代码实现简单,但不灵活。当然支持sortList选项,会增加较多的Mybatis的代码,是否需要支持排序选项,视业务需求而定。  	​	下面是查询用户记录的方法示例: ```java 	/** 	 * @methodName		: selectItems 	 * @description		: 根据条件查询用户对象列表,用于前端和内部查询记录 	 * @param params	: 查询参数,前端调用至少要有一个条件参数,参数形式如下: 	 * 	{ 	 * 		"userType"		: 3,	// 用户类型,1-系统管理员、2-公司内部用户、3-外部用户,可选 	 * 		"userTypeList"	: [],	// 用户类型列表,in,可选 	 * 		"userTypeNList"	: [],	// 用户类型列表,not in,可选 	 * 		"sex"			: 1,	// 性别,1-无值、2-男、3-女、4-其它,可选 	 * 		"sexList"		: [],	// 性别列表,in,可选 	 * 		"deleteFlag"	: 0,	// 记录删除标记,0-正常、1-禁用,可选 	 * 		"userName"		: "",	// 用户名,精确匹配,可选 	 * 		"userNameLike"	: "",	// 用户名,like,可选 	 * 		"phoneNumber"	: "",	// 手机号码,精确匹配,可选 	 * 		"phoneNumberLike": "",	// 手机号码,like,可选 	 * 		"realName"		: "",	// 真实姓名,精确匹配,可选 	 * 		"realNameLike"	: "",	// 真实姓名,like,可选 	 * 		"email"			: "",	// Email,精确匹配,可选 	 * 		"emailLike"		: "",	// Email,like,可选 	 * 		"birthGte"		: "",	// 生日起始值,yyyy-MM-dd格式,gte,可选 	 * 		"birthLte"		: "",	// 生日终止值,yyyy-MM-dd格式,lte,可选 	 * 		"birthNull"		: "",	// 生日,is null,任意字符串值,可选 	 * 		"orgId"			: 1,	// 组织ID,可选 	 * 		"orgIdList"		: [],	// 组织ID列表,in,可选	  	 * 		"offset"		: 0,	// limit记录偏移量,可选 	 * 		"rows"			: 20,	// limit最大记录条数,可选 	 * 		"sortList"		: [],	// 排序选项,SortField对象列表,只支持数据表字段属性,可选 	 * 	} 	 * @return			: 用户对象列表 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public List<User> selectItems(Map<String, Object> params); ```	​	 	​	对于前端而言,枚举类型如用户类型,可能是单选,也可能是多选,因此提供了userType和userTypeList条件字段,以满足不同场景的查询需求。如果枚举项很多,也可以提供类似userTypeNList的条件字段,相当于反选,以减少用户勾选点击次数。 	​	如phoneNumber字段,前端需要模糊匹配,后端内部调用需要精确匹配,因此同时提供phoneNumber和phoneNumberLike条件字段,以满足不同需求。 	​	简而言之,selectItems的条件选项集是一个大拼盘,提供丰富的条件选项,以满足不同场景的需求。	 	​	但是,也没必要所有的字段要按各种条件都给出选项。需要提供哪些条件选项,除了业务需求要求的特殊条件选项外,有以下较为通用的设置原则: ```java /** 	1、如果某属性不给任何条件,即为空集时,其效果相当于全集。 	2、对于二值枚举类型,只需要'='条件的选项,不需要'in'条件的选项。 	3、对于多值枚举类型,需要'='、'in'条件的选项,枚举项较多时,需要'not in'条件选项。 	4、对于字符串类型,如果为不易改变的字段,如name,phoneNumber等,需要'='和'like'条件的选项,必要时,还需要'in'条件选项;对于如desc、remark等容易改变的信息字段,只需提供'like'条件选项。 	5、对于本表的记录ID字段,提供'='、'in'条件选项,如果记录ID与时间序列高度相关,则可能还需要'gte'和'lt'条件选项。 	6、对于外键ID,提供'='、'in'条件选项。'in'条件选项用于分步查询,如查询:‘组织名称包含xxx的用户记录’,因为用户表没有组织名称字段(orgName),但有组织ID(orgId),因此需要先查询组织表,获取orgIdList,然后就可以在人员表中查询了。 	7、对于数值量和日期字段,如有需要,提供'gte'、'lt'条件选项,特殊情况可再提供'gt'和'lte'以及'not between and'条件选项。 	8、对于默认值为null的字段,提供'is null'和'is not null'条件选项。 	9、与数据权限相关的字段,假设用户表的orgId字段涉及数据权限,用户只能查询本组织或给定组织列表的人员记录,则此字段需要'in'条件选项,以便后端服务层根据通过提供用户的orgId权限列表,过滤查询数据。如果通过其它途径已有了orgIdList集合,后端服务层还需要将数据权限要求的orgIdList与之取交集。 */ 

5.5.2、根据key查询对象

​	根据key查询对象的方法名和形式如下所示: 
	/** 	 * @methodName		: selectItemByKey 	 * @description		: 根据key查询一个XXX对象 	 * @param keyPropName1	: 对象xxx的key1属性字段	   	 * ....					: 	  	   	 * @param keyPropNameN	: 对象xxx的keyn属性字段,如果只有一个key字段,则无此参数,数据类型依据具体key而定 	 * @return			: XXX对象 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public XXX selectItemByKey(@Param("keyPropName1") Integer keyPropName1,...,@Param("keyPropNameN") Integer keyPropNameN); 
​	根据key查询对象,经常用于获取对象,以及存在性检查。 ​	下面是根据key查询用户对象的方法示例: 
	/** 	 * @methodName		: selectItemByKey 	 * @description		: 根据key查询一个用户对象 	 * @param userId	: 用户ID 	 * @return			: 用户对象 	 * @history			: 	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public User selectItemByKey(@Param("userId") Long userId); 

5.5.3、查询对象记录数

​	查询对象记录数的方法名和形式如下所示: 
	/** 	 * @methodName		: selectCount 	 * @description		: 根据条件查询XXX对象的记录数 	 * @param params	: 查询条件参数,形式如下: 	 * 	{ 	 * 		条件字段: 	 * 		"condPropName1"	: 0,	// condPropName1字段说明,可选 	 * 		...... 	 * 		"condPropNameN"	: 0,	// condPropNameN字段说明,可选 	 * 	} 	 * @return			: XXX对象记录数 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public Integer selectCount(Map<String, Object> params); 
​	查询记录条数,经常用于存在性检查,查询计数的性能要比查询记录列表高得多,且可以减少IO和内存的开销,因此也较为常用。 ​	selectCount的条件选项一般与selectItems保持一致。 ​	如果预见表的记录数规模会很大时,返回值类型可以改为Long型,一般情况,Integer型足够了。 

5.5.4、根据唯一键查询对象

​	根据唯一键(或组合唯一键)查询对象的方法名和形式如下所示: 
	/** 	 * @methodName		: selectItemByXXX 	 * @description		: 根据XXX查询XXX对象 	 * @param uniPropName1	: 的唯一键组合属性字段1 	 * ....					: 	  	   	 * @param uniPropNameN	: 的唯一键组合属性字段N,如果只有一个字段,则无此参数,数据类型依据具体key而定 	 * @return			: XXX对象 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public XXX selectItemByXXX(@Param("uniPropName1") String uniPropName1,...,@Param("uniPropNameN") String uniPropNameN); 
​	根据唯一键组合查询对象,此方法可以完全被selectItems覆盖,但是由于Map<String, Object>类型的参数构造比较费代码,需要创建Map对象,然后再赋值,查询结果还是一个List类型。对于结果为唯一对象(或为null)的查询,服务层往往希望有简单的接口形式。因此,如果确定某些属性(或组合属性)是唯一时,可以提供此方法。 ​	一个对象,可能有多个唯一字段或唯一组合,因此每个都需要提供类似方法。如用户对象,登录名是唯一的。还有一些属性,有值时是唯一的,如手机号码、email账号、身份证号码、微信小程序openid、微信公众号openid等,这些准唯一字段,也需要提供类似方法,但是调用时,需要判断是否为例外情况,如为空串,则不能调用,否则会抛出异常。另外,由于准唯一字段,数据库层面没有使用uniquekey约束,所有的唯一性约束是通过代码层面控制的,数据库记录层面看,并没有唯一性要求,为避免意外(如记录被人工修改失去唯一性),调用时应加try/catch进行保护。 ​	下面是根据用户名查询用户对象的方法示例:	 
	/** 	 * @methodName		: selectItemByUserName 	 * @description		: 根据用户名(登录)查询用户对象 	 * @param userName	: 用户名 	 * @return			: 用户对象 	 * @history			:  	 * ------------------------------------------------------------------------------ 	 * date			version		modifier		remarks 	 * ------------------------------------------------------------------------------ 	 * yyyy/mm/dd	1.0.0		author		    初版 	 * 	 */ 	public User selectItemByUserName(@Param("userName") String userName); 

6、Mybatis脚本 ​

​	Mybatis提供操作访问数据库表的接口方法的实现,文件命名为XXXDaoMapper.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.abc.example.dao.UserDao">  </mapper> 

6.1、新增单条记录

​	新增单条记录,方法名为insertItem,使用insert标签,参数为实体类对象。分自增ID和非自增ID。非自增ID,ID可以是全局ID,或用户指定ID,需要在调用dao接口前赋值。 ​	由于数据类型可以通过实体类对象反射获取,因此无需表明JDBC数据类型;另外,由于实体类的属性都有默认值,因此,insert语句变得很简单。create_time和update_time字段,由数据库维护,无需新增。 

6.1.1、自增ID的新增单条记录

​	示例代码如下:	 
    <insert id="insertItem" parameterType="com.abc.example.entity.User">         <selectKey keyProperty="userId" order="AFTER" resultType="java.lang.Long">             SELECT LAST_INSERT_ID()         </selectKey>	         INSERT INTO exa_users(             user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag         )         VALUES(             #{userId},#{userName},#{password},#{salt},#{userType},#{orgId},#{realName},#{email},             #{phoneNumber},#{sex},#{birth},#{idNo},#{openId},#{woaOpenid},#{remark},             #{operatorName},#{deleteFlag}         )     </insert> 

6.1.2、非自增ID的新增单条记录

​	示例代码如下:	 
    <insert id="insertItem" parameterType="com.abc.example.entity.User">         INSERT INTO exa_users(             user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag         )         VALUES(             #{userId},#{userName},#{password},#{salt},#{userType},#{orgId},#{realName},#{email},             #{phoneNumber},#{sex},#{birth},#{idNo},#{openId},#{woaOpenid},#{remark},             #{operatorName},#{deleteFlag}         )     </insert> 

6.2、批量新增记录

​	批量单条记录,方法名为insertItems,使用insert标签,参数为实体类对象列表。分自增ID和非自增ID。非自增ID,需要在调用dao接口前赋值。 ​	批量新增语句,使用foreach标签来实现对列表的遍历。 

6.2.1、自增ID的批量新增记录

​	示例代码如下:	 
    <insert id="insertItems" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="userId">         INSERT INTO exa_users(             user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag         )         VALUES         <foreach collection ="list" item="item" index= "index" separator =",">         (             #{item.userId},#{item.userName},#{item.password},#{item.salt},#{item.userType},             #{item.orgId},#{item.realName},#{item.email},#{item.phoneNumber},#{item.sex},             #{item.birth},#{item.idNo},#{item.openId},#{item.woaOpenid},#{item.remark},             #{item.operatorName},#{item.deleteFlag}         )         </foreach>     </insert> 

6.2.2、非自增ID的批量新增记录

​	示例代码如下:	 
    <insert id="insertItems" parameterType="java.util.List">         INSERT INTO exa_users(             user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag         )         VALUES         <foreach collection ="list" item="item" index= "index" separator =",">         (             #{item.userId},#{item.userName},#{item.password},#{item.salt},#{item.userType},             #{item.orgId},#{item.realName},#{item.email},#{item.phoneNumber},#{item.sex},             #{item.birth},#{item.idNo},#{item.openId},#{item.woaOpenid},#{item.remark},             #{item.operatorName},#{item.deleteFlag}         )         </foreach>     </insert> 

6.3、修改单条记录

​	修改单条记录,方法名为updateItemByKey,使用update标签。参数为Map<String,Object>类型,由于参数项的值的类型为Object,因此需表明JDBC数据类型;另外,允许修改部分字段,即每个修改字段都是可选的,条件字段为对象的key值。create_time和update_time字段,由数据库维护,不允许修改。 ​	示例代码如下:	 
    <update id="updateItemByKey" parameterType="java.util.Map">         UPDATE             exa_users         <set>             <if test='userId != null'>                 user_id = #{userId,jdbcType=BIGINT},             </if>             <if test='userName != null'>                 user_name = #{userName,jdbcType=VARCHAR},             </if>             <if test='password != null'>                 password = #{password,jdbcType=VARCHAR},             </if>             <if test='salt != null'>                 salt = #{salt,jdbcType=VARCHAR},             </if>             <if test='userType != null'>                 user_type = #{userType,jdbcType=TINYINT},             </if>             <if test='orgId != null'>                 org_id = #{orgId,jdbcType=INTEGER},             </if>             <if test='realName != null'>                 real_name = #{realName,jdbcType=VARCHAR},             </if>             <if test='email != null'>                 email = #{email,jdbcType=VARCHAR},             </if>             <if test='phoneNumber != null'>                 phone_number = #{phoneNumber,jdbcType=VARCHAR},             </if>             <if test='sex != null'>                 sex = #{sex,jdbcType=TINYINT},             </if>             <if test='birth != null'>                 birth = #{birth,jdbcType=TIMESTAMP},             </if>             <if test='idNo != null'>                 id_no = #{idNo,jdbcType=VARCHAR},             </if>             <if test='openId != null'>                 open_id = #{openId,jdbcType=VARCHAR},             </if>             <if test='woaOpenid != null'>                 woa_openid = #{woaOpenid,jdbcType=VARCHAR},             </if>             <if test='remark != null'>                 remark = #{remark,jdbcType=VARCHAR},             </if>             <if test='operatorName != null'>                 operator_name = #{operatorName,jdbcType=VARCHAR},             </if>             <if test='deleteFlag != null'>                 delete_flag = #{deleteFlag,jdbcType=TINYINT},             </if>         </set>         WHERE             user_id = #{userId,jdbcType=BIGINT}      </update> 
​	示例代码中,user_id = #{userId,jdbcType=BIGINT}确保修改字段为空时,语句仍可以正常执行,此时表记录没有任何改动。	 

6.4、批量修改记录

​	批量修改记录,方法名为updateItems,使用update标签。参数为Map<String,Object>类型,由于参数项的值的类型为Object,因此需表明JDBC数据类型;批量修改的字段和条件,由业务需求决定。调用前应确保至少有一个修改字段。 ​	示例代码如下:	 
    <update id="updateItems" parameterType="java.util.Map">         UPDATE             exa_users         <set>             <if test='orgId != null'>                 org_id = #{orgId,jdbcType=INTEGER},             </if>             <if test='userType != null'>                 user_type = #{userType,jdbcType=TINYINT},             </if>             <if test='deleteFlag != null'>                 delete_flag = #{deleteFlag,jdbcType=TINYINT},             </if>             <if test='operatorName != null'>                 operator_name = #{operatorName,jdbcType=VARCHAR},             </if>         </set>         WHERE             1 = 1             <if test='userIdList != null and userIdList.size != 0'>                 AND user_id IN                 <foreach collection ="userIdList" item="userId" index= "index" open="(" close=")" separator =",">                     #{userId}                 </foreach>             </if>                             <if test='userName != null'>                 AND user_name = #{userName,jdbcType=VARCHAR}             </if>             <if test='phoneNumber != null'>                 AND phone_number = #{phoneNumber,jdbcType=VARCHAR}             </if>     </update>  
​	updateItems方法,包含了条件不为key字段的修改方法以及批量修改记录方法。	 

6.5、删除单条记录

​	删除单条记录,方法名为deleteItemByKey,使用delete标签。参数为对象的key值,由接口的@Param注解确定,由于数据类型已明确,因此无需表明JDBC数据类型。一般支持物理删除的关系表,需要删除方法,下例为用户角色表的记录删除方法。 ​	示例代码如下:	 
    <delete id="deleteItemByKey">         DELETE FROM              exa_user_roles         WHERE             user_id = #{userId} AND             role_id = #{roleId}     </delete>  

6.6、批量删除记录

​	批量删除记录,方法名为deleteItems,使用delete标签。参数为Map<String,Object>类型,由于参数项的值的类型为Object,因此需表明JDBC数据类型;批量修改的字段和条件,由业务需求决定。调用前应确保至少有一个条件字段(否则为全表删除了)。一般支持物理删除的关系表,需要批量删除方法,下例为用户角色表的批量删除记录方法。 ​	示例代码如下:	 
    <delete id="deleteItems" parameterType="java.util.Map">         DELETE FROM              exa_user_roles         WHERE             1 = 1             <if test='roleId != null'>                 AND role_id = #{roleId,jdbcType=INTEGER}             </if>             <if test='userId != null'>                 AND user_id = #{userId,jdbcType=BIGINT}             </if>     </delete>  
​	上述示例代码,允许删除指定userId的所有用户角色数据,或指定roleId的所有用户角色数据。	 

6.7、查询记录

​	查询记录,使用select标签。CRUD通用查询语句(不包括子查询、分组查询、UNION等特殊查询)包含4个部分:查询字段集、数据表及表的联结关系、查询条件、排序选项。 ​	查询字段集,是实体类字段的一个子集或全集。 ​	数据表及表的联结关系,包括主表和外键参照表,一般使用内联结(INNER JOIN),由于查询语句的执行次序优先执行ON的过滤,以避免庞大的笛卡尔乘积数,因此应使用JOIN..ON形式,而不是FROM table1,table2 WHERE形式。 ​	如果外键数目较多,应考虑分步查询或结合缓存赋值。原则上,除主表外,最多联结3个表。 ​	查询条件,根据5.3节的条件字段命名规范,确定查询条件。 ​	排序选项,使用SortField列表sortList,理论上,所有字段都可以排序。考虑到SQL注入问题,排序选项的代码会显得冗长。 

6.7.1、查询单条记录

​	查询单条记录,方法名为selectItemByKey,使用select标签。参数为对象的key值,由接口的@Param注解确定,由于数据类型已明确,因此无需表明JDBC数据类型。 ​	示例代码如下:	 
    <select id="selectItemByKey" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             user_id = #{userId,jdbcType=BIGINT}      </select>  
​	上述示例代码,为单表查询。但是实体类User还需要对orgName外键引用字段进行赋值(往往前端页面需要),这可根据org_id字段查询exa_orgnizations表获取。由于所有查询都有类似外键引用字段问题需要处理,在下节selectItems方法时进一步讨论。 

6.7.2、查询记录列表

​	查询记录列表,方法名为selectItems,使用select标签。参数为Map<String,Object>类型。 ​	示例代码如下:	 
    <select id="selectItems" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             1 = 1             <if test='userId != null'>                 AND user_id = #{userId,jdbcType=BIGINT}             </if>             <if test='userIdList != null and userIdList.size != 0'>                 AND user_id IN                 <foreach collection ="userIdList" item="userId" index= "index" open="(" close=")" separator =",">                     #{userId}                 </foreach>             </if>                             <if test='userName != null'>                 AND user_name = #{userName,jdbcType=VARCHAR}             </if>             <if test='userNameLike != null'>                 AND user_name LIKE CONCAT('%',#{userNameLike,jdbcType=VARCHAR},'%')             </if>             <if test='userType != null'>                 AND user_type = #{userType,jdbcType=TINYINT}             </if>             <if test='userTypeList != null and userTypeList.size != 0'>                 AND user_type IN                 <foreach collection ="userTypeList" item="userType" index= "index" open="(" close=")" separator =",">                     #{userType}                 </foreach>             </if>                             <if test='sex != null'>                 AND sex = #{sex,jdbcType=TINYINT}             </if>             <if test='deleteFlag != null'>                 AND delete_flag = #{deleteFlag,jdbcType=TINYINT}             </if>             <if test='phoneNumber != null'>                 AND phone_number = #{phoneNumber,jdbcType=VARCHAR}             </if>             <if test='phoneNumberLike != null'>                 AND phone_number LIKE CONCAT('%',#{phoneNumberLike,jdbcType=VARCHAR},'%')             </if>             <if test='realNameLike != null'>                 AND real_name LIKE CONCAT('%',#{realNameLike,jdbcType=VARCHAR},'%')             </if>             <if test='email != null'>                 AND email = #{email,jdbcType=VARCHAR}             </if>             <if test='emailLike != null'>                 AND email LIKE CONCAT('%',#{emailLike,jdbcType=VARCHAR},'%')             </if>             <if test='birthGte != null'>                 AND birth &gt;= #{birthGte,jdbcType=TIMESTAMP}             </if>                          <if test='birthLte != null'>                 AND birth &lt;= #{birthLte,jdbcType=TIMESTAMP}             </if>                          <if test='orgId != null'>                 AND org_id = #{orgId,jdbcType=INTEGER}             </if>             <if test='orgIdList != null and orgIdList.size != 0'>                 AND org_id IN                 <foreach collection ="orgIdList" item="orgId" index= "index" open="(" close=")" separator =",">                     #{orgId}                 </foreach>             </if>                             <if test='openId != null'>                 AND open_id = #{openId,jdbcType=VARCHAR}             </if>             <if test='woaOpenid != null'>                 AND woa_openid = #{woaOpenid,jdbcType=VARCHAR}             </if>         <if test='sortList != null and sortList.size != 0'>         ORDER BY             <foreach collection ="sortList" item="sortItem" index= "index" open="" close="" separator =",">                 <choose>                     <when test='sortItem.fieldName == "userId"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 user_id ASC                             </when>                             <otherwise>                                 user_id DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "userName"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 user_name ASC                             </when>                             <otherwise>                                 user_name DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "password"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 password ASC                             </when>                             <otherwise>                                 password DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "salt"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 salt ASC                             </when>                             <otherwise>                                 salt DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "userType"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 user_type ASC                             </when>                             <otherwise>                                 user_type DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "orgId"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 org_id ASC                             </when>                             <otherwise>                                 org_id DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "realName"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 real_name ASC                             </when>                             <otherwise>                                 real_name DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "email"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 email ASC                             </when>                             <otherwise>                                 email DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "phoneNumber"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 phone_number ASC                             </when>                             <otherwise>                                 phone_number DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "sex"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 sex ASC                             </when>                             <otherwise>                                 sex DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "birth"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 birth ASC                             </when>                             <otherwise>                                 birth DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "idNo"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 id_no ASC                             </when>                             <otherwise>                                 id_no DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "openId"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 open_id ASC                             </when>                             <otherwise>                                 open_id DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "woaOpenid"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 woa_openid ASC                             </when>                             <otherwise>                                 woa_openid DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "remark"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 remark ASC                             </when>                             <otherwise>                                 remark DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "operatorName"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 operator_name ASC                             </when>                             <otherwise>                                 operator_name DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "deleteFlag"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 delete_flag ASC                             </when>                             <otherwise>                                 delete_flag DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "createTime"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 create_time ASC                             </when>                             <otherwise>                                 create_time DESC                             </otherwise>                         </choose>                                             </when>                     <when test='sortItem.fieldName == "updateTime"'>                         <choose>                             <when test='sortItem.sortOrder == 0'>                                 update_time ASC                             </when>                             <otherwise>                                 update_time DESC                             </otherwise>                         </choose>                                             </when>                 </choose>             </foreach>                     </if>                                    <if test='rows != null'>             <choose>                 <when test='offset != null'>                     LIMIT #{offset,jdbcType=INTEGER},#{rows,jdbcType=INTEGER}                 </when>                 <otherwise>                     LIMIT #{rows,jdbcType=INTEGER}                 </otherwise>             </choose>         </if>     </select>  
​	上述示例代码,为单表查询。 ​	查询条件,使用了5.3节规定的命名规则,从而允许同一个字段的不同查询条件选项。其中对于List条件,空集等于全集,这是由于全集往往是一个很大的数据集,使用空集表示可简化处理,并且sql语句不支持"in ()"的空集形式。列表的无效集(真正的空集),可以使用一个无效的数据,如[-1]来表示。 ​	排序选项,根据sortList列表次序,确定排序字段和排序方法。由于避免使用会引入SQL注入的"${field_name}"形式,因此需要遍历所有可排序的字段。如果sortList列表为空或空集,则使用默认排序(主键字段排序)。 ​	LIMIT选项参数(rows和offset),可用于分页查询。单元测试辅助服务类,使用此参数,获取一定数量的样本记录。 

6.7.3、外键引用字段查询

​	实体类有一些外键引用字段,如User对象的orgName,其根据org_id,查询exa_orgnizations表获取。 ​	有三种处理方法,方法1为联结表查询;方法2为分步查询;方法3为分步查询结合缓存赋值。假设用户表exa_users有10万条记录,组织表exa_orgnizations有1000条记录,下面比较各种处理方法的优劣。  ​	方法1:联结表查询,即联结exa_users和exa_orgnizations表,获取org_name值。代码如下: 
    <select id="selectItems" resultType="com.abc.example.entity.User">         SELECT              t1.user_id,t1.user_name,t1.password,t1.salt,t1.user_type,t1.org_id,t1.real_name,t1.email, 			t1.phone_number,t1.sex,t1.birth,t1.id_no,t1.open_id,t1.woa_openid,t1.remark, 			t1.operator_name,t1.delete_flag,t1.create_time,t1.update_time, 			t2.org_name         FROM             exa_users t1         INNER JOIN             exa_orgnizations t2         ON             t1.org_id = t2.org_id         WHERE             ...      </select>  
​	方法1,联结查询,为了提升查询性能,此时主表应建立外键(如exa_users表的org_id字段)的索引。使用Explain对查询语句进行性能分析,优化查询语句。如果使用"t2.org_name like '%a%'"的查询条件,由于需要全表扫描联结后数据(10万条),大致需要1秒量级完成查询,性能不如方法2(分步单表查询)。 ​	方法1的优点是处理代码比较简单;缺点是可能有性能问题。  ​	方法2:分步查询,即将联结查询拆分为单表查询或更少联结表的查询。为了提升查询性能,此时主表仍应建立外键(如exa_users表的org_id字段)的索引。 ​	如查询用户数据,分两种情况。 ​	情况1:查询条件不包含exa_orgnizations表字段的查询条件,则先查询exa_users表,获取userList,然后获取不重复的orgIdList,再查询exa_orgnizations表,并建立字典(orgId到Orgnization对象的字典),然后逐个给userList的项设置orgName值。 ​	情况2:查询条件如果包含exa_orgnizations表的查询条件,如"org_name like '%a%'",则先查询exa_orgnizations表,获取orgList,并建立字典(orgId到Orgnization对象的字典),然后获取不重复的orgIdList,再根据orgIdList条件和exa_users表的其它条件,查询User记录,获取userList。然后逐个给userList的项设置orgName值。 ​	对于情况1,由于exa_orgnizations表的org_id字段为主键字段,因此orgIdList的查询条件对性能影响可以忽略不计,第二次查询时间消耗很少,主要是代码处理处理复杂度有所增加。由于需要使用外键引用字段,一般为前端,此时往往使用分页查询,假设每页50条记录,则处理orgName属性值的时间一般不超过50ms(在1000条记录中查询orgIdList,大致需要20ms,然后是内存处理)。 ​	对于情况2,由于exa_users表的org_id字段为索引字段,因此orgIdList的查询条件对exa_users表的查询性能影响可以忽略不计。exa_orgnizations表的查询,由于like条件不支持索引,使用全表扫描,在一个较小的数据集中搜索(1000条记录),大致需要50ms,结合内存处理,总时间不超过100ms。 ​	方法2的优点是没有性能问题;缺点是处理代码比较复杂,对于情况1,需要频繁查询exa_orgnizations表。  ​	方法3:分步查询结合缓存赋值。exa_users表的查询,同方法2,也分两种情况,但orgName的赋值,不必每次都查询exa_orgnizations表,而是使用缓存。由于组织Orgnization对象数目相对有限,且被多个实体类对象引用,因此可以使用缓存(单机部署可以直接使用内存,集群部署可使用Redis),关于缓存对象管理,后面将详细讨论。 ​	情况1:查询条件不包含exa_orgnizations表字段的查询条件,则先查询exa_users表,获取userList,然后遍历userList的项,根据orgId,从缓存(一般为orgId到Orgnization对象的字典)中获取Orgnization对象,然后设置orgName值。 ​	情况2:查询条件如果包含exa_orgnizations表的查询条件,如"org_name like '%a%'",则先查询exa_orgnizations表,获取orgList,然后获取不重复的orgIdList,再根据orgIdList条件和exa_users表的其它条件,查询User记录,获取userList。然后逐个给userList的项设置orgName值。 ​	方法3的优点是没有性能问题,相对于方法2,赋值处理得到简化,并且减少了对外键表的查询,处理性能更高;缺点是需要缓存管理,引入了缓存数据一致性的问题和生命周期管理。 ​	缓存对象管理,需要系统层面进行统一设计。一般使用缓存的对象,是记录数相对较少(不到1万条)或增长非常缓慢的,且被多种实体类对象引用的对象。  ​	在实际处理中,这3种方法都会使用。方法1,用于外键较少,且查询条件字段属于主表字段的情况;方法2,用于外键较多,且外表记录数较多,不适合建立缓存的的情况;方法3,用于外键对象使用缓存管理的。 

6.7.4、查询记录数

​	查询记录数,方法名为selectCount,使用select标签。参数为Map<String,Object>类型,返回值为Integer或Long型。 ​	示例代码如下:	 
    <select id="selectCount" resultType="java.lang.Integer">         SELECT              COUNT(1)         FROM             exa_users         WHERE             1 = 1             <if test='userId != null'>                 AND user_id = #{userId,jdbcType=BIGINT}             </if>             <if test='userIdList != null and userIdList.size != 0'>                 AND user_id IN                 <foreach collection ="userIdList" item="userId" index= "index" open="(" close=")" separator =",">                     #{userId}                 </foreach>             </if>                             <if test='userName != null'>                 AND user_name = #{userName,jdbcType=VARCHAR}             </if>             <if test='userNameLike != null'>                 AND user_name LIKE CONCAT('%',#{userNameLike,jdbcType=VARCHAR},'%')             </if>             <if test='userType != null'>                 AND user_type = #{userType,jdbcType=TINYINT}             </if>             <if test='userTypeList != null and userTypeList.size != 0'>                 AND user_type IN                 <foreach collection ="userTypeList" item="userType" index= "index" open="(" close=")" separator =",">                     #{userType}                 </foreach>             </if>                             <if test='sex != null'>                 AND sex = #{sex,jdbcType=TINYINT}             </if>             <if test='deleteFlag != null'>                 AND delete_flag = #{deleteFlag,jdbcType=TINYINT}             </if>             <if test='phoneNumber != null'>                 AND phone_number = #{phoneNumber,jdbcType=VARCHAR}             </if>             <if test='phoneNumberLike != null'>                 AND phone_number LIKE CONCAT('%',#{phoneNumberLike,jdbcType=VARCHAR},'%')             </if>             <if test='realNameLike != null'>                 AND real_name LIKE CONCAT('%',#{realNameLike,jdbcType=VARCHAR},'%')             </if>             <if test='email != null'>                 AND email = #{email,jdbcType=VARCHAR}             </if>             <if test='emailLike != null'>                 AND email LIKE CONCAT('%',#{emailLike,jdbcType=VARCHAR},'%')             </if>             <if test='birthGte != null'>                 AND birth &gt;= #{birthGte,jdbcType=TIMESTAMP}             </if>                          <if test='birthLte != null'>                 AND birth &lt;= #{birthLte,jdbcType=TIMESTAMP}             </if>                          <if test='orgId != null'>                 AND org_id = #{orgId,jdbcType=INTEGER}             </if>             <if test='orgIdList != null and orgIdList.size != 0'>                 AND org_id IN                 <foreach collection ="orgIdList" item="orgId" index= "index" open="(" close=")" separator =",">                     #{orgId}                 </foreach>             </if>                             <if test='openId != null'>                 AND open_id = #{openId,jdbcType=VARCHAR}             </if>             <if test='woaOpenid != null'>                 AND woa_openid = #{woaOpenid,jdbcType=VARCHAR}             </if>     </select>   

6.7.5、根据唯一键或准唯一键查询记录

​	根据唯一键或准唯一键查询记录,方法名为selectItemByXXX,使用select标签。参数为对象的key值,由接口的@Param注解确定,返回值为对象记录。 ​	示例代码如下:	 
    <select id="selectItemByUserName" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             user_name = #{userName,jdbcType=VARCHAR}      </select>      <select id="selectItemByPhoneNumber" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             phone_number = #{phoneNumber,jdbcType=VARCHAR}      </select>      <select id="selectItemByIdNo" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             id_no = #{idNo,jdbcType=VARCHAR}      </select>      <select id="selectItemByOpenId" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             open_id = #{openId,jdbcType=VARCHAR}      </select>      <select id="selectItemByWoaOpenid" resultType="com.abc.example.entity.User">         SELECT              user_id,user_name,password,salt,user_type,org_id,real_name,email,phone_number,sex,             birth,id_no,open_id,woa_openid,remark,operator_name,delete_flag,create_time,update_time         FROM             exa_users         WHERE             woa_openid = #{woaOpenid,jdbcType=VARCHAR}      </select>   
​	对于准唯一键,调用dao接口前,需排除例外情况(如手机号码允许为空串)。如果查询结果集超过一条记录,会抛出异常,因此调用时,需要用try/catch进行保护。	  (未完待续...) 

发表评论

评论已关闭。

相关文章