Spring Boot实现高质量的CRUD-2

这篇具有很好参考价值的文章主要介绍了Spring Boot实现高质量的CRUD-2。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

(续前文)文章来源地址https://www.toymoban.com/news/detail-479767.html

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
	 * ------------------------------------------------------------------------------
	 * 2022/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进行保护。	

(未完待续...)

到了这里,关于Spring Boot实现高质量的CRUD-2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • AI Code Translator —— 能够实现高质量的自动编程语言转换工具

    https://github.com/mckaywrigley/ai-code-translator AI Code Translator —— 是一款基于大型语言模型的代码翻译工具,同时也是一款颠覆性的编程语言翻译工具,它基于先进的机器学习技术和大规模语料库训练而成,能够实现高质量的自动编程语言转换。 这款工具最大的突破在于翻译速度快且

    2024年02月04日
    浏览(49)
  • 高质量椭圆检测库

    目录 前言 效果展示 检测库 简介 安装库 用法 测试 论文算法步骤简读 1. lsd 检测 2. lsd group 3. 生成初始 ellipse 4. 聚类 椭圆检测是工业中比较常用的一种检测需求。目前常用的基于传统图像处理的椭圆检测方法是霍夫变换,但是霍变换的检测率比较低,很难满足工业场景。而基

    2024年02月07日
    浏览(151)
  • 如何写出高质量代码?

    作为一名资深开发人员,写出高质量的代码是我们必须要追求的目标。然而,在实际开发中,我们常常会遇到各种问题。比如,代码的可读性、可维护性、健壮性和灵活性等,这些都会影响代码的质量。那么,究竟如何才能写出高质量的代码呢? 代码结构清晰易懂,能够使代

    2024年02月02日
    浏览(58)
  • 如何编写高质量代码

    现代软件开发中,代码是构建高质量软件的核心。高质量代码能够提高软件系统的可靠性、可维护性和可扩展性,减少bug的数量和修复时间,提升开发效率和代码可读性,同时有助于团队协作和知识传承共享。 然而,梦想是丰满的,现实是骨感的!软件开发面临诸多挑战。

    2024年02月02日
    浏览(128)
  • 如何写出高质量代码

    一、 前言 编写高质量代码是每一位程序员的追求。高质量的代码可以提高代码可读性、可维护性、可扩展性以及软件运行的性能和稳定性。在这篇文章中,我将分享一些编写高质量代码的特征、编程实践技巧和软件工程方法论。 可读性:好的代码应该能够被维护者轻易地理

    2024年02月02日
    浏览(88)
  • 如何写出高质量的代码

    你是否曾经为自己写的代码而感到懊恼?你是否想过如何才能写出高质量代码?那就不要错过这个话题!在这里,我们可以讨论什么是高质量代码,如何写出高质量代码等问题。无论你是初学者还是资深开发人员,都可以在这个话题下进行分享,汲取灵感和知识,共同提高自

    2023年04月25日
    浏览(129)
  • 网络安全高质量文库

    PeiQI文库 http://api.orchidstudio.cn/ PeiQi文库是一个面对网络安全从业者的知识库,涉及漏洞研究,代码审计,CTF夺旗,红蓝对抗等多个安全方向,用于解决安全信息不聚合,安全资料不易找的难题。帮助网络安全从业者共同构建安全的互联网,快速验证并及时修复相关漏洞,为甲

    2024年02月12日
    浏览(54)
  • Visio 转为高质量PDF

    Visio另存为pdf不够清晰怎么办 - - 可以选择先另存为高分辨率的图片( 存的时候分辨率选择打印机或者自定义即可 ),然后转为pdf. 或者用 打印 1 保存为高质量 2 的pdf (本文介绍) 版本:Microsoft Visio 2010 Adobe Acrobat 2018 关键就是设置分辨率,不按照以上流程亦可 一般情况下安装完

    2024年02月04日
    浏览(52)
  • 有哪些高质量的自学网站?

    分享32个鲜为人知并且完全免费的高质量自学网站,每个都是堪称神器,让你相见恨晚。 是一个完全免费的综合视频教程网站,非常良心实用。 它提供的视频教程非常丰富并且质量很高,包括:PS 教程、手机摄影教程、Ai 做图教程、Excel 教程、Word 教程、PPT 教程、Pr 视频剪辑

    2024年02月02日
    浏览(129)
  • 如何编写高质量的测试计划

    1.1目的 简述本计划的目的,旨在说明各种测试阶段任务、人员分配和时间安排、工作规范等。 测试计划在策略和方法的高度说明如何计划、组织和管理测试项目。测试计划包含足够的信息使测试人员明白项目需要做什么是如何运作的。另外,清晰的文档结构能使任何一个读

    2024年02月16日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包