上篇文章讲述了Spring如何配置Mybatis环境。
SpringBoot配置Mybatis:详细易懂
本篇文章主要介绍Mybatis的一些高频面试知识点。持续更新。希望点个收藏
网上标准答案:#{}是预编译处理,${ } 是字符串替换。
Mybatis在处理#{}时,会将sql语句中的#{}替换成?号。调用PreparedStatement的set方法来赋值Mybatis在处理${}时,就是把${}替换成变量的值。使用#{}可以有效防止SQL注入,提高系统安全性
答案肯定是对的,但是大部分人简单看完,对防止SQL注入的原理还是一知半解。下面举个实例加深理解
-- 针对此条Sql语句. 网站登入验证SQL语句
-- name = "1' OR '1'='1"
-- passWord = "1' OR '1'='1"
select * from user where name = #{name} and passwrod = #{password};
-- #{} 分两步
-- 第一步 替换成一下sql,进行预编译
select * from user where name = ? and password = ?;
PreparedStatement pstatement = conn.prepareStatement(sql);
-- 第二步 PreparedStatement的set方法替换,执行sql
pstmt.setString(1, name);
pstmt.setString(2, password);
ResultSet rs=pstmt.executeUpdate();
-- ${} 直接替换
select * from user where name = ${name} and password = ${password};
-- 替换后
select * from user where name ='1' or '1' = '1' and password = '1' or '1' = '1';
-- 等同于
select * from user;
-- 在后台帐号验证的时候巧妙地绕过了检验,达到无账号密码,亦可登录网站。所以SQL注入攻击被俗称为黑客的填空游戏。
官方答案:Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
具体原理:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原
理。
看完,大多数人只有一个概念,延迟加载是什么估计都没有明白
我先用sql知识解释一下延迟加载,然后,在mybatis环境下实验
关联查询:是一个多表连接查询,设计到join操作,不如单表查询快速
-- Student表属性列:Sno, Sname, Sage;
-- SC表属性列: Sno, Cno;
-- 查询选了课的学生名字,关联查询
select Sno, Cno, Sname from sc, student where sc.Sno = student.Sno;
-- 其实可以拆分成两个单表查询
select Sno, Cno from SC;
select Sname from student where Sno = #{第一个查询结果Sno}
延迟加载原理就是将关联查询分解成两个单表查询,首先执行第一个单表查询,当有需要时,再进行第二个单表查询。
前期准备:
在MySQL数据库中创建两个表,一个学生表,一个选课表,并插入数据
创建两个bean类,对应表的属性列
package com.test.demo;
public class Student {
public int sNo;
public String sName;
public int sAge;
public Student(int sNo, String sName, int sAge) {
this.sNo = sNo;
this.sName = sName;
this.sAge = sAge;
}
//getter、setter方法此处省略
}
package com.test.demo;
public class SC {
Student student;
int cNo;
//getter、setter方法省略
}
创建接口和xml文件:
package com.test.demo.mapper;
import java.util.List;
import com.test.demo.Student;
public interface StudentMapper {
void insert(Student student);
void delete(String sNo);
Student selectByNumber(Integer sNo);
void updateName(Student student);
List<Student> findByCondition(String sName, Integer sAge);
List<Student> selectByNumberList(List<Integer> list);
}
package com.test.demo.mapper;
import com.test.demo.SC;
import java.util.List;
public interface SCMapper {
List<SC> selectByNumber(Integer cNo);
}
XML文件重点
studentMapper文件
<select id="selectByNumber" parameterType="java.lang.Integer" resultType="com.test.demo.Student">
SELECT * FROM student where Sno = #{sNo}
select>
mapper>
scMapper文件
<select id="selectByNumber" parameterType="java.lang.Integer" resultMap="lazyMap">
SELECT * FROM sc where Cno = #{cNo}
select>
<resultMap id="lazyMap" type="com.test.demo.SC">
<association property="student" javaType="com.test.demo.Student" select="com.test.demo.mapper.StudentMapper.selectByNumber" column="Sno">
association>
resultMap>
mapper>
main方法
List<SC> scList = scMapper.selectByNumber(4);
for(SC sc : scList) {
//延迟加载
Student student = sc.getStudent();
System.out.println(student.getSName());
}
一级缓存
默认开启一级缓存,一级缓存的作用范围是SqlSession
。如果执行的sql语句,SqlSession以前执行过,直接会复用缓存的结果,而不会去数据库查询。调用SqlSession的close
、clearCache
方法或者执行SQL语句是update、delete、insert
,都会更新一级缓存
二级缓存
二级缓存向胆固全局缓存,默认不开启,可以再配置文件中开启,并且要求POJO实现Serializable接口,即必须可序列化。
#{}与${}的区别
延迟加载(mybatis笔记)
mybatis的一级和二级缓存详细介