一、延迟加载
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
延迟加载:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 | true \ false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true \ false | true |
//例如
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
场合:
当只有部分记录需要关联查询其它信息时,此时可按需延迟加载,需要关联查询时再向数据库发出sql,以提高数据库性能。
当全部需要关联查询信息时,此时不用延迟加载,直接将关联查询信息全部返回即可,可使用resultType或resultMap完成映射。
二、查询缓存
Mybatis提供查询缓存,用于减轻数据压力,提高数据库压力。
Mybatis提供一级缓存和二级缓存。
在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于缓存数据。
不同的SqlSession之间的缓存数据区域是互相不影响的。
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
1、一级缓存
第一次发起查询,先去找缓存中是否有id为1的用户信息,如果没有,从数据库中查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
第二次发起查询用户id为1的用户信息,先去缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
如果SqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存。目的是为了让缓存中存储的是最新的信息,避免脏读。
Mybatis默认支持一级缓存,不需要在配置文件中配置。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象。
应用场景:
2、二级缓存
SqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
SqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
开启二级缓存:
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 | 允许值 | 默认值 |
---|---|---|
cacheEnabled对在此配置文件下的所有cache 进行全局性开/关设置。 | true false | true |
要在你的Mapper映射文件中添加一行: ,表示此mapper
开启二级缓存。
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
为了将缓存数据取出执行反序列化,因为二级缓存存储介质多种多样,不一定在内存。
禁用二级缓存:
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
刷新缓存(就是清空缓存):
设置statement配置中的flushCache=“true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
应用场景:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
局限性:
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
案例
UserMapper.java
package com.xbj.mapper;
import com.xbj.po.TUser;
import java.util.List;
/**
* 延迟加载
*/
public interface UserMapper {
//延迟加载
List<TUser> userIncludeOrder();
TUser findUserById(Integer id);
List<TUser> findAll();
void deleteById(Integer id);
}
UserMapper.xml
<?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.xbj.mapper.UserMapper">
<!--开启二级缓存-->
<cache />
<!--SQL片段的引用-->
<sql id="baseSql">
SELECT * from t_user
</sql>
<!--延迟加载-->
<resultMap id="userInfos" type="TUser" autoMapping="true">
<id property="id" column="id"/>
<collection property="tOrders" ofType="TOrder" select="findorderByUid" column="id"/>
</resultMap>
<select id="findorderByUid" resultType="TOrder" parameterType="int">
SELECT * from t_order where uid=#{id}
</select>
<select id="userIncludeOrder" resultMap="userInfos">
SELECT * from t_user
</select>
<select id="findUserById" parameterType="int" resultType="TUser">
SELECT * FROM t_user where id=#{id}
</select>
<select id="findAll" resultType="TUser">
SELECT * from t_user
</select>
<delete id="deleteById" parameterType="int" >
DELETE from t_user WHERE id=#{id}
</delete>
</mapper>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>dome02</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
</project>
sqlMapConfig.xml
<?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>
<settings>
<!--允许延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<typeAliases>
<package name="com.xbj.po" />
</typeAliases>
<!--数据源的配置-->
<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/user"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--对象的操作-->
<mappers>
<!--<mapper resource="com/com.xbj/mapper/UserMapper.xml"/>-->
<!--<mapper url="file:///D:\projects\javaprojects\620\Demo02\src\main\resources\com\com.xbj\mapper\UserMapper.xml" />-->
<!--<mapper class="com.com.xbj.mapper.UserMapper" />-->
<package name="com.xbj.mapper" />
</mappers>
</configuration>
log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
TOrder.java
package com.xbj.po;
import java.io.Serializable;
import java.util.List;
/**
* @Author:晓宇码匠
* @Date:2019/6/21 0021
*/
public class TUser implements Serializable {
private int id;
private String username;
private String password;
private Integer age;
private List<TOrder> tOrders;
public List<TOrder> gettOrders() {
return tOrders;
}
public void settOrders(List<TOrder> tOrders) {
this.tOrders = tOrders;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TUser tUser = (TUser) o;
if (id != tUser.id) return false;
if (username != null ? !username.equals(tUser.username) : tUser.username != null) return false;
if (password != null ? !password.equals(tUser.password) : tUser.password != null) return false;
if (age != null ? !age.equals(tUser.age) : tUser.age != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (age != null ? age.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "TUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
TUser.java
package com.xbj.po;
import java.io.Serializable;
import java.util.List;
/**
* @Author:晓宇码匠
* @Date:2019/6/21 0021
*/
public class TUser implements Serializable {
private int id;
private String username;
private String password;
private Integer age;
private List<TOrder> tOrders;
public List<TOrder> gettOrders() {
return tOrders;
}
public void settOrders(List<TOrder> tOrders) {
this.tOrders = tOrders;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TUser tUser = (TUser) o;
if (id != tUser.id) return false;
if (username != null ? !username.equals(tUser.username) : tUser.username != null) return false;
if (password != null ? !password.equals(tUser.password) : tUser.password != null) return false;
if (age != null ? !age.equals(tUser.age) : tUser.age != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (age != null ? age.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "TUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
test.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import com.xbj.mapper.UserMapper;
import com.xbj.po.TUser;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @Author:晓宇码匠
* @Date:2019/6/21 0021
*/
public class test {
private UserMapper userMapper;
@Before
public void init() throws IOException {
InputStream is= Resources.getResourceAsStream("sqlMapConfig.xml");
userMapper=new SqlSessionFactoryBuilder().build(is).openSession(true).getMapper(UserMapper.class);
}
//测试延迟加载
@Test
public void t1(){
List<TUser> infos=userMapper.userIncludeOrder();
System.out.println(infos);
}
//测试一级缓存
@Test
public void t2(){
TUser user1=userMapper.findUserById(1);
TUser user2=userMapper.findUserById(1);
}
/*在进行增、改、删操作的时候会清空缓存*/
@Test
public void t3(){
userMapper.findAll();
userMapper.deleteById(4);
userMapper.findAll();
}
}
Test02.java
import com.xbj.mapper.UserMapper;
import com.xbj.po.TUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class Test02 {
private SqlSessionFactory factory;
private UserMapper userMapper;
private UserMapper userMapper2;
@Before
public void init() throws IOException {
InputStream is= Resources.getResourceAsStream("sqlMapConfig.xml");
factory=new SqlSessionFactoryBuilder().build(is);
userMapper=factory.openSession(true).getMapper(UserMapper.class);
userMapper2=factory.openSession(true).getMapper(UserMapper.class);
}
/*测试在不同的mapper会话中,是访问不同的缓存区*/
@Test
public void t1(){
TUser user1=userMapper.findUserById(1);
TUser user2=userMapper2.findUserById(1);
}
/*在进行增、改、删操作的时候会清空缓存*/
@Test
public void t2(){
userMapper.findAll();
userMapper.deleteById(4);
userMapper.findAll();
}
/*测试二级缓存的时候,需要将第一个查询操作先关闭,否则程序不会进入缓存里面查找数据*/
@Test
public void t3(){
SqlSession sqlSession1=factory.openSession(true);
SqlSession sqlSession2=factory.openSession(true);
sqlSession1.selectOne("findUserById",1);
sqlSession1.close(); //第一个人 调用后 会话结束 写入缓存
sqlSession2.selectOne("findUserById",1);
}
}
sql.sql
/*
Navicat MySQL Data Transfer
Source Server : Mysql
Source Server Version : 50717
Source Host : localhost:3306
Source Database : user
Target Server Type : MYSQL
Target Server Version : 50717
File Encoding : 65001
Date: 2019-06-21 15:06:09
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for t_item
-- ----------------------------
DROP TABLE IF EXISTS `t_item`;
CREATE TABLE `t_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`detail` varchar(200) DEFAULT NULL,
`price` float(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_item
-- ----------------------------
INSERT INTO `t_item` VALUES ('1', '苹果', '真好吃', '6.00');
INSERT INTO `t_item` VALUES ('2', '橘子', '淮南橘子', '3.00');
INSERT INTO `t_item` VALUES ('3', '香蕉', '海南香蕉,香蕉中的战斗蕉', '2.00');
INSERT INTO `t_item` VALUES ('4', '芒果', '泰国芒果', '11.00');
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
CONSTRAINT `t_order_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_order
-- ----------------------------
INSERT INTO `t_order` VALUES ('1', '1');
INSERT INTO `t_order` VALUES ('2', '1');
INSERT INTO `t_order` VALUES ('3', '1');
INSERT INTO `t_order` VALUES ('4', '2');
INSERT INTO `t_order` VALUES ('5', '3');
-- ----------------------------
-- Table structure for t_order_detail
-- ----------------------------
DROP TABLE IF EXISTS `t_order_detail`;
CREATE TABLE `t_order_detail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`oid` int(11) DEFAULT NULL,
`item_id` int(11) DEFAULT NULL,
`count` float(8,2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `oid` (`oid`),
KEY `item_id` (`item_id`),
CONSTRAINT `t_order_detail_ibfk_1` FOREIGN KEY (`oid`) REFERENCES `t_order` (`id`),
CONSTRAINT `t_order_detail_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `t_item` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_order_detail
-- ----------------------------
INSERT INTO `t_order_detail` VALUES ('1', '1', '1', '2.00');
INSERT INTO `t_order_detail` VALUES ('2', '1', '2', '2.00');
INSERT INTO `t_order_detail` VALUES ('3', '2', '3', '2.00');
INSERT INTO `t_order_detail` VALUES ('4', '2', '4', '2.00');
INSERT INTO `t_order_detail` VALUES ('5', '3', '1', '2.00');
INSERT INTO `t_order_detail` VALUES ('6', '3', '1', '2.00');
INSERT INTO `t_order_detail` VALUES ('7', '4', '2', '2.00');
INSERT INTO `t_order_detail` VALUES ('8', '4', '2', '2.00');
INSERT INTO `t_order_detail` VALUES ('9', '5', '1', '2.00');
INSERT INTO `t_order_detail` VALUES ('10', '1', '4', '2.00');
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(200) NOT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'tom', 'ok', '33');
INSERT INTO `t_user` VALUES ('2', 'lucy', 'ok', '43');
INSERT INTO `t_user` VALUES ('3', 'jack', 'ok', '53');