1、Mybatis中的缓存机制
(1)一级缓存
Mybatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户。不需要再进行一次数据库查询了。
MyBatis会在表示一次会话的一个SqlSession对象中创建一个本地缓存。对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。否则,从数据库中读取数据,将查询结果存入缓存,并返回给用户。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后,该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
(2)二级缓存
二级缓存是mapper级别的缓存,多个sqlSession去操作同一个Mapper的sql语句,多个sqlSession去操作数据库得到数据会存在二级缓存区域,多个sqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个sqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
2、#{ }和${ }的区别是什么?
解释一:
#{}是预编译处理,${}是字符串替换。mybatis在处理#{}时,会将sql中的 #{}号替换为?号,调用PreparedStatement的set方法来赋值,最后注入进去是带单引号的;mybatis在处理${}时,就是把${}替换为变量的值。使用#{}可以有效的防止SQL注入,提高系统安全性。
例如:
#是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.
$是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1。
解释二:
mybatis在对sql语句进行预编译之前,会对sql进行动态解析,也就是在此处对动态SQL进行处理的。在动态SQL解析阶段,#{}和${}会有不同的表现。
(1)我们使用#的时候:
select * from user where name=#{name};
#{}在动态解析的时候,会解析成一个问号(?)(参数占位符)。就是解析之后的语句是:
select * from user where name=?
如果我们传入的的参数为“Jack”,那么占位符进行变量替换后,sql语句变为:
select * from user where name='Jack'
(2)那么我们使用${}的时候:
select * from user where name=${name};
${}在动态解析的时候,会将我们传入的参数当做String字符串填充到我们的语句之中,当我们传递的参数为 "Jack" 时,上述 sql 的解析为:就会变成下面的语句:
select * from user where name=“Jack”
预编译之前的SQL语句已经不包含变量了,完全已经是常量数据了。相当于我们普通没有变量的sql了。
综上所得,${}变量的替换阶段是在动态SQL解析阶段,而#{}变量的替换是在DBMS中。
这里#{}和${}我们能看到的主要的区别,除此之外,还有以下区别:
#方式能够很大程度防止sql注入。
$方式无法防止sql注入。
$方式一般用于传入数据库对象,例如传入表名。
一般能用#的就别用$。
注:sql预编译
(1)定义
sql预编译指的是数据库驱动在发送sql语句和参数给DBMS(数据库管理系统)之前对sql语句进行编译,这样DBMS执行sql时,就不需要重新编译。
(2)为什么需要预编译?
JDBC中使用对象PreparedStatement来抽象预编译语句,使用预编译。预编译阶段可以优化sql的执行。预编译之后的sql多数情况下可以直接执行,DBMS不需要再次编译,越复杂的sql,编译的复杂度越大,预编译阶段可以合并多次操作为一个操作。预编译语句对象可以重复使用。把一个sql预编译后产生的PreparedStatement对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的PreparedStatement对象。默认情况下,将对所有的sql进行预编译。
3、最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口,就是人们常说的Mapper接口,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。