首先了解什么叫缓存,缓存可以看作是一种程序的优化机制,拿查询来说,第一次查询,会访问数据库,查询到结果之后,先将结果保存在内存中,然后在返回结果;第二次执行相同的sql时,先判断内存中是否有此查询的结果,有就直接返回不访问数据库;没有再去访问数据库;这样就保证了程序访问数据库的次数减少,防止了数据库压力过大导致崩溃。
MyBatis中的缓存是默认开启的无法关闭,当然只是单指MyBatis单独存在的情况下,后期涉及到MyBatis整合Spring,Spring会将一级缓存关闭掉,当然这是后话!但只有MyBatis的情况下,一级缓存是无法关闭的!
一级缓存的作用范围
一级缓存的作用范围是同一个session
和同一个sql相同的参数
当我们执行一个查询语句两次时,我们看日志会是什么情况
@Test
public void testQueryBookByAuthorOrName(){
BookDao bd = sqlSession.getMapper(BookDao.class);
List<Book> books = bd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
List<Book> books1 = bd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
for (Book book : books) {
System.out.println(book);
}
}
此时我们查看日志
发现,我们执行了两次查询,但是只执行了一次sql语句,为了证明缓存的存在,我们在执行一遍查询之后将缓存清除掉,在去查看日志!
sqlSession.clearCache();
是清除缓存
@Test
public void testQueryBookByAuthorOrName(){
BookDao bd = sqlSession.getMapper(BookDao.class);
List<Book> books = bd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
//清除缓存
sqlSession.clearCache();
List<Book> books1 = bd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
for (Book book : books) {
System.out.println(book);
}
}
日志如下
我们可以很清楚的看到,缓存清除之后,sql语句执行了两边;所以能够证明一级缓存默认开启,且无法关闭,能够优化查询效率。但是注意一点哦
执行修改操作会清空缓存哦!例如 :update insert delete等
二级缓存的作用范围是:
一个mapper的namespace ,同一个namespace中查询sql可以从缓存中命中。
开启二级缓存的方法是在Mapper文件里面加一个标签 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.senior.dao.BookDao">
<cache/>
</mapper>
完整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.senior.dao.BookDao">
<cache/>
<select id="queryBookByAuthorOrName" resultType="com.senior.entity.Book">
select * from book
<where>
<if test="author!=null">
author = #{author}
</if>
<if test="name!=null">
and name = #{name}
</if>
</where>
</select>
<update id="updateBook" parameterType="com.senior.entity.Book">
update book
<set>
<if test="name!=null">
name = #{name},
</if>
<if test="author!=null">
author = #{author},
</if>
<if test="createDate!=null">
createdate = #{createDate},
</if>
<if test="status !=null">
status = #{status},
</if>
<if test="clazzId !=null">
clazzid = #{clazzId}
</if>
</set>
where id=#{id}
</update>
<select id="findBookByIds" resultType="com.senior.entity.Book">
select * from book where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
</mapper>
测试观看日志;为了验证二级缓存的存在,咱们查询一次之后把Session给关闭掉,在创建一个新的SqlSession看它的日志输出情况!
@Test
public void testQueryBookByAuthorOrName(){
BookDao bd = sqlSession.getMapper(BookDao.class);
List<Book> books = bd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
//关闭sqlSession
sqlSession.close();
//创建一个新的SqlSession
SqlSession newSqlSession = MyBatisUtil.openSqlSession();
BookDao newDd = newSqlSession.getMapper(BookDao.class);
List<Book> books1 = newDd.queryBookByAuthorOrName("皇甫嗷嗷叫", null);
for (Book book : books) {
System.out.println(book);
}
}
查看日志
可以看出Sql只执行了一次,由第二个圈着的地方:是计算的缓存命中率,命中率为0.5,执行了两次sql,在缓存中只查了一次,所以它的命中率为0.5,也很直接的证明了二级缓存在起作用。
1.不开启 二级缓存默认关闭 2.在全局配置文件中配置,全局配置文件一旦禁用二级缓存,
Mapper开启二级缓存也没用
,Mapper配置文件的配置如下:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
完整配置文件 注意顺序
<?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 resource="properties/db.properties">
<!--开启配置文件配置默认值-->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
<!--配置默认使用那个环境变量 以id作为标志-->
<environments default="test">
<!--配置连接环境-->
<environment id="test">
<!--事务管理器使用JDBC-->
<transactionManager type="JDBC"/>
<!--数据源 连接池使用该POOLED-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver:com.mysql.jdbc.Driver}"/>
<property name="url" value="${mysql.url:jdbc:mysql://127.0.0.1:3306/testmybatis}"/>
<property name="username" value="${mysql.username:root}"/>
<property name="password" value="${mysql.password:hr}"/>
</dataSource>
</environment>
</environments>
<!--将Mapper文件在这里注册-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
<mapper resource="mapper/BookMapper.xml"/>
</mappers>
</configuration>
其中可以配置缓存策略
在我们使用Sql进行查询时,难免有时候会遇到表字段与实体类属性不相同的情况,我们可以这样解决! select id as
你的属性名
<select id="queryBookByAuthorOrName" resultType="com.senior.entity.Book">
select id as `你的属性名`,name,author from book
<where>
<if test="author!=null">
author = #{author}
</if>
<if test="name!=null">
and name = #{name}
</if>
</where>
</select>
这样做只适用于单表查询,如果时多表查询你应该怎么搞?此时就需要用到resultMap属性了 我们先创建一个类别表,以便下面进行测试
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `bookclazz`;
CREATE TABLE `bookclazz` (
`id` int(7) NOT NULL,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `bookclazz` VALUES (1, '国外名著');
INSERT INTO `bookclazz` VALUES (2, '四大名著');
INSERT INTO `bookclazz` VALUES (3, 'MyBatsi');
SET FOREIGN_KEY_CHECKS = 1;
此时如果有个需求:查询一个类别下的所有书籍,此时就应该会用到表连接,其sql应该这样写!
SELECT c.`name`,b.`name`,b.createdate,b.author FROM book b INNER JOIN bookclazz c on b.clazzid=c.id ORDER BY c.id
查询结果是这样
哪映射到Mapper文件上应该怎么写呢?此时我们应该去回想一下JAVA面向对象的思想,试想,一个类别下应该有多个书籍,所以类别实体类下应该有个Book类型的集合!一本书应该只能属于一个类别,所以书籍实体类下应该会有一个类别实体类对象,具体实体类设计应该这样搞!
package com.senior.entity;
import java.util.Date;
/**
* 书籍实体类
* @author 皇甫
*/
public class NewBook {
private Integer id;
private String name;
private String author;
private Date createDate;
private Integer status;
/**
* 一本书一定属于一个类别,所以应该有个类别对象
*/
private BookClazz bookClazz;
@Override
public String toString() {
return "NewBook{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", createDate=" + createDate +
", status=" + status +
", bookClazz=" + bookClazz +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public BookClazz getBookClazz() {
return bookClazz;
}
public void setBookClazz(BookClazz bookClazz) {
this.bookClazz = bookClazz;
}
public NewBook(Integer id, String name, String author, Date createDate, Integer status, BookClazz bookClazz) {
this.id = id;
this.name = name;
this.author = author;
this.createDate = createDate;
this.status = status;
this.bookClazz = bookClazz;
}
public NewBook() {
}
}
package com.senior.entity;
import java.util.ArrayList;
import java.util.List;
/**
* 类别实体类
* @author 皇甫
*/
public class BookClazz {
private Integer id;
private String name;
/**
* 每一个类别下都有很多本书籍,故而使用集合存放
*/
private List<Book> books = new ArrayList<Book>();
@Override
public String toString() {
return "BookClazz{" +
"id=" + id +
", name='" + name + '\'' +
", books=" + books +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public BookClazz(Integer id, String name, List<Book> books) {
this.id = id;
this.name = name;
this.books = books;
}
public BookClazz() {
}
}
编写DAO接口类
package com.senior.dao;
import com.senior.entity.BookClazz;
import java.util.List;
/**
* @author 皇甫
*/
public interface BookClazzDao {
/**
* 查询所有
* @return
*/
public List<BookClazz> queryAllBookClazz();
}
编写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.senior.dao.BookClazzDao">
<!--id 随便起 type你要返回的类型 这里返回的是书籍类别-->
<resultMap id="queryAllBookClazzMap" type="com.senior.entity.BookClazz">
<!--column 为你数据库字段的名字 或as重名后的名字 property为对应的实体类属性的名字-->
<!--id 为主键映射 result为普通字段映射-->
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<!--collection 代表你要映射的是实体类里面的集合属性-->
<!--property 实体类里面集合类的名字-->
<!--javaType 集合类的泛型种类-->
<collection property="books" javaType="com.senior.entity.Book">
<id property="id" column="b_id"/>
<result property="name" column="b_name"/>
<result property="createDate" column="b_date"/>
<result property="author" column="b_author"/>
</collection>
</resultMap>
<!--id 对应方法的名字 resultMap对应resultMap定义的id值-->
<select id="queryAllBookClazz" resultMap="queryAllBookClazzMap">
SELECT
c.id as c_id,
c.`name` as c_name,
b.id as b_id,
b.`name` as b_name,
b.createdate b_date,
b.author b_author
FROM
book b INNER JOIN bookclazz c on b.clazzid=c.id ORDER BY c.id
</select>
</mapper>
测试
@Test
public void testQueryAllBookClazz(){
BookClazzDao bc = sqlSession.getMapper(BookClazzDao.class);
List<BookClazz> bookClazzes = bc.queryAllBookClazz();
for (BookClazz bookClazz : bookClazzes) {
System.out.println("------------------"+bookClazz.getName()+"------------------");
for (Book book : bookClazz.getBooks()) {
System.out.println(book);
}
}
}
结果
------------------国外名著------------------
Book{id=1, name='鲁宾孙漂流记', author='丹尼尔.迪福', createDate=Sun Feb 24 14:38:58 CST 2019, status=null, clazzId=null}
Book{id=7, name='简爱', author='夏洛蒂.勃朗特', createDate=Thu Feb 14 14:41:11 CST 2019, status=null, clazzId=null}
------------------四大名著------------------
Book{id=3, name='水浒传', author='施耐庵', createDate=Fri Feb 15 14:39:44 CST 2019, status=null, clazzId=null}
Book{id=6, name='聊斋志异', author='蒲松龄', createDate=Tue Jan 29 14:40:56 CST 2019, status=null, clazzId=null}
Book{id=2, name='红楼梦', author='曹雪芹', createDate=Tue Feb 12 14:39:27 CST 2019, status=null, clazzId=null}
Book{id=5, name='西游记', author='吴承恩', createDate=Wed Jan 30 14:40:39 CST 2019, status=null, clazzId=null}
Book{id=4, name='三国演义', author='罗贯中', createDate=Sun Feb 24 14:40:13 CST 2019, status=null, clazzId=null}
------------------MyBatsi------------------
Book{id=9, name='MyBatis中级篇章', author='皇甫嗷嗷叫', createDate=Sun Feb 24 14:55:34 CST 2019, status=null, clazzId=null}
Book{id=8, name='MyBatis入门篇章', author='皇甫嗷嗷叫', createDate=Sun Feb 24 14:54:59 CST 2019, status=null, clazzId=null}
Book{id=10, name='MyBatis高级篇章', author='皇甫嗷嗷叫', createDate=Sun Feb 24 14:56:01 CST 2019, status=null, clazzId=null}
那么当我想查询一个书属于那个类别应该如何去查呢? dao接口
/**
* 查询所有书籍以及类别
* @return
*/
public List<NewBook> findBookAndBookClazz();
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.senior.dao.BookDao">
<cache/>
<resultMap id="findBookAndBookClazzMap" type="com.senior.entity.NewBook">
<id property="id" column="b_id"/>
<result property="name" column="b_name"/>
<result property="createDate" column="b_date"/>
<result property="author" column="b_author"/>
<!--association 为对象类型不是集合类型-->
<association property="bookClazz" javaType="com.senior.entity.BookClazz">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
</association>
</resultMap>
<select id="findBookAndBookClazz" resultMap="findBookAndBookClazzMap">
SELECT
c.id as c_id,
c.`name` as c_name,
b.id as b_id,
b.`name` as b_name,
b.createdate b_date,
b.author b_author
FROM
book b INNER JOIN bookclazz c on b.clazzid=c.id ORDER BY c.id
</select>
</mapper>
测试
@Test
public void testFindBookAndBookClazz(){
BookDao bd = sqlSession.getMapper(BookDao.class);
List<NewBook> books = bd.findBookAndBookClazz();
for (NewBook book : books) {
System.out.println(book.getName()+"--类别:"+book.getBookClazz().getName());
}
}
结果
鲁宾孙漂流记--类别:国外名著
简爱--类别:国外名著
水浒传--类别:四大名著
聊斋志异--类别:四大名著
红楼梦--类别:四大名著
西游记--类别:四大名著
三国演义--类别:四大名著
MyBatis中级篇章--类别:MyBatsi
MyBatis入门篇章--类别:MyBatsi
MyBatis高级篇章--类别:MyBatsi
我们发现两次查询的SQL是一样的,但是结果却截然不同,这就是Map映射的魅力!
但是,我们能否更简化一点呢?既然一个SQL两个配置文件都用到了 我们能否将SQL提取出来呢
Mapper内的SQL片段提取
<?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.senior.dao.BookClazzDao">
<!--id 随便起 type你要返回的类型 这里返回的是书籍类别-->
<resultMap id="queryAllBookClazzMap" type="com.senior.entity.BookClazz">
<!--column 为你数据库字段的名字 或as重名后的名字 property为对应的实体类属性的名字-->
<!--id 为主键映射 result为普通字段映射-->
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<!--collection 代表你要映射的是实体类里面的集合属性-->
<!--property 实体类里面集合类的名字-->
<!--javaType 集合类的泛型种类-->
<collection property="books" javaType="com.senior.entity.Book">
<id property="id" column="b_id"/>
<result property="name" column="b_name"/>
<result property="createDate" column="b_date"/>
<result property="author" column="b_author"/>
</collection>
</resultMap>
<!--将SQL片段提提取出来-->
<sql id="NEWBOOKANDBOOKCLAZZ">
c.id as c_id,
c.`name` as c_name,
b.id as b_id,
b.`name` as b_name,
b.createdate b_date,
b.author b_author
</sql>
<!--id 对应方法的名字 resultMap对应resultMap定义的id值-->
<select id="queryAllBookClazz" resultMap="queryAllBookClazzMap">
/*SQL语句内引入SQL片段*/
SELECT
<include refid="NEWBOOKANDBOOKCLAZZ"/>
FROM
book b INNER JOIN bookclazz c on b.clazzid=c.id ORDER BY c.id
</select>
</mapper>
查询语句中出现大于号和小于号的解决方案
业务需求,求书籍id小于2的数据
select * from book where id>2
Mapper
<!--报错 因为不允许使用`<`-->
<select id="queryBook" resultType="com.senior.entity.Book">
select * from book where id < 2
</select>
<select id="queryBook" resultType="com.senior.entity.Book">
select * from book where id < 2
</select>
<select id="queryBook" resultType="com.senior.entity.Book">
<![CDATA[
select * from book where id < 2
]]>
</select>