Mybatis之——延迟加载、缓存、注解开发

延迟加载

什么是延迟加载

实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
  • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

实现延迟加载

这里实现的是用户与账户一对多的延迟查询(一个用户下有多个账户)
当使用Mybatis创建的Dao代理对象获取其属性时
如果我们没有获取该用户的账户属性,那么则不执行查询账户的SQL语句

SQLMapConfig.xml添加配置

1
2
3
4
5
6
7
<settings>
<!--延迟加载全局开关-->
<setting name="lazyLoadingEnabled" value="true"/>

<!--当开启时,会加载该对象所有属性(实体类所有属性),否则按需加载,也就是等要用到某个属性再从数据库中获取-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

User实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

//该用户名下的账户信息,这里通过延迟加载
private List<Account> accounts;


public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday
+ ", sex=" + sex + ", address="
+ address + "]";
}
}

映射配置文件:UserDao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>


<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
-->
<collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findByUid"
column="id">
</collection>

</resultMap>
<!-- 配置查询所有用户信息的操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>

<!-- 根据用户 id 查询账户信息 -->
<select id="findByUid" resultType="account" parameterType="int">
select * from account where uid = #{uid}
</select>
  • 标签:用于加载关联的集合对象(某个用户下的所有账户信息集合)
  • select属性:用于指定查询账户信息的SQL语句
  • column属性:指定sql语句的参数来源,上面参数来自于user对象的id属性(user表的is字段),所以这里就写id了

缓存

什么是缓存?

答:存在于内存中的临时数据

为什么使用缓存?

答:减少和数据库的交互次数,提高执行效率

什么样的数据能使用缓存?什么样的数据不能使用缓存

适用与缓存的数据:

  • 经常查询并且不经常改变的数据
  • 数据的正确与否对最终结果影响不大的数据,比如:商品的库存,商品显示有库存,但下单买时告诉你没库存了,这样对最终的结果影响不大

不适用与缓存的数据:

  • 经常改变的数据
  • 数据的正确与否对最终结果影响很大的,例如:银行的汇率、股市的价格

Mybatis中的一级缓存和二级缓存

一级缓存

它是指Mybatis中SqlSession对象级别的缓存,它是不需要任何配置默认就有的
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中。
该区域的结构是一个Map。
当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。

  • 一级缓存数据的更变

    一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close(),clearCache()等方法时,就会清空一级缓存。

二级缓存

它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
它的存储原理与一级缓存唯一不相同的地方就是,一级缓存以Map对象形式存储,二级缓存则是以{"id":1, "username":"张三"}这种数据的形式进行存储

二级缓存配置步骤

1、在 SqlMapConfig.xml 文件开启二级缓存

1
2
3
4
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
  • ps:因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
    false 代表不开启二级缓存。

2、配置相关的 Mapper 映射文件

1
2
3
4
5
6
7
8
<?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.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
  • 标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
  • ps:所以二级缓存也可以叫Mappre级别的缓存,因为他作用在一个Mappre映射上

3、配置 statement(映射文件的select标签) 上面的 useCache 属性

1
2
3
4
5
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>

  • 将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
  • 注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

二级缓存注意事项

当我们在使用二级缓存时,所有缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

注解开发

注意事项:如果注解开发和xml文件一起使用了,不管主配置文件有没有调用、调用了哪个,Mybatis都会报错

常用注解

  • @Insert:实现新增
  • @SelectKey:新增后获取新记录id,然后封装到实体类对象中
  • @Update:实现更新
  • @Delete:实现删除
  • @Select:实现查询
  • @Result:实现结果集封装
  • @Results:可以与@Result 一起使用,封装多个结果集
  • @ResultMap:实现引用@Results 定义的封装
  • @One:实现一对一结果集封装
  • @Many:实现一对多结果集封装
  • @SelectProvider: 实现动态 SQL 映射
  • @CacheNamespace:实现注解二级缓存的使用

映射关系的注解说明

@Results 注解

代替的是标签****
该注解中可以使用单个@Result 注解,也可以使用@Result 集合

  • @Results({@Result(),@Result()})
  • 或@Results(@Result())

@Resutl 注解

代替了 **标签和**标签

  • id:是否是主键字段
  • column:数据库的列名
  • property:需要装配的属性名
  • one:需要使用的@One 注解(@Result(one=@One)()))
  • many:需要使用的@Many 注解(@Result(many=@many)()))

@One 注解(一对一)

代替了****标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

  • select:指定用来多表查询的 sqlmapper
  • fetchType:会覆盖全局的配置参数 lazyLoadingEnabled,这里是配置延迟加载
  • 使用格式:@Result(column=" ",property="",one=@One(select=""))

