day64(MYBATIS框架基础2:查询数据,动态sql ,关联查询,小结,关于#{}和${}格式的占位符,Mybatis的缓存机制)
1.查询数据
1.统计
select count(*) from ams_admin
-
int count();
-
在AdminMapper.xml中配置以上抽象方法映射的sql语句
<!-- int count(); -->
<select id="count" resultType="int">
select count(*) from ams_admin
</select>注意:所有select节点必须配置resultType或resultMap这2个属性中的其中1个
-
在MyDetailsTest.java编写并执行测试
@Test
public void testCount() {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
AdminMapper adminMapper = ac.getBean(AdminMapper.class);
int count = adminMapper.count();
System.out.println("当前表中有" + count + "条记录");
ac.close();
}
2.查询某一条数据
1.普通查询
-
select * from ams_admin where id=?
-
Admin getById(Long id);
-
在AdminMapper.xml中配置以上抽象方法映射的sql语句
<!-- Admin getById(Long id); -->
<select id="getById" resultType="cn.tedu.mybatis.Admin">
select * from ams_admin where id=#{id}
</select> -
在MyDetailsTest.java编写并执行测试:
@Test
public void testGetById() {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
AdminMapper adminMapper = ac.getBean(AdminMapper.class);
Long id = 3L;
Admin admin = adminMapper.getById(id);
System.out.println("查询结果:" + admin);
ac.close();
} -
通过测试可以发现:当存在匹配的数据时,将可以查询到数据,当不存在匹配的数据时,将返回null
2.封装查询(resultMap)
-
为了解决此问题,可以在查询时使用自定义的别名,使得名称保持一致,不过,更推荐配置<resultMap>以指导Mybatis封装查询结果
-
resultMap属性
-
例子:
-
ResultMap使用示例
<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.查询列表
-
select * from ams_admin order by id
-
List<Admin> list();
-
在AdminMapper.xml中配置以上抽象方法映射的sql语句
<!-- List<Admin> list(); -->
<select id="list" resultMap="BaseResultMap">
select * from ams_admin order by id
</select>注意:(1) 查询时,结果集中可能超过1条数据时,必须显式的使用ORDER BY子句对结果集进行排序;(2) 查询时,结果集中可能超过1条数据时,应该考虑是否需要分页
-
在MyDetailsTest.java编写并执行测试:
@Test
public void testList() {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
AdminMapper adminMapper = ac.getBean(AdminMapper.class);
List<Admin> list = adminMapper.list();
for (Admin admin : list) {
System.out.println(admin);
}
ac.close();
}
2.动态sql
1.概念
2.实现
-
delete from ams_admin where id in (?,?)
-
int deleteByIds(Long... ids);
-
或
int deleteByIds(Long[] ids);
-
或
int deleteByIds(List<Long> ids);
-
-
在AdminMapper.xml中配置以上抽象方法映射的sql语句:
<!-- int deleteByIds(List<Long> ids); --> <delete id="deleteByIds"> delete from ams_admin where id in ( <foreach collection="list" item="id" separator=" , "> #{id} </foreach> ) </delete>
-
在MyDetailsTest.java编写并执行测试
@Test public void testDeleteByIds() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); AdminMapper adminMapper = ac.getBean(AdminMapper.class); List<Long> ids = new ArrayList<>(); ids.add(16L); ids.add(18L); ids.add(19L); int rows = adminMapper.deleteByIds(ids); System.out.println("受影响的行数为:" + rows); ac.close(); }
3.其它
-
在Mybatis中动态sql还有其它节点,例如:
-
if
-
choose/when/otherwise
-
3.关联查询
1.前期准备
2.RBAC
1.概念
-
RBAC = Role Based Access Control(基于角色的访问控制)
-
RBAC是经典的用户权限管理的设计思路。在这样的设计中,会存在3种类型:用户、角色、权限,权限将分配到各种角色上,用户可以关联某种角色,进而实现用户与权限相关。使用这样的设计,更加利于统一管理若干个用户的权限。
-
在RBAC的设计思路中,用户与角色一般是多对多的关系,而在数据库中,仅仅只是使用“用户”和“角色”这2张表是不利于维护多对多关系的,通常会增加一张中间表,专门记录对应关系,同理,角色和权限也是多对多的关系,也需要使用中间表来记录对应关系!
2.举例: 典型的RBAC设计
1.准备工作(在console中添加)
-
ams_admin:管理员表
-- 管理员表:创建数据表 drop table if exists ams_admin; create table ams_admin ( id bigint unsigned auto_increment, username varchar(50) default null unique comment '用户名', password char(64) default null comment '密码(密文)', nickname varchar(50) default null comment '昵称', avatar varchar(255) default null comment '头像URL', phone varchar(50) default null unique comment '手机号码', email varchar(50) default null unique comment '电子邮箱', description varchar(255) default null comment '描述', is_enable tinyint unsigned default 0 comment '是否启用,1=启用,0=未启用', last_login_ip varchar(50) default null comment '最后登录IP地址(冗余)', login_count int unsigned default 0 comment '累计登录次数(冗余)', gmt_last_login datetime default null comment '最后登录时间(冗余)', gmt_create datetime default null comment '数据创建时间', gmt_modified datetime default null comment '数据最后修改时间', primary key (id) ) comment '管理员' charset utf8mb4;
-
ams_role:角色表
-- 角色表:创建数据表 drop table if exists ams_role; create table ams_role ( id bigint unsigned auto_increment, name varchar(50) default null comment '名称', description varchar(255) default null comment '描述', sort tinyint unsigned default 0 comment '自定义排序序号', gmt_create datetime default null comment '数据创建时间', gmt_modified datetime default null comment '数据最后修改时间', primary key (id) ) comment '角色' charset utf8mb4;
-
ams_admin_role:管理员与角色的关联表
-- 管理员角色关联表:创建数据表 drop table if exists ams_admin_role; create table ams_admin_role ( id bigint unsigned auto_increment, admin_id bigint unsigned default null comment '管理员id', role_id bigint unsigned default null comment '角色id', gmt_create datetime default null comment '数据创建时间', gmt_modified datetime default null comment '数据最后修改时间', primary key (id) ) comment '管理员角色关联' charset utf8mb4;
-
ams_permission:权限表
-- 权限表:创建数据表 drop table if exists ams_permission; create table ams_permission ( id bigint unsigned auto_increment, name varchar(50) default null comment '名称', value varchar(255) default null comment '值', description varchar(255) default null comment '描述', sort tinyint unsigned default 0 comment '自定义排序序号', gmt_create datetime default null comment '数据创建时间', gmt_modified datetime default null comment '数据最后修改时间', primary key (id) ) comment '权限' charset utf8mb4;
-
ams_role_permission:角色与权限的关联表
-- 角色权限关联表:创建数据表 drop table if exists ams_role_permission; create table ams_role_permission ( id bigint unsigned auto_increment, role_id bigint unsigned default null comment '角色id', permission_id bigint unsigned default null comment '权限id', gmt_create datetime default null comment '数据创建时间', gmt_modified datetime default null comment '数据最后修改时间', primary key (id) ) comment '角色权限关联' charset utf8mb4;
-
插入测试数据
truncate ams_admin; truncate ams_admin_role; truncate ams_role; truncate ams_permission; insert into ams_admin (username, password) values ('admin001', '123456'); insert into ams_admin (username, password) values ('admin002', '123456'); insert into ams_admin (username, password) values ('admin003', '123456'); insert into ams_admin (username, password) values ('admin004', '123456'); insert into ams_admin (username, password) values ('admin005', '123456'); insert into ams_permission (name, value, description) values ('商品-商品管理-读取', '/pms/product/read', '读取商品数据,含列表、详情、查询等'), ('商品-商品管理-编辑', '/pms/product/update', '修改商品数据'), ('商品-商品管理-删除', '/pms/product/delete', '删除商品数据'), ('后台管理-管理员-读取', '/ams/admin/read', '读取管理员数据,含列表、详情、查询等'), ('后台管理-管理员-编辑', '/ams/admin/update', '编辑管理员数据'), ('后台管理-管理员-删除', '/ams/admin/delete', '删除管理员数据'); insert into ams_role (name) values ('超级管理员'), ('系统管理员'), ('商品管理员'), ('订单管理员'); insert into ams_admin_role (admin_id, role_id) values (1, 1), (1, 2), (1, 3), (1, 4), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (4, 1);
2.练习
-
当有了新的数据表后,你应该在课后使用这些表继续练习常规数据管理,例如:
-
提示:
3.关联查询
-
sql语句
select * from ams_admin left join ams_admin_role on ams_admin.id=ams_admin_role.admin_idleft join ams_role on ams_admin_role.role_id=ams_role.id where ams_admin.id=?
-
注意:在《Java开发手册》中在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
-
由于不建议使用星号表示字段列表,而关联查询时,由于涉及多张表,则字段列表可能较长,可以使用节点封装字段列表,并在编写sql语句时,使用节点include进行引用
-
提示:在IntelliJ IDEA中,如果在sql节点中直接写字段列表,会提示错误,这是IntelliJ IDEA的误判,可以无视,并不影响运行,或者,使用其它方式避免误判,例如添加if恒等式(见下页示例)
-
注意:使用节点可以封装sql语句的任何部分,而封装字段列表是最常见的做法
-
使用与的简单示例:
<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 xxx { // 用户基本信息的若干个属性,例如用户名、密码等 // 此用户的若干个角色数据,可以使用 List<?> }
-
-
-
先创建“角色”对应的数据类型:
public class Role { private Long id; private String name; private String description; private Integer sort; private LocalDateTime gmtCreate; private LocalDateTime gmtModified; // Setters & GetteRSS // toString() }
-
创建用于封装此次查询结果的类型 :
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; // Setters & GetteRSS // toString() }
-
AdminDetailsVO getDetailsById(Long id);
-
需要注意,由于此次关联了3张表一起查询,结果集中必然出现某些列的名称是完全相同的,所以,在查询时,不可以使用星号表示字段列表(因为这样的结果集中的列名就是字段名,会出现相同的列名),而是应该至少为其中的一部分相同名称的列定义别名
-
举例
select 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_modified from ams_admin left join ams_admin_role on ams_admin.id=ams_admin_role.admin_id left join ams_role on ams_admin_role.role_id=ams_role.id where ams_admin.id=1;
-
在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>
-
完整的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属性:必须的,用于指定此XML文件对应的接口,取值为接口的全限定名 --> <mapper namespace="cn.tedu.mybatis.mapper.AdminMapper"> <!-- 根据要执行的数据操作的类型来选择insert/delete/update/select节点 --> <!-- 节点的id是抽象方法的名称(仅名称) --> <!-- 节点的内部编写sql语句 --> <!-- sql语句中的参数值使用#{}格式的占位符 --> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> insert into ams_admin ( username, password, nickname, avatar, phone, email, description, is_enable, last_login_ip, login_count, gmt_last_login, gmt_create, gmt_modified ) values ( #{username}, #{password}, #{nickname}, #{avatar}, #{phone}, #{email}, #{description}, #{isEnable}, #{lastLoginIp}, #{loginCount}, #{gmtLastLogin}, #{gmtCreate}, #{gmtModified} ) </insert> <insert id="insertBatch"> insert into ams_admin (username) values <foreach collection="list" item="username" separator=","> (#{username}) </foreach> </insert> <delete id="deleteById"> delete from ams_admin where id=#{id} </delete> <!-- foreach节点是用于对参数值进行遍历的 --> <!-- collection属性:被遍历对象 --> <!-- 如果抽象方法的参数只有1个,当参数是数组时,collection="array",当参数是List时,collection="list" --> <!-- 如果抽象方法的参数只有多个,则collection="参数名",例如通过@Param注解配置的名称 --> <!-- item属性:自定义名称,是被遍历的对象的名称 --> <!-- separator属性:遍历过程中各值之间的分隔符号 --> <delete id="deleteByIds"> delete from ams_admin where id in ( <foreach collection="array" item="id" separator=","> #{id} </foreach> ) </delete> <update id="updatePasswordById"> update ams_admin set password=#{password} where id=#{id} </update> <!-- 查询所使用的必须是select节点 --> <!-- select节点必须配置resultType或resultMap中的其中1个 --> <!-- resultType的值就是抽象方法的返回值类型的全限定名 --> <select id="count" resultType="int"> select count(*) from ams_admin </select> <select id="getById" resultMap="BaseResultMap"> select <include refid="xxx"/> from ams_admin where id=#{id} </select> <select id="list" resultMap="BaseResultMap"> select <include refid="xxx"/> from ams_admin order by id </select> <sql id="xxx"> <if test="true"> id, username, password </if> </sql> <!-- 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> <resultMap id="BaseResultMap" type="cn.tedu.mybatis.entity.Admin"> <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" /> </resultMap> </mapper>
-
-
4.小结
1.了解
-
了解如何创建一个整合了Spring框架的Mybatis工程
-
了解整合了Spring框架的Mybatis工程的配置
-
了解使用注解配置sql语句
-
理解resultType与resultMap使用原则
-
尽可能的全部使用resultMap,如果查询结果是单一某个数据类型(例如基本数据类型或字符串或某个时间等),则使用resultType
-
2.掌握
-
掌握声明抽象方法的原则:
-
掌握使用XML配置sql语句
-
掌握的配置方式
-
掌握动态sql中的``的使用
5.课后阅读
1. 关于#{}和${}格式的占位符
-
在Mybatis中,配置sql语句时,参数可以使用#{}或${}格式的占位符
-
-
需要执行的sql语句大致是:
select * from ams_admin order by id limit ?, ?
List<Admin> listPage(@Param("offset") Integer offset, @Param("size") Integer size);
-
配置sql语句:
<select id="listPage" resultMap="BaseResultMap"> select <include refid="BaseQueryFields" /> from ams_admin order by id limit #{offset}, #{size} </select>
-
执行测试:
@Test public void testListPage() { Integer offset = 0; Integer size = 3; List<Admin> adminList = adminMapper.listPage(offset, size);System.out.println("查询到的记录数:" + adminList.size()); for (Admin admin : adminList) { System.out.println(admin); } }
-
-
-
在“根据用户名查询用户详情”时,如果将username=#{username}换成username=${username}会出现错误!
-
其实,使用#{}格式的占位符时,Mybatis在处理时会使用预编译的做法,所以,在编写sql语句时不必关心数据类型的问题(例如字符串值不需要添加单引号),也不存在sql注入的风险!这种占位符只能用于表示某个值,而不能表示sql语句片段!
-
当使用${}格式的占位符时,Mybatis在处理时会先将参数值代入到sql语句中,然后再执行编译相关过程,所以需要关心某些值的数据类型问题(例如涉及字符串值时,需要在编写sql语句时添加一对单引号框住字符串),并且,存在sql注入的风险!其优点是可以表示sql语句中的任何片段!
-
在一般情况下,应该尽可能的使用#{}格式的占位符,并不推荐使用${}格式的占位符,即使它可以实现“泛用”的效果!
-
在一些特殊的情况下,如果一定要使用${}格式的占位符,必须考虑sql注入的风险,应该使用正则表达式或其它做法避免出现sql注入问题!
-
2. Mybatis的缓存机制
1.缓存
-
通常,存储缓存数据的位置是读写效率较高的,相比其它“非缓存”的数据有更高的处理效率
-
由于缓存的数据通常并不是必须的,则需要额外消耗一定的存储空间,同时由于从缓存获取数据的效率更高,所以是一种牺牲空间、换取时间的做法
-
另外,你必须知道,从数据库读取数据的效率是非常低下的
2.两种缓存机制
-
一级缓存是基于sqlSession的缓存,也称之为“会话缓存”,仅当是同一个会话、同一个Mapper、同一个抽象方法(同一个sql语句)、同样的参数值时有效,一级缓存在集成框架的应用中默认是开启的,且整个过程不由人为控制(如果是自行得到sqlSession后的操作,可自行清理一级缓存)
-
二级缓存默认是全局开启的,它是基于namespace的,所以也称之为“ namespace缓存” ,需要在配置sql语句的XML中添加节点,以表示当前XML中的所有查询都允许开通二级缓存,并且,在节点上配置useCache= "true" ,则对应的节点的查询结果将被二级缓存处理,并且,此查询返回的结果的类型必须是实现了Serializable接口的,如果使用了配置如何封装查询结果,则必须使用节点来封装主键的映射,满足以上条件后,二级缓存将可用,只要是当前namespace中查询出来的结果,都会根据所执行的sql语句及参数进行结果的缓存
-
无论是一级缓存还是二级缓存,只要数据发生了写操作(增、删、改),缓存数据都将被自动清理
-
由于Mybatis的缓存清理机制过于死板,所以,一般在开发实践中并不怎么使用!更多的是使用其它的缓存工具并自行制定缓存策略
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。