(续前文)

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

(未完待续...)

Spring Boot实现高质量的CRUD-2的更多相关文章

  1. Spring Boot整合Mybatis并完成CRUD操作

    MyBatis 是一款优秀的持久层框架,被各大互联网公司使用,本文使用Spring Boot整合Mybatis,并完成CRUD操作. 为什么要使用Mybatis?我们需要掌握Mybatis吗? 说的官 ...

  2. Spring boot 实现高吞吐量异步处理(适用于高并发场景)

    技术要点 org.springframework.web.context.request.async.DeferredResult<T> 示例如下: 1.   新建Maven项目  asy ...

  3. 从头开始搭建一个Spring boot+ActiveMQ高可用分布式环境

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  4. Spring Boot整合Mybatis完成级联一对多CRUD操作

    在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能.关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Myb ...

  5. Github点赞超多的Spring Boot学习教程+实战项目推荐!

    Github点赞接近 100k 的Spring Boot学习教程+实战项目推荐!   很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Boot 进行开发,毕竟它这 ...

  6. Spring Boot不同版本整合Redis的配置

    1. Spring Boot为1.4及其他低版本 1.1 POM.XML配置 <!--引入 spring-boot-starter-redis(1.4版本前)--> <depende ...

  7. 【ELK】4.spring boot 2.X集成ES spring-data-ES 进行CRUD操作 完整版+kibana管理ES的index操作

    spring boot 2.X集成ES 进行CRUD操作  完整版 内容包括: ============================================================ ...

  8. 15套java架构师、集群、高可用、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩展. ...

  9. Spring boot(三)整合mybaties+thymeleaf实现基础crud

    工程结构: 首先在pom文件中引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xml ...

  10. Spring Boot使用Spring Data Jpa对MySQL数据库进行CRUD操作

    只需两步!Eclipse+Maven快速构建第一个Spring Boot项目 构建了第一个Spring Boot项目. Spring Boot连接MySQL数据库 连接了MySQL数据库. 本文在之前 ...

随机推荐

  1. 支付回调MQ消息的幂等处理及MD5字符串es中的使用及支付宝预授权完成

    支付回调MQ消息的幂等处理及MD5字符串es中的使用及支付宝预授权完成 1.幂等的处理,根据对象的转json 转md5作为key,退款的处理 控制发送端?业务上比较难控制.支付异步通知,退款有同步通知 ...

  2. 一文总结你需要的OpenCV操作

    目录 一.OpenCV简介 1.1 OpenCV是什么 1.2 安装及使用 二.图像的基础 2.1 成像原理 2.2 图像格式 2.3 颜色空间 三.OpenCV基础操作 3.1 图像的读取.显示.保 ...

  3. Unity3D中的Attribute详解(四)

    本篇我们将逐一讲解Unity中经常使用的Attribute(Unity对应的文档版本为2018.1b). 首先是Serializable,SerializeField以及NonSerialized,H ...

  4. 6步带你用Spring Boot开发出商城高并发秒杀系统

    摘要:本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性. 本文分享自华为云社区<Spring Boot实现商 ...

  5. Python中,类的特殊方法与内置函数的关联

    目录 Python类 Python类的设计原则 特殊方法[Special methods] Duck typing 内置函数 English Version The key design princi ...

  6. [软件测试]Web接口的性能测试

    1 接口响应性能影响因素分析 影响Web接口查询响应性能的重要因素: 1.网络/带宽.服务器硬件资源(CPU.内存.磁盘) 2.用户并发数 3.查询的基础数据集的量级.百万级?亿级?百亿级? 4.查询 ...

  7. day93:flask:Cookie&Session&请求钩子&捕获错误&上下文&Flask-Script

    目录 1.HTTP的会话控制 2.Cookie 3.Session 4.请求钩子 5.捕获错误 6.上下文:context 7.Flask-Script 1.HTTP的会话控制 1.什么是会话控制? ...

  8. 从源码角度分析ScheduleMaster的节点管理流程

    ScheduleMaster是一个开源的分布式任务调度系统,它基于.NET Core 3.1平台构建,支持跨平台多节点部署运行. 项目主页:https://github.com/hey-hoho/Sc ...

  9. 安装anaconda遇到的一些问题

    文章目录 前言 一.报错:jupyter notebook:Bad file descriptor (C:\ci\zeromq_1602704446950\work\src\epoll.cpp:100 ...

  10. iOS中的3种定时器

    在iOS中有3种常见的定时器,它们会根据不同的场景进行选择使用. 1.DispatchSourceTimer: 基于GCD实现. 2.CADisplayLink:基于屏幕刷新实现. 3.Timer:基 ...