1. 什么是Mybatis
MyBatis是一个半自动化的ORM框架 (Object Relationship Mapping-->对象关系映射),避免了几乎所有的JDBC代码和手动配置参数以及获取结果集的过程,比如 : 数据取出时的封装 , 数据库的建立连接等等,通过MyBatis框架可以减少重复代码,提高了开发效率。
MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。
其实所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别
MyBatis的优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供xml标签,支持编写动态sql。
持久化 由于内存本身存在的缺陷,内存断电后数据会丢失,但是一些重要的数据无论如何都不能丢失的,比如:银行卡密码等,但是我们也无法保证永不断电。 数据持久化就能很好的解决该问题,持久化即把数据保存到可永久保存的存储设备中。持久化主要的应用是将内存中的数据保存到数据库中,或者存储在磁盘文件、XML数据文件中等等。JDBC也是一种持久化机制。
2. HelloWorld
步骤:搭建环境-->导入Mybatis--->编写代码--->测试
1、创建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'王五','123456'),(2,'张三','abcdef'),(3,'李四','987654');
2、导入相关jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
3、核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 通过properties标签,读取java配置文件的内容实现外部配置且可动态替换 -->
<!--<properties resource="db.properties" />-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/dao/userMapper.xml"/>
</mappers>
</configuration>
4、Mybatis工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
5、创建实体类
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
//构造,有参,无参
//set/get
//toString()
}
6、编写Mapper接口类
public interface UserMapper {
List<User> selectUser();
}
7、编写Mapper.xml配置文件
- namespace对应着Mapper接口或者Dao接口的完整包名,必须一致!
- id对应方法名
<?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="com.dao.UserMapper">
<select id="selectUser" resultType="com.pojo.User">
select * from user
</select>
</mapper>
8、测试类
public class MyTest {
@Test
public void selectUser() {
SqlSession session = MybatisUtils.getSession();
//方法一:
//List<User> users = session.selectList("com.mapper.UserMapper.selectUser");
//方法二:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user: users){
System.out.println(user);
}
session.close();
}
}
3. 配置文件解析
核心配置
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
</mappers>
</configuration>
environments
- 通过default可以配置多套运行环境 mappers
- 定义映射SQL语句文件
- mappers元素引入方式
<!-- 1. 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 2. 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!--
3. 使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!--
4. 将包内的映射器接口实现全部注册为映射器
但是需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
- typeAliases 类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
指定类
<!--配置别名,注意顺序-->
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>
指定包名
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
Mapper文件
<?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="com.dao.UserMapper">
<select id="selectUser" resultType="com.pojo.User">
select * from user
</select>
</mapper>
- namespace:必须与某接口同名,接口中的方法与映射文件中的select标签中的id对应。
4. CRUD操作
步骤:
- 创建Mapper接口方法
- 在Mapper.xml中编写sql
- 调用方法
select
如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可
直接在方法中传参:
//通过密码和名字查询用户
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);
/*
<select id="selectUserByNP" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
</select>
*/
使用Map作参数
User selectUserByNP2(Map<String,Object> map);
<select id="selectUserByNP2" parameterType="map" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
</select>
Map<String, Object> map = new HashMap<String, Object>();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);
insert
int addUser(User user);
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
public void testAddUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(5,"王五","zxcvbn");
int i = mapper.addUser(user);
System.out.println(i);
session.commit(); //提交事务,重点!不写的话不会提交到数据库
session.close();
}
注意点:增、删、改操作需要提交事务!
update
与上同
delete
与上同
总结
- 所有的增删改操作都需要提交事务
- 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!
- 有时候根据业务的需求,可以考虑使用map传递参数!
- 为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!
5. 生命周期和作用域
5.1 生命周期
-
首先通过SqlSessionFactoryBuilder的builde()方法去加载xml文件或者配置文件,会生成一个SqlSessionFactory工厂对象。
-
得到工厂对象后,就可以去工厂里面去拿我们需要的SqlSession对象了,调用openSession()方法即可。
-
拿到SqlSession对象后,可以调用getMapper(Class mapperClass)方法,可以得到一个映射器接口的实例对象。SqlSession可以理解为一个请求,向连接池连接的请求,使用完毕之后立马关闭它,否则会造成资源浪费。
-
通过映射器实例对象去调用相应方法。
-
最后关闭SqlSession即可。
5.2 作用域
-
SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
-
SqlSessionFactory可以被认为是一个数据库连接池,所以它占据着数据库的连接资源,它的作用是创建SqlSession接口对象。因为Mybatis的本质就是Java对数据库的操作,所以SqlSessionFactory的生命周期存在整个Mybatis应用中,一旦创建SqlSessionFactory,就要长期保存它,直至不再使用Mybatis应用,所以可以认为SqlSessionFactory的生命周期等同于Mybatis的应用周期。因此一般的应用中,我们往往希望SqlSessionFactory作为一个单例,让它在应用中的被共享,最佳作用域是应用作用域。
-
SqlSessionFactory是一个数据库连接池,那么SqlSession就是一个数据库连接(Connection对象),可以在一个事务中执行多条SQL,然后通过它的commit、rollback等方法,提交或回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后应该关闭连接,让它归还给SqlSessionFactory,否则数据库资源就很快会耗光,系统就会瘫痪,所以应该用try...catch...finally...语句来保证其正确关闭。所以SqlSession最佳的作用域是请求或方法作用域。
6. ResultMap及分页
数据库中的字段名和实体类的属性名不同,就会出现查询结果为null的问题。因为mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到对应的setXXX()方法 , 所以返回null ;
解决方案:
- as指定别名
<select id="selectUserById" resultType="User">
select id , name , pwd as password from user where id = #{id}
</select>
- 使用结果集映射->
<ResultMap>【推荐】
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
自动映射:
<select id="selectUserById" resultType="map">
select id , name , pwd
from user
where id = #{id}
</select>
以上是自动映射的例子,但是并没有显式指定resultMap,只是简单的将所有的列映射到HashMap的键上,这由resultType属性指定虽然大部分时候都够用,但是你的程序更有可能会使用JavaBean或者POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。
ResultMap
resultMap元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBCResultSets数据提取代码中解放出来。- 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份
resultMap能够代替实现同等功能的长达数千行的代码。 - ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
7. 分页的几种方式
增上改查操作中,使用最多的是查操作。如果查询的数据量大的时候就需要进行分页查询,也就是每次处理小部分的数据,这样的对数据库压力就在可控范围内。
7.1 Limit分页
#语法
SELECT * FROM table LIMIT stratIndex,pageSize
SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
#如果只给定一个参数,它表示返回最大的记录行数目:
SELECT * FROM table LIMIT 5; //检索前 5 个记录行
#换句话说,LIMIT n 等价于 LIMIT 0,n。
7.2 RowBounds分页
public void testUserByRowBounds() {
SqlSession session = MybatisUtils.getSession();
int currentPage = 2; //第几页
int pageSize = 2; //每页显示几个
RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
//通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user: users){
System.out.println(user);
}
session.close();
}
7. 使用注解开发
Mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的,这样Java 注解的的表达力和灵活性十分有限,MyBatis 3提供了新的基于注解的配置,利用注解开发就不需要mapper.xml映射文件了。不过使用注解只适用于简单的查询语句,如果是复杂的就会出现上面所提到的查询结果为null的问题。
- sql 类型主要分成 :
-
- @select ()
- @update ()
- @Insert ()
- @delete ()
- 我们在我们的接口中添加注解
//根据id查询用户
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);
- 在mybatis的核心配置文件中注入
<!--使用class绑定接口-->
<mappers>
<mapper class="com.mapper.UserMapper"/>
</mappers>
- 测试
public void testGetAllUser() {
SqlSession session = MybatisUtils.getSession();
//底层主要应用反射得到UserMapper的方法,通过解析方法和注解实现查询
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
@Param:
- 在方法只接受一个参数的情况下,可以不使用@Param。
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
- 如果参数是 JavaBean , 则不能使用@Param。
- 不使用@Param注解时,参数只能有一个,并且是Javabean。
#与$的区别:
-
#{} 的作用是对传入的参数视为字符串,也就是它会预编译,相当于对数据 加上 双引号。 【推荐使用】
select * from user where name = #{name} select * from user where name = 'csdn' -
${} 的作用是直接进行字符串替换,相当于直接显示数据。
select * from user where name=${name} select * from user where name=csdn -
MyBatis排序时使用order by 动态参数时需要注意,用$而不是#。
总结:
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速也是首选做法,有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像ORDER BY,你可以这样来使用: ORDER BY ${columnName} 这里MyBatis不会修改或转义字符串。
重要:接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会导致潜在的SQL注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查。
8. 多对一和一对多的处理
8.1 多对一的处理
举一个学生和老师的例子,如果对于学生这边,就是多对一的关系,即从学生这边关联一个老师;如果对于老师这边,就是一个一对多的关系,即从一个老师下面拥有一群学生(集合)
实体类的编写:
@Data //GET,SET,ToString,有参,无参构造
public class Teacher {
private int id;
private String name;
}
@Data
public class Student {
private int id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
以下是对应Mapper文件的编写:
按查询嵌套处理: association中嵌套了一个select
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
<association property="teacher" column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
select="getTeacher"指定了要传递的目标,传递的参数是tid。
如果需要传递的对象只有一个时下面的#{}可以是任何值。
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id} and name = #{name}
</select>
按结果嵌套处理:
<select id="getStudents2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
总结:
-
按照查询进行嵌套处理就像SQL中的子查询
-
按照结果进行嵌套处理就像SQL中的联表查询
8.2 一对多的处理
实体类编写:
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
按查询嵌套处理:
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!--
集合的话,使用collection!
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
-->
<!--column是一对多的外键 , 写的是一的主键的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
按结果嵌套处理:
<mapper namespace="com.mapper.TeacherMapper">
<!--
思路:
1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname , t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id=#{id}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
</mapper>
总结:
- 关联-association
- 集合-collection
- 所以association是用于一对一和多对一,而collection是用于一对多的关系
- JavaType和ofType都是用来指定对象类型的
- JavaType是用来指定pojo中属性的类型
- ofType指定的是映射到list集合属性中pojo的类型。
9. 动态SQL
当面临复杂的业务的时候,需要写复杂的Sql语句时往往需要拼接,但是拼接Sql稍不注意就可能导致错误。这时候就需要用到Mybatis的动态Sql,组合成非常灵活的Sql语句,提高Sql语句准确性的同时,也大大提高了开发人员的开发效率。
if语句:
需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!
Where:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
Set:
更新操作使用set,用法同理。
<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
choose语句:
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
SQL片段:
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
<!-- 提取SQL片段: -->
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<!-- 引用SQL片段: -->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
注意:
- 最好基于 单表来定义 sql 片段,提高片段的可重用性
- 在 提取sql 片段中不要包括 where
Foreach:
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
测试:
public void testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
10. 缓存
10.1 简介
1. 什么是缓存(Cache)?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2. 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
3. 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
4. Mybatis的缓存
Mybatis系统中定义了两级缓存:一级缓存、二级缓存。为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
10.2 一级缓存
默认情况下只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存),与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效的四种情况:
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
-
sqlSession不同。
-
sqlSession相同,查询条件不同。
-
sqlSession相同,两次查询之间执行了增删改操作!
-
sqlSession相同,手动清除一级缓存
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.clearCache();//手动清除缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
10.3 二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,需要手动开启和配置。他是基于namespace级别的缓存,
工作机制:
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。新的会话查询信息,就可以从二级缓存中获取内容,不同的mapper查出的数据会放在自己对应的缓存(map)中。
使用步骤:
- 开启全局缓存
<setting name="cacheEnabled" value="true"/>
- 在需要开启二级缓存的mapper.xml(xxxMapper.xml)中配置标签。 例如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
结论:
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。
- 查出的数据都会被默认先放在一级缓存中。
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。