Mybatis的基础使用(续)
1.更新数据
int updatePasswordById ( @Param ( "id" ) Long id, @Param ( "password" ) String password) ;
* 提示:在默认情况下,当Java程序源代码(.java文件) 经过编译后,所有局部的变量的名称都会丢失,为使得配置SQL语句是可以根据指定的名称使用方法中的参数,需要在方法的各个参数前添加@Param以指定名称
* 如果方法的参数只有一个则可以匍@Param指定名称,因为Mybatis可以直接找到此参数
在xx.XML文件中配置以上抽象方法映射的SQL语句
< update id= "updatePasswordById" >
update ams_admin set password= #{ password} where id= #{ id}
< / update
* 提示:以上占位符中的名称是通过@Param注解指定的名称,而不是抽象方法的参数名
2.查找数据
int count ( ) ;
< ! -- int count ( ) ; -- >
< select id= "count" resultType= "int" >
select count ( * ) from ams_admin
< / select>
* 注意:所有的select节点必须配置resultType或resultMap这两个属性中的其中一个
Admin getById ( Long id) ;
< ! -- Admin getById ( Long id) ; -- >
< select id= "getById" resultType= "cn.tedu.mybatis.Admin" >
select * from ams_admin where id= #{ id}
< / select>
* 需要注意:如果查询结果集中的列名与类的属性名不匹配时,默认将放弃处理这些结果数据,则返回的对象中对应的属性值为null
* 为了解决此问题,可以再查询时使用自定义的别名,使得名称保持一致,但是更推荐配置<resultMap>以指导Mybatis封装 查询结果
< select id= "getById" resultMap= "BaseResultMap" >
select * from ams_admin where id= #{ id}
< / select>
< ! -- resultMap节点的作用是:指导Mybatis 如何将结果集中的数据封装到返回的对象中 -- >
< ! -- id属性:自定义名称 -- >
< ! -- type属性:将结果集封装到哪种类型的对象中 -- >
< resultMap id= "BaseResultMap" type= "cn.tedu.mybatis.Admin" >
< ! -- 使用若干个result节点配置名称不统一的对应关系 -- >
< ! -- 在单表查询中,名称本来就一致的是不需要配置的 -- >
< ! -- column属性:列名 -- >
< ! -- property属性:属性名 -- >
< result column= "is_enable" property= "isEnable" / >
< result column= "last_login_ip" property= "lastLoginIp" / >
< result column= "login_count" property= "loginCount" / >
< result column= "gmt_last_login" property= "gmtLastLogin" / >
< result column= "gmt_create" property= "gmtCreate" / >
< result column= "gmt_modified" property= "gmtModified" / >
< / resultMap
3.动态SQL
Mybatis中的动态SQL表现为:根据参数不同,生成不同的SQL语句 例如:你可以对某些参数值进行判断,根据判断结果走向不同分支,来决定SQL语句的某个片段,如果参数值是可遍历的,你还可以遍历此参数来生成部分 SQL片段
3.1 动态SQL–foreach
int deleteByIds ( Long . . . ids) ;
int deleteByIds ( Long [ ] ids) ;
int deleteByIds ( List < Long > ids) ;
* 以上三种情况表示相同内容
< ! -- int deleteByIds ( List < Long > ids) ; -- >
< delete id= "deleteByIds" >
delete from ams_admin where id in (
< foreach collection= "list" item= "id" separator= "," >
#{ id}
< / foreach>
)
< / delete>
* 以上代码中:
* <foreach>标签:用于遍历集合或数组类型的参数对象
* collection属性:被遍历的参数对象,当抽象方法的参数只有1个且没有添加 @Param注解时,如果参数是List类型则此属性值为list,如果参数是数组类型(包 括可变参数)则此属性值为array;当抽象方法的参数有多个或添加了@Param注解 时,则此属性值为@Param注解中配置的值
* item属性:自定义的名称,表示遍历过程中每个元素的变量名,可在<foreach>子级 使用#{变量名}表示数据
* separator属性:分隔符号,会自动添加在遍历到的各元素之间
3.2其他动态SQL节点
* <if>
* <choose> / <when> / <otherwise>
* 其它
4.关联查询
4.1 RBAC
RBAC = Role Based Access Control(基于角色的访问控制) RBAC是经典的用户权限管理的设计思路。在这样的设计中,会存在3种类 型:用户、角色、权限,权限将分配到各种角色上,用户可以关联某种角 色,进而实现用户与权限相关。使用这样的设计,更加利于统一管理若干 个用户的权限。 在RBAC的设计思路中,用户与角色一般是多对多的关系,而在数据库中, 仅仅只是使用“用户”和“角色”这2张表是不利于维护多对多关系的, 通常会增加一张中间表,专门记录对应关系,同理,角色和权限也是多对 多的关系,也需要使用中间表来记录对应关系!
4.2 关联查询
在阿里巴巴的《Java开发手册》中指出:
【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明 确写明。 使用<'sql>与<'include>的简单示例
< sql id= "SimpleQueryFields" >
< if test= "true" >
id, username, password
< / if >
< / sql>
< select id= "getById" resultType= "xx.xx.xx.AdminVO" >
select
< include refid= "SimpleQueryFields" / >
from ams_admin
where id= #{ id}
< / select>
如果使用封装了查询的字段列表,与的相性更好,所 以,在开发实践中,通常结合一起使用 另外,在开发实践中,许多不同条件的查询的字段列表是相同的,使用可以很好的实现代码片段的复用 创建"角色"对应的数据类型:
public class Role {
private Long id;
private String name;
private String description;
private Integer sort;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
}
public class AdminDetailsVO {
private Long id;
private String username;
private String password;
private String nickname;
private String avatar;
private String phone;
private String email;
private String description;
private Integer isEnable;
private String lastLoginIp;
private Integer loginCount;
private LocalDateTime gmtLastLogin;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
private List < Role > roles;
}
AdminDetailsVO getDetailsById ( Long id) ;
< ! -- AdminDetailsVO getDetailsById ( Long id) ; -- >
< select id= "getDetailsById" resultMap= "DetailsResultMap" >
select
< include refid= "DetailsQueryFields" / >
from ams_admin
left join ams_admin_role on ams_admin. id = ams_admin_role. admin_id
left join ams_role on ams_role. id = ams_admin_role. role_id
where ams_admin. id= #{ id}
< / select>
< sql id= "DetailsQueryFields" >
< if test= "true" >
ams_admin. id,
ams_admin. username,
ams_admin. password,
ams_admin. nickname,
ams_admin. avatar,
ams_admin. phone,
ams_admin. email,
ams_admin. description,
ams_admin. is_enable,
ams_admin. last_login_ip,
ams_admin. login_count,
ams_admin. gmt_last_login,
ams_admin. gmt_create,
ams_admin. gmt_modified,
ams_role. id AS role_id,
ams_role. name AS role_name,
ams_role. description AS role_description,
ams_role. sort AS role_sort,
ams_role. gmt_create AS role_gmt_create,
ams_role. gmt_modified AS role_gmt_create
< / if >
< / sql>
< ! -- resultMap节点的作用:指导mybatis将查询到的结果集封装到对象中 -- >
< ! -- resultMap节点的id属性:自定义名称 -- >
< ! -- resultMap节点的type属性:封装查询结果的类型的全限定名 -- >
< ! -- 主键应该使用id节点进行配置,非主键、非集合的使用result节点进行配置 -- >
< ! -- column= 结果集中的列名,property= 属性名 -- >
< ! -- 在关联查询中,即便结果集中的列名与类的属性名完全相同,也必须配置 -- >
< ! -- collection子节点:用于配置1 对多关系的数据部分,通常在类中是List 类型的属性 -- >
< ! -- collection子节点的ofType:List 集合中的元素的类型 -- >
< resultMap id= "DetailsResultMap" type= "cn.tedu.mybatis.vo.AdminDetailsVO" >
< id column= "id" property= "id" / >
< result column= "username" property= "username" / >
< result column= "password" property= "password" / >
< result column= "nickname" property= "nickname" / >
< result column= "avatar" property= "avatar" / >
< result column= "phone" property= "phone" / >
< result column= "email" property= "email" / >
< result column= "description" property= "description" / >
< result column= "is_enable" property= "isEnable" / >
< result column= "last_login_ip" property= "lastLoginIp" / >
< result column= "login_count" property= "loginCount" / >
< result column= "gmt_last_login" property= "gmtLastLogin" / >
< result column= "gmt_create" property= "gmtCreate" / >
< result column= "gmt_modified" property= "gmtModified" / >
< collection property= "roles" ofType= "cn.tedu.mybatis.entity.Role" >
< id column= "role_id" property= "id" / >
< result column= "role_name" property= "name" / >
< result column= "role_description" property= "description" / >
< result column= "role_sort" property= "sort" / >
< result column= "role_gmt_create" property= "gmtCreate" / >
< result column= "role_gmt_modified" property= "gmtModified" / >
< / collection>
< / resultMap>
Mybatis处理中此查询时,并不会那么只能的完成结果集的封装,所以必须自行配置<'resultMap>用于指定Mybatis完成封装
< resultMap id= "DetailsResultMap" type= "xx.xx.xx.xx.AdminDetailsVO" >
< ! -- 在1 对多、多对多的查询中,即使名称匹配的结果,也必须显式的配置 -- >
< ! -- 主键字段的结果必须使用id节点进行配置,配置方式与result节点完全相同 -- >
< id column= "id" property= "id" / >
< result column= "gmt_create" property= "gmtCreate" / >
< ! -- 需要使用collection节点配置1 对多中“多”的数据 -- >
< collection property= "roles" ofType= "xx.xx.xx.Role" >
< id column= "role_id" property= "id" / >
< result column= "gmt_create" property= "gmtCreate" / >
< / collection>
< / resultMap>
5.附加
5.1 关于#{}和${}格式的占位符
在Mybatis中,配置SQL语句时,参数可以使用#{}或${}格式的占位符 例如存在需求:分页查询表中的所有数据。 需要执行的SQL语句大致是:
select * from ams_admin order by id limit ? , ?
List < Admin > listPage ( @Param ( "offset" ) Integer offset, @Param ( "size" ) Integer size) ;
< select id= "listPage" resultMap= "BaseResultMap" >
select
< include refid= "BaseQueryFields" / >
from ams_admin
order by id
limit #{ offset} , #{ size}
< / select>
其实,使用#{}格式的占位符时,Mybatis在处理时会使用预编译的做法, 所以,在编写SQL语句时不必关心数据类型的问题(例如字符串值不需要 添加单引号),也不存在SQL注入的风险!这种占位符只能用于表示某个 值,而不能表示SQL语句片段! 当使用${}格式的占位符时,Mybatis在处理时会先将参数值代入到SQL语 句中,然后再执行编译相关过程,所以需要关心某些值的数据类型问题(例如涉及字符串值时,需要在编写SQL语句时添加一对单引号框住字符 串),并且,存在SQL注入的风险!其优点是可以表示SQL语句中的任何 片段! 在一般情况下,应该尽可能的使用#{}格式的占位符,并不推荐使用${}格 式的占位符,即使它可以实现“泛用”的效果! 在一些特殊的情况下,如果一定要使用${}格式的占位符,必须考虑SQL注 入的风险,应该使用正则表达式或其它做法避免出现SQL注入问题!
5.2 Mybatis的缓存机制
缓存:通常是一个临时存储的数据,在未来的某个时间点可能会被删除 通常,存储缓存数据的位置是读写效率较高的,相比其它“非缓存”的数 据有更高的处理效率 由于缓存的数据通常并不是必须的,则需要额外消耗一定的存储空间,同时由于从缓存获取数据的效率更高,所以是一种牺牲空间、换取时间的做法 另外,你必须知道,从数据库读取数据的效率是非常低下的 Mybatis有2种缓存机制,分别称之一级缓存和二级缓存 一级缓存是基于SqlSession的缓存,也称之为“会话缓存”,仅当是同 一个会话、同一个Mapper、同一个抽象方法(同一个SQL语句)、同样 的参数值时有效,一级缓存在集成框架的应用中默认是开启的,且整个过 程不由人为控制(如果是自行得到SqlSession后的操作,可自行清理一 级缓存) 二级缓存默认是关闭的,它是基于namespace的,所以也称之为 “namespace缓存”,需要在配置SQL语句的XML中添加’<'cache/>'节点, 以表示当前XML中的所有查询都允许开通二级缓存,并且,在<'select>节 点上配置useCache=“true”,则对应的<'select>节点的查询结果将被二级 缓存处理,并且,此查询返回的结果的类型必须是实现了Serializable接口的,如果使用了<'resultMap>配置如何封装查询结果,则必须使用 节点来封装主键的映射,满足以上条件后,二级缓存将可用,只要是当前 namespace中查询出来的结果,都会根据所执行的SQL语句及参数进行 结果的缓存 无论是一级缓存还是二级缓存,只要数据发生了写操作(增、删、改), 缓存数据都将被自动清理 由于Mybatis的缓存清理机制过于死板,所以,一般在开发实践中并不怎 么使用!更多的是使用其它的缓存工具并自行制定缓存策略