查询当前表有多少条数据,SQL语句为
select count(*) from ams_admin
在AdminMapper接口中添加抽象方法
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(); }
目标:根据id查询管理员信息 ,SQL语句
select * from ams_admin where id=?
在AdminMapper接口中添加抽象方法
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
需要注意,如果查询结果集中的列名与类的属性名不匹配时,默认将放弃处理这些结果数据,则返回的对象中对应的属性值为null
为了解决此问题,可以在查询时使用自定义的别名,使得名称保持一致,不过,更推荐配置<resultMap>以指导Mybatis封装查询结果
resultMap属性
resultMap节点的作用是:指导Mybatis如何将结果集中的数据封装到返回的对象中
id属性:自定义名称
type属性:将结果集封装到哪种类型的对象中
column属性:列名
property属性:属性名
例子:
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>
查询所有管理员信息SQL
select * from ams_admin order by id
在AdminMapper接口中添加抽象方法
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(); }
根据参数不同,生成不同的SQL语句
举例
你可以对某些参数值进行判断,根据判断结果走向不同分支,来决定SQL语句的某个片段,如果参数值是可遍历的,你还可以遍历此参数来生成部分SQL片段
目标: 根据若干个id一次性删除若干条管理数据 SQL
delete from ams_admin where id in (?,?)
注意:以上SQL语句中,id值的数量(以上?的数量)对于开发人员而言是未知的,通常是软件的使用者决定的
在AdminMapper接口中添加抽象方法:
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>
以上代码中:
foreach标签:用于遍历集合或数组类型的参数对象
collection属性:被遍历的参数对象,当抽象方法的参数只有1个且没有添加@Param注解时,如果参数是List类型则此属性值为list,如果参数是数组类型(包括可变参数)则此属性值为array;当抽象方法的参数有多个或添加了@Param注解时,则此属性值为@Param注解中配置的值
item属性:自定义的名称,表示遍历过程中每个元素的变量名,可在foreach子级使用#{变量名}表示数据
separator属性:分隔符号,会自动添加在遍历到的各元素之间
在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(); }
在Mybatis中动态SQL还有其它节点,例如:
if
choose/when/otherwise
准备一些数据表,及必要的测试数据,例如:存在若干条用户数据,存在若干条角色数据,某个用户存在与角色的关联,最好有些用户有多个关联,又有些用户只有1个关联,还有些用户没有关联
RBAC = Role Based Access Control(基于角色的访问控制)
RBAC是经典的用户权限管理的设计思路。在这样的设计中,会存在3种类型:用户、角色、权限,权限将分配到各种角色上,用户可以关联某种角色,进而实现用户与权限相关。使用这样的设计,更加利于统一管理若干个用户的权限。
在RBAC的设计思路中,用户与角色一般是多对多的关系,而在数据库中,仅仅只是使用“用户”和“角色”这2张表是不利于维护多对多关系的,通常会增加一张中间表,专门记录对应关系,同理,角色和权限也是多对多的关系,也需要使用中间表来记录对应关系!
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);
当有了新的数据表后,你应该在课后使用这些表继续练习常规数据管理,例如:
向权限表中插入新的数据
根据id删除某个权限
查询权限表中的所有权限
提示:
需要新的数据类型,例如Permission类
需要新的接口文件,用于定义以上2个数据访问功能的抽象方法
需要新的XML文件,用于配置抽象方法对应的SQL语句
需要修改配置信息,将此前指定的XML文件由AdminMapper.xml改为*.xml,并把SpringConfig类中sqlSessionFactoryBean()方法的第2个参数由Resource类型改为Resource...类型
当需要测试时,使用新的测试类
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>
如果使用封装了查询的字段列表,与的相性更好,所以,在开发实践中,通常结合一起使用
另外,在开发实践中,许多不同条件的查询的字段列表是相同的,使用可以很好的实现代码片段的复用
通过测试运行,可以发现(必须基于以上测试数据):
当使用的id值为1时,共查询到4条记录,并且用户的基本信息是相同的,只是与角色关联的数据不同
当使用的id值为2时,共查询到3条记录
当使用的id值为3时,共查询到2条记录
当使用其它有效用户的id时,共查询到1条记录
此类查询期望的结果应该是:
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() }
在AdminMapper接口中添加抽象方法:
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>
了解如何创建一个整合了Spring框架的Mybatis工程
了解整合了Spring框架的Mybatis工程的配置
了解使用注解配置SQL语句
掌握使用封装SQL语句片段,并使用进行引用,以实现SQL语句的复用
理解resultType与resultMap使用原则
尽可能的全部使用resultMap,如果查询结果是单一某个数据类型(例如基本数据类型或字符串或某个时间等),则使用resultType
掌握声明抽象方法的原则:
返回值类型:增删改类型的操作均返回int,表示“受影响的行数”,查询类型操作,根据操作得到的结果集来决定,只要能够放入所有所需的数据即可,通常,统计查询返回int,查询最多1个结果时返回自定义的某个数据类型,查询多个结果时返回List集合类型
方法名称:自定义,不要使用重载,其它命名建议参考此前的笔记
参数列表:根据需要执行的SQL语句中的参数来设计,并且,当需要执行的是插入数据操作时,必须将这些参数进行封装,并在封装的类中添加主键属性,以便于Mybatis能获取自动编号的值回填到此主键属性中,当需要执行的是其它类型的操作时,如果参数数量较多,可以封装,如果只有1个,则直接声明为方法参数,如果超过1个且数量不多,则每个参数之前添加@Param注解
掌握使用XML配置SQL语句
这类XML文件需要顶部特殊的声明,所以,通常是从网上下载或通过复制粘贴得到此类文件
根节点必须是,且必须配置namespace,取值为对应的Java接口的全限定名
应该根据要执行的SQL语句不同来选择、、、节点,这些节点都必须配置id属性,取值为对应的抽象方法的名称
其实,节点和节点可以随意替换使用,但不推荐
在不考虑“获取自动生成的主键值”的情况下,和、也可以混为一谈,但不推荐
当插入数据时,当需要获取自动生成的主键值时,需要在节点上配置useGeneratedKeys和keyProperty属性
在节点上,必须配置resultMap或resultType属性中的其中1个
掌握的配置方式
主键列与属性的映射必须使用节点配置
在1对多、多对多的查询中,集合类型的属性的映射必须使用子节点配置
其它列与属性的映射使用节点配置
在单表查询中,列与属性名一致时,可以不必显式的配置,但是,在关联查询中,即使列与属性名称一致,也必须显式的配置出来
掌握动态SQL中的``的使用
在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); } }
以上代码可以正常通过测试,并且观察结果也都是符合预期的,即使把SQL语句中的#{}换成${}格式,也是完全没有问题的!
例如还存在需求:根据用户名查询此用户的详情
在“根据用户名查询用户详情”时,如果将username=#{username}换成username=${username}会出现错误!
其实,使用#{}格式的占位符时,Mybatis在处理时会使用预编译的做法,所以,在编写SQL语句时不必关心数据类型的问题(例如字符串值不需要添加单引号),也不存在SQL注入的风险!这种占位符只能用于表示某个值,而不能表示SQL语句片段!
当使用${}格式的占位符时,Mybatis在处理时会先将参数值代入到SQL语句中,然后再执行编译相关过程,所以需要关心某些值的数据类型问题(例如涉及字符串值时,需要在编写SQL语句时添加一对单引号框住字符串),并且,存在SQL注入的风险!其优点是可以表示SQL语句中的任何片段!
在一般情况下,应该尽可能的使用#{}格式的占位符,并不推荐使用${}格式的占位符,即使它可以实现“泛用”的效果!
在一些特殊的情况下,如果一定要使用${}格式的占位符,必须考虑SQL注入的风险,应该使用正则表达式或其它做法避免出现SQL注入问题!
通常是一个临时存储的数据,在未来的某个时间点可能会被删除
通常,存储缓存数据的位置是读写效率较高的,相比其它“非缓存”的数据有更高的处理效率
由于缓存的数据通常并不是必须的,则需要额外消耗一定的存储空间,同时由于从缓存获取数据的效率更高,所以是一种牺牲空间、换取时间的做法
另外,你必须知道,从数据库读取数据的效率是非常低下的
一级缓存是基于SqlSession的缓存,也称之为“会话缓存”,仅当是同一个会话、同一个Mapper、同一个抽象方法(同一个SQL语句)、同样的参数值时有效,一级缓存在集成框架的应用中默认是开启的,且整个过程不由人为控制(如果是自行得到SqlSession后的操作,可自行清理一级缓存)
二级缓存默认是全局开启的,它是基于namespace的,所以也称之为“ namespace缓存” ,需要在配置SQL语句的XML中添加节点,以表示当前XML中的所有查询都允许开通二级缓存,并且,在节点上配置useCache= "true" ,则对应的节点的查询结果将被二级缓存处理,并且,此查询返回的结果的类型必须是实现了Serializable接口的,如果使用了配置如何封装查询结果,则必须使用节点来封装主键的映射,满足以上条件后,二级缓存将可用,只要是当前namespace中查询出来的结果,都会根据所执行的SQL语句及参数进行结果的缓存
无论是一级缓存还是二级缓存,只要数据发生了写操作(增、删、改),缓存数据都将被自动清理
由于Mybatis的缓存清理机制过于死板,所以,一般在开发实践中并不怎么使用!更多的是使用其它的缓存工具并自行制定缓存策略