@Many 注解(多对一)

代替了****标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。

  • 注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义
  • 使用格式:@Result(property="",column="",many=@Many(select=""))

注解开发简单关系映射示例

UserDao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"),
@Result(column="birthday",property="userBirthday")
})
List<User> findAll();



/**
* 根据 id 查询一个用户
* @param userId
* @return
*/
@Select("select * from user where id = #{uid} ")
@ResultMap("userMap")
User findById(Integer userId);



/**
* 保存操作,并且获取新增记录的id,然后封装到user中
* @param user
* @return
*/
@Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address})")
@SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" })
int saveUser(User user);



/**
* 更新操作
* @param user
* @return
*/
@Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id} ")
int updateUser(User user);



/**
* 删除用户
* @param userId
* @return
*/
@Delete("delete from user where id = #{uid} ")
int deleteUser(Integer userId);



/**
* 查询使用聚合函数
* @return
*/
@Select("select count(*) from user ")
@ResultMap("userMap")
int findTotal();



/**
* 模糊查询
* @param name
* @return
*/
@Select("select * from user where username like #{username} ")
@ResultMap("userMap")
List<User> findByName(String name);
}

SqlMapConfig.xml主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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 文件的位置 -->
<properties resource="jdbcConfig.properties"></properties>


<!-- 配置别名的注册 -->
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>


<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型是 JDBC -->
<transactionManager type="JDBC"></transactionManager>


<!-- 配置数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<!-- 配置映射信息 -->
<mappers>
<!-- 配置 dao 接口的位置,它有两种方式
第一种:使用 mapper 标签配置 class 属性
第二种:使用 package 标签,直接指定 dao 接口所在的包
-->
<package name="com.itheima.dao"/>
</mappers>
</configuration>

注解开发复杂关系映射示例:一对一

  • 需求:

    • 加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
  • ps:通常一对一关系都采用立即加载,而一对多采用延迟加载(懒加载)

User 实体类及 Account 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;


public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
+ userBirthday + ", userSex="
+ userSex + ", userAddress=" + userAddress + "]";
}
}




/**
*
* <p>Title: Account</p>
* <p>Description: 账户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//多对一关系映射:从表方应该包含一个主表方的对象引用
private User user;



public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]";
}
}

账户(Account)的持久层接口注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
*
* <p>Title: IAccountDao</p>
* <p>Description: 账户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IAccountDao {
/**
* 查询所有账户,采用延迟加载的方式查询账户的所属用户
* @return
*/
@Select("select * from account")
@Results(id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
property="user",
one=@One(select="com.itheima.dao.IUserDao.findById",
fetchType=FetchType.LAZY)
)
})
List<Account> findAll();
}
  • fetchType=FetchType.LAZY: 开启延迟加载

用户(User)的持久层接口注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {

/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"),
@Result(column="birthday",property="userBirthday")
})
List<User> findAll();



/**
* 根据 id 查询一个用户
* @param userId
* @return
*/
@Select("select * from user where id = #{uid} ")
@ResultMap("userMap")
User findById(Integer userId);
}

注解开发复杂关系映射示例:一对多

  • 需求:

    • 查询用户信息时,也要查询他的账户列表。使用注解方式实现。
  • 分析:

    • 一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
  • ps:通常一对一关系都采用立即加载,而一对多采用延迟加载(懒加载)

User 实体类加入 List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
//一对多关系映射:主表方法应该包含一个从表方的集合引用
private List<Account> accounts;



public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
+ userBirthday + ", userSex="
+ userSex + ", userAddress=" + userAddress + "]";
}
}

用户(User)持久层接口注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"),
@Result(column="birthday",property="userBirthday"),
@Result(column="id",property="accounts",
many=@Many(
select="com.itheima.dao.IAccountDao.findByUid",
fetchType=FetchType.LAZY
)
)
})
List<User> findAll();
}

  • @Many:相当于<collection>的配置
  • select 属性:代表将要执行的 sql 语句
  • fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值

账户(Account)持久层对象注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
*
* <p>Title: IAccountDao</p>
* <p>Description: 账户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IAccountDao {

/**
* 根据用户 id 查询用户下的所有账户
* @param userId
* @return
*/
@Select("select * from account where uid = #{uid} ")
List<Account> findByUid(Integer userId);
}

基于注解的二级缓存配置

SqlMapConfig.xml主配置文件中开启二级缓存

1
2
3
4
5
<!-- 配置二级缓存 -->
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>

在持久层接口中注解配置二级缓存

1
2
3
4
5
6
7
8
9
/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}