Mybatis
一、简介
1、什么是Mybatis
MyBatis 是一款优秀的持久层框架。
它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。2013年11月迁移到Github。
2、持久化
数据的持久化:
持久化就是将程序的数据在持久状态和瞬时状态转换的过程
内存的机制:断电就会失去数据
JDBC,IO文件是持久化的手段,JDBC是主要手段
持久化类似生活中存储食物的原理,把食物冷藏存储起来,需要食用再取出
为什么需要持久化:
有些数据,不能让其丢失
内存的价格太昂贵了,而磁盘的价格很便宜,把数据存储在磁盘更为划算
3、持久层
Dao层、Service层、Controller层等
持久层是完成持久化的代码块
层与层之间是界限明确的
4、为什么需要Mybatis
传统JDBC代码过于复杂,所以诞生了mybatis框架,可以使得代码自动化、简化。
优点:
1.简单
2.灵活
3.sql和代码分离,提高可维护性
4.提供映射标签,支持对象与数据库orm字段关系映射
5.提供对象关系映射标签,支持对象关系组件维护
6.提供xml标签,支持编写动态sql
7.使用人数多,认同广泛
二、实操Mybatis
使用任何技术的思路:搭建环境->导入依赖->编写代码->测试代码csdn
1、搭建环境
- 搭建数据库
- 新建项目
- 导入依赖
2、准备工作
- 编写mybatis的核心配置文件(其中包含原生JDBC的db.properties配置文件的内容,用于链接数据库,还包含mybatis映射注册)
- 编写mybatis工具类(作用:利用配置文件中的数据库配置,链接数据库,并得到mybatis的关键对象,sqlsession对象)
3、开始编写代码
需要:
- Dao/Mapper接口
- pojo实体类
- 接口的实现由原来的利用实现类实现换为使用一个mybatis规定的mapper配置文件(xml文件)
4、使用junit依赖包进行测试
三、增删改查操作
1、mapper.xml配置文件中:
- namespace的值为绑定对应的Dao/Mapper接口的路径
- select等sql标签中的id属性值为namespace对应的接口的方法名
- select等sql标签中的resultType属性值为sql语句的返回值,需要填写对应的pojo实体类或Java原生数据类型
- select等sql标签中的parameterType属性值为方法参数的数据类型
- 各种语句中,插入数据用#{数据名},其实${}也可以使用,但#可以防注入,所以一般默认使用#
增删改需要提交事务!!而查询则不需要(因为不用改变数据库的内容)
假如pojo实体类或者数据库中表,字段或参数过多,我们就要考虑使用Map
2、模糊查询
四、配置解析
1、核心配置文件
mybatis-config.xml
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
2、环境配置(environments)
- MyBatis 可以配置成适应多种环境
- 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
- mybatis默认事务管理器是JDBC,连接池为POOLED
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
3、属性(properties)
- 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。如db.properties
- 在XML中,所有标签都可以规定顺序
- 若引入外部properties配置文件,则优先使用配置文件中的数据,在properties属性下配置的属性优先级低于配置文件中的属性。
<!-- 引入外部配置properties文件 -->
<properties resource="db.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
4、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
- 给特定类取别名
<typeAliases>
<typeAlias type="com.xiafan.pojo.User" alias="User"/>
</typeAliases>
- 直接给包取别名,该包下的类可以直接用类名
<typeAliases>
<package name="com.xiafan.pojo"/>
</typeAliases>
- 可以用注解直接取别名,优先使用注解的别名,前提是需要先给包取别名,不能直接使用!!!否则会报错!!!
@Alias("User")
public class User
5、设置(settings)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7N63gRQ-1631102085748)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210826183305994.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRkNcZfd-1631102085758)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210826183316613.png)]
6、插件(plugins)
常见的插件,如:mybatis-generator-core、mybatis-plus、通用mapper
7、映射器(mappers)
- 映射方式一:通过xml注册映射**(推荐使用)**
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式一没有条件限制,可以任意使用
- 映射方式二:直接通过接口注册映射
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意: 方式二有使用限制
①对应接口和XML配置文件必须同名(即只有文件后缀不同)
②对应接口和XML配置文件必须在同个包下
-
映射方式三:直接使用包名注册映射,该包下的所有接口一起被注册
方式三也有使用限制,且使用限制和方式二相同
8、生命周期和作用域(scope)
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
一旦创建了 SqlSessionFactory,就不再需要它了。
局部变量
SqlSessionFactory:
说白就是可以想象为:数据库连接池。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
SqlSessionFactory 的最佳作用域是应用作用域。
最简单的就是使用单例模式或者静态单例模式。
SqlSession:
连接到连接池的一个请求!
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
用完后需要赶紧关闭,否则资源被占用!
这里的每一个Mapper,就代表一个具体的业务!
五、解决实体类属性名和数据库字段名不一致的映射问题
解决方式:
-
在sql语句中起别名
如:select id,name,pwd as password from mybatis.user where id = #{id}(属性名为password,字段名为pwd)
-
结果集映射(resultmap)
<!-- 结果集映射 id对应select等标签中的resultmap的名字 --> <!-- 如<select id="getUserList" resultMap="usermap"> --> <!-- type为实体类名称 --> <!-- column为数据库字段名,property为实体类中的属性名。一一对应就可以实现数据库字段名和属性名不一致的问题 --> <resultMap id="usermap" type="User"> <result column="id" property="ID" /> </resultMap>
resultMap
元素是 MyBatis 中最重要最强大的元素。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
六、日志
1、日志工厂
解决的问题:如果数据库操作出现了异常,需要排除错误,但由于报错往往难以直观得出错误的原因,这时就需要日志的帮助
曾经:sout、debug
现在:日志工厂
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIIkvTnX-1631102085763)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210901192302017.png)]
日志实现类型:
SLF4J 重点
LOG4J
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 重点(标准日志)
NO_LOGGING 无日志
日志也是在核心配置文件mybatis-config中配置的
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XEOmIoD-1631102085770)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210901192939838.png)]
2、LOG4J详解
什么是Log4j?
-
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
-
我们也可以控制每一条日志的输出格式;
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
-
可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
配置LOG4J的步骤
-
在pom.xml文件中导入log4j的依赖包(可以选择父项目中导入也可以选择子项目中导入)
<dependencies> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
-
在resources文件夹下建立log4j.properties文件进行配置
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码log4j.rootLogger = DEBUG,console ,file#控制台输出的相关设置log4j.appender.console = org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target = System.outlog4j.appender.console.Threshold = DEBUGlog4j.appender.console.layout = org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern = [%c]-%m%n#文件输出的相关设置log4j.appender.file = org.apache.log4j.RollingFileAppenderlog4j.appender.file.File = ./log/kuang.loglog4j.appender.file.MaxFileSize = 10mblog4j.appender.file.Threshold = DEBUGlog4j.appender.file.layout = org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern = [%p][%d{yy-MM-dd}][%c]%m%n#日志输出级别log4j.logger.org.mybatis=DEBUGlog4j.logger.java.sql=DEBUGlog4j.logger.java.sql.Statement=DEBUGlog4j.logger.java.sql.ResultSet=DEBUGlog4j.logger.java.sql.PreparedStatement=DEBUG
-
在核心配置文件mybatis-config中配置
<settings> <setting name="logImpl" value="LOG4J"/></settings>
-
Logger类的简单使用
导入import org.apache.log4j.Logger包
然后在类中声明静态logger对象,如:static Logger logger = Logger.getLogger(UserDaoTest.class);
最后在原先sout调试的地方使用logger语句进行调试,如:
logger.info("-info:output success");logger.debug("-debug:output success");logger.error("-error:output success");
七、分页操作
分页的作用:减少数据的处理量
1、使用Limit分页
limit分页的语法复习:
select * from user limit startindex,pagesize;从s开始显示p个数据select * from user limit n; 从0开始显示到n个数据PS:数据下标是从0开始的,程序员数数是从0开始数的!
mybatis实现分页的步骤:
-
编写接口
-
Mapper.xml编写sql语句
-
测试代码是否顺利运行
2、使用RowBounds分页(较少使用)
实际上是被淘汰的方式,在java代码层面做sql代码分页,而不是在mapper配置文件中配置即可,显然更为繁琐。
3、前端实现分页(分页插件pageHelper)
需要可以直接看文档使用即可
八、使用注解进行开发
1、 面向接口编程
之前学过面向对象编程,也学习过接口,但在真正的开发中,很多时候会选择面向接口编程。
根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对 系统设计人员来讲就不那么重要了;
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑 的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
2、注解使用
-
在接口上实现sql
@Select("select * from user")注解List<User> getUsers();
-
在核心配置文件中,配置映射
<!--绑定接口!--><mappers> <mapper class="com.kuang.dao.UserMapper" /></mappers>
-
测试代码
使用注解的本质原理,或者说是技术支持,是反射机制
底层是动态代理
3、mybatis的详细执行过程
学会如何分析框架原码,方便理解框架运行。
4、使用注解进行CRUD
提升:sqlsession工具类创建时可以直接进行事务的提交,而不需要体现在测试代码中
public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
当方法含有多个参数时,要使用@param注解
如:
//查询特定用户用户,当参数为多个的时候,参数前要加@param注解 @Select("select * from mybatis.user where id=#{id} and name=#{name}") User getUserByIdAndName(@Param("id")int id,@Param("name")String name);
- 注:sql语句中#{}中的参数名称必须和@Param("")中的参数名称相同,否则无法匹配
CRUD接口方法代码:
//查询全部用户 @Select("select * from mybatis.user") List<User> getUserList(); //查询特定用户用户,当参数为多个的时候,参数前要加@param注解 @Select("select * from mybatis.user where id=#{id} and name=#{name}") User getUserByIdAndName(@Param("id")int id,@Param("name")String name); @Insert("insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd})") int addUser(User user); @Update("update mybatis.user set name=#{name} where id=#{id}") int updateUser(User user); @Delete("delete from mybatis.user where id = #{id}") int deleteUser(@Param("id")int id);
@Param注解的使用:
- 基本类型的参数或String类型的参数,需要加上Param注解
- 引用类型不需要添加Param注解
- 如果只有一个基本类型,可以不添加,但可以培养加Param注解的习惯
- 我们在sql语句中引用的就是@Param注解中的参数名,如:@Param(“uid”)则sql语句中使用的参数就是uid
八の补充、Lombok的使用
1、背景
Lombok的诞生(为什么需要它):Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString;异常处理;I/O流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok应运而生。
如何导入:Lombok可以直接从idea中下载插件或者直接在pom文件中添加依赖导入
主要是帮助POJO实体类节省繁杂的初始化工作,如get、set方法和tostring方法以及构造方法等的实现,Lombok可以直接使用一个注解代替这些方法的实现
2、使用
下面三个注解是最常用的:
@Data是最常用的注解:它实现了无参构造方法、getter、setter、hascode、equals方法的实现或重写
@AllArgsConstructor :它实现了全参构造方法(注:使用@Data又使用@AllArgsConstructor的时候,@Date的无参构造方法会失效,此时需要再添加@NoArgsConstructor才能实现无参构造方法)
@NoArgsConstructor:它实现了无参构造方法
3、lombok的优缺点和争议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItZ8zKIu-1631102085785)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210904192342833.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nY92Hg42-1631102085787)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20210904192538589.png)]
九、一对多和多对一的处理
1、举例说明
举栗子:比如一个老师会对应多个学生
-
从老师的角度来说:是集合collection的关系,一个老师集合多个学生(一对多)
-
从学生角度来说:是关联association的关系,多个学生关联一个老师(多对一),也就是反映出来的是单个的**“对象”**
小知识:在java中创建多级目录用.,比如:com.xiafan.pojo;而在resources中创建多级目录则用/,比如:com/xiafan/pojo。如果分级符号用的不正确,则无法顺利分级。
2、关于多对一(关联association)
处理方式一:按照查询嵌套进行处理(子查询)
<!-- 目的:查询所有pojo学生类的信息(注:这里是要实体类的信息,而不是单纯数据库的原始信息) 思路一: 1.查询数据库中所有学生的信息 2.根据查询出来的学生tid,查询对应的老师信息(子查询思想) --> <select id="getStudent" resultMap="SandT"> select * from mybatis.student </select> <resultMap id="SandT" type="com.xiafan.pojo.Student"> <result property="id" column="id"/> <result property="name" column="name"/> <!-- 对于复杂的属性,需要进行单独处理,如这里的Teacher属性 如果该属性是关联属性,则使用association 如果该属性是集合属性,则使用collection property:将查询得到的结果集注入实体类中的属性 column:在上次查询得到的结果集中,用哪些列作为条件去执行下一条查询语句的参数 javaType:把sql查询出来的结果集封装成某个类的对象 select:指定下一条执行的sql语句 --> <association property="teacher" column="tid" javaType="com.xiafan.pojo.Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="com.xiafan.pojo.Teacher"> select * from mybatis.teacher where id = #{id} </select>
处理方式二:按照结果嵌套进行处理(关联查询)
<!-- 目的:查询所有pojo学生类的信息(注:这里是要实体类的信息,而不是单纯数据库的原始信息) 思路二: 按照结果查询,实际上是直接使用关联查询的sql语句,在结果映射中进行关联 --> <resultMap id="SandT2" type="com.xiafan.pojo.Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="com.xiafan.pojo.Teacher"> <result property="name" column="tname"/> </association> </resultMap> <select id="getStudent2" resultMap="SandT2"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid = t.id </select>
3、关于一对多(集合collection)
处理方式一:按照查询嵌套进行处理(子查询)
<!--按查询嵌套--> <select id="getTeacher2" resultMap="TandS2"> select * from mybatis.teacher where id = #{id} </select> <resultMap id="TandS2" type="com.xiafan.pojo.Teacher"> <collection property="students" javaType="ArrayList" ofType="com.xiafan.pojo.Student" select="getStudent" column="id"/> </resultMap> <select id="getStudent" resultType="com.xiafan.pojo.Student"> select * from student where tid = #{tid} </select>
处理方式二:按照结果嵌套进行处理(关联查询)
<!--按结果查询--> <select id="getTeacher" resultMap="TandS"> select s.id sid,s.name sname,t.id tid,t.name tname from student s,teacher t where s.tid = t.id and t.id = #{id} </select> <resultMap id="TandS" type="com.xiafan.pojo.Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--ofType:泛型类型--> <collection property="students" ofType="com.xiafan.pojo.Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> </collection> </resultMap>
4、题外话,面试必备的问题
- mysql的引擎
- innodb的底层原理
- 索引
- 索引的优化问题
十、动态SQL
什么是动态SQL:可以根据不同条件生成不同SQL语句的SQL语句被称为动态SQL
实际上,就是在使用各种标签拼接SQL语句而已,相比原始的使用Java代码进行拼接会简略很多。
Mybatis主要是依靠标签来实现动态SQL的,如IF,WHERE,FOREACH等,以下会举例常见标签。
1、if
if就是简单的条件判断,test中是if的判断条件,是最简单的标签,此处不进行赘述。
<select id="getBlogIfViews" parameterType="map" resultType="blog"> select * from blog <where> <if test="title!=null"> and title = #{title} </if> <if test="author!=null"> and author = #{author} </if> </where> </select>
2、where
where虽然使用简单但是是十分重要的一个标签,它可以解决我们可能出现拼接不成功的问题。
如:
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if></select>
乍一看觉得这样的配置没有什么问题,但如果恰好没有任何一个if条件成立的话,则会使得sql语句变成SELECT * FROM BLOG WHERE。
这就变成了错误的where语句,无法顺利执行,所以我们不再在sql语句中加入where,而是把where作为一个标签来使用,这样可以让框架自己判断是否需要where。
如:
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where></select>
这里引用mybatis3文档中的一句话:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
3、choose(when、otherwise)
choose标签相当于编程语言中经典的switch语句,用法也十分相似。先按when标签从上到下逐个匹配条件是否符合,若符合则拼接该when标签下的语句;如果when标签的所有条件都不符合,则拼接otherwise标签下的语句。
如:
<select id="getBlogChoose" 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>
4、trim(where、set)
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
如:
<update id="updateBlog" parameterType="map"> update mybatis.blog <set> <if test="title!=null"> title = #{title}, </if> <if test="author!=null"> author = #{author}, </if> </set> <where> id = #{id} </where> </update>
trim标签可以方便的实现SQL语句前后缀的添加或者删除
如:
<select id="getBlogIfViews" parameterType="map" resultType="blog"> select * from blog <trim prefix="where" prefixOverrides="and|or"> <if test="title!=null"> and title = #{title} </if> <if test="author!=null"> and author = #{author} </if> </trim></select>
以上使用trim替换了原来的where,功能一样,但可操控性更强了,可以随意控制前缀后缀的增减。
trim标签的含义:prefix:给sql语句拼接的前缀suffix:给sql语句拼接的后缀prefixOverrides:去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
5、SQL片段
SQL标签可以用于封装公用SQL片段语句,方便修改和使用
如:
<sql id="if-title-author"> <if test="title!=null"> and title = #{title} </if> <if test="author!=null"> and author = #{author} </if> </sql>
需要调用时,在需要的地方使用include标签即可。
如:
<select id="getBlogIfViews" parameterType="map" resultType="blog"> select * from blog <trim prefix="where" prefixOverrides="and|or"> <include refid="if-title-author"></include> </trim> </select>
SQL标签使用的注意事项:
- 最好基于单表定义SQL片段。防止联表查询等情况下SQL语句出错。
- 在SQL片段中不要存在where标签;最好只包含if,choose等判断标签。
6、foreach
循环加入所需的SQL元素,比如需要多条件查询的时候,为了方便减少代码改动并使得代码更灵活智能,我们使用foreach进行循环插入SQL元素。
如:
<select id="foreachGetBlog" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <!-- collection:map传入参数的键名(一般是一个LIST) item:LIST中每个数据的名字 open:插入时的起始符号 close:插入时的结束符号 separator:每个元素语句之间插入的符号 --> <foreach collection="views" item="view" open="(" separator="or" close=")"> views = #{view} </foreach> </where> </select>
以上的原SQL语句最终为:select * from mybatis.blog where views = #{view1} views = #{view2} views = #{view3}
其中#{view}代入测试用例中使用的数值。
十一、缓存
1、背景
-
一个东西被创造出来并使用,一定是因为它具有一定的价值,解决了某些在此之前存在的问题,那么为什么需要缓存?
-
在有缓存机制之前,每次进行查询操作都需要链接数据库,这样十分消耗资源,使得效率低下。
-
在建立缓存机制后,我们进行对某一特定数据库的第一次查询后会将查询的结果保存在内存中,这样当我们再次查询相同的数据的时候就可以直接从内存中获取,而不需要再链接数据库去获取。
-
可以人为控制查询后是否要存储在内存中:如果是经常需要查询且不经常发生改变的数据一般会使用缓存,反之则不建议使用
2、Mybatis中的缓存机制
Mybatis中默认定义了两级缓存:一级缓存和二级缓存
- 一级缓存是默认开启的,是sqlsession级别的缓存,也称为本地缓存
- 二级缓存需要手动开启,是namespace级别的缓存
3、一级缓存
一级缓存实际上就是本地缓存,它是在sqlsession级别上的缓存,在sqlsession创建(getSqlSession)并查询(select)后,缓存就会一直存在,知道sqlsession关闭(close)后缓存也会清除。
注意:一级缓存是在sqlsession创建(getSqlSession)并查询(select)后就把查询的内容放在缓存中的,如果之后在sqlsession关闭(close)之前改动了相关数据,那么缓存不会自动更新,换句话说 ,此时的缓存是旧版本的缓存,是无用缓存。
4、二级缓存
二级缓存的机制:当sqlsession关闭后,会把一级缓存的内容放到二级缓存中,换句话说也就是必须在有sqlsession关闭或有事务提交后才会二级缓存的存在。
开启二级缓存的步骤:
-
先在核心配置文件中开启全局缓存设置
<setting name="cacheEnable" value="true"> </setting>
-
在想开启二级缓存的Mapper.xml配置文件中加入二级缓存标签
<cache/>
也可以在固定语句中控制该查询是否开启二级缓存(可用于项目优化),如:
<select id="foreachGetBlog" parameterType="map" resultType="blog" useCache="true">
-
可以在cache标签中加入想要控制的参数数值,如:
<cache eviction="FIFO" //缓存方式 flushInteravl="60000"//缓存自动清楚时间 size="512"//缓存大小 readOnly="true" />
出现错误的原因:
- 实体类需要序列化,否则会报错
总结:
- 只要开启二级缓存,在同个Mapper下都有效
- 所有缓存会先存在一级缓存中,只有当会话提交或会话关闭的时候,才会将原一级缓存中的数据提交到二级缓存中
5、Mybatis的缓存机制
当用户查询数据时,查询顺序如下:
- 先从二级缓存中查询是否存在所需数据
- 若二级缓存中不存在,则从一级缓存中查询
- 若一级缓存也不存在,再从数据库中查询
6、自定义缓存ehcache
-
ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存
-
使用步骤:
-
导入相应依赖:
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --><dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version></dependency>
-
在想要使用自定义缓存的mapper.xml配置文件中配置cache
<cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
-
手动添加ehcache的xml配置文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。
-
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
–>
```- 一般常用Redis数据库作为缓存