Mybatis系列文章
文章目录
1. Mybatis 中的延迟加载
1.1 延迟加载和立即加载
- 在上一篇笔记的学习中,我们用到了用户和账户这个一对多的例子。假如此时我们有 1 个用户,该用户拥有 100 个账户,那么问题来了:
- 当我们查询该用户的时候,要不要把关联的账户也一起查询出来?
- 当我们查询某个账户的时候,要不要把关联的用户一起查询出来?
- 答案很简单。在查询用户的时候,用户所拥有的账户信息应该是需要使用的时候才去查询,不然每次查询该用户的时候,都要查询他拥有的账户,那么开销无疑是比较大的;而在查询账户的时候,由于每个账户对应一个用户,所以应该让用户信息随账户信息一并查询出来,否则别人不知道该账户属于谁。
- 延迟加载
- 延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载。
- 在一对多或多对多的表关系中,通常情况下我们都是采用延迟加载。
- 立即加载
- 立即加载就是不管是否需要数据,只要一进行查询,就会把相关联的数据一并查询出来。
- 在多对一或一对一的表关系中,通常情况下我们都是采用立即加载。
1.2 一对一实现延迟加载
- 实现以下需求:
- 当查询账户信息时使用延迟加载。也就是说,如果不需要使用用户信息的话,那么只查询账户信息;只有当需要使用用户信息时,才去关联查询
- 首先在 Mybatis 的配置文件中开启延迟加载
<!-- 开启延迟加载 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 注意,在编写 Mybatis 的配置文件时,文档结构一定不可以随便写,一定要按照官方文档所要求的顺序,比如说:
<settings></settings>
标签不可以写在<environments></environments>
下方。具体文档结构见下图:
- 具体说明参考官方文档 : Mybatis 的 XML 配置
- 修改上一篇笔记编写好的账户映射文件
AccountMapper.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="cn.ykf.mapper.AccountMapper">
<!-- 定义可以封装带有User的Account的 resultMap -->
<resultMap id="AccountWithUserMap" type="cn.ykf.pojo.Account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 关联 User 对象 -->
<association property="user" javaType="cn.ykf.pojo.User" column="uid" select="cn.ykf.mapper.UserMapper.getUserById"></association>
</resultMap>
<!-- 配置查询所有账户,延迟加载用户信息 -->
<select id="listAllAccounts" resultMap="AccountWithUserMap">
SELECT * FROM account
</select>
</mapper>
<association></association>
标签中的select
属性表示我们要调用的映射语句的 ID,它会从column
属性指定的列中检索数据,作为参数传递给目标 select 语句。column
属性指定传递给我们要调用的映射语句的参数。
cn.ykf.mapper.UserMapper.getUserById
的映射文件如下:
<mapper namespace="cn.ykf.mapper.UserMapper">
<!-- 查询单个用户 -->
<select id="getUserById" parameterType="java.lang.Integer" resultType="cn.ykf.pojo.User">
SELECT * FROM user WHERE id = #{uid}
</select>
</mapper>
- 执行上篇笔记的测试代码,运行结果如下
- 可以发现,现在查询账户的话,并不会和之前一样,把所有的账户信息连同用户信息一并查询出来。而是由于我们在测试代码中打印了 Account 对象,其中调用了 User 对象,才会进行查询。
1.3 一对多实现立即加载
- 实现以下需求:
- 当查询用户信息时使用延迟加载。也就是说,如果不需要使用账户信息的话,那么只查询用户信息;只有当需要使用账户信息时,才去关联查询
- 还是一样先修改 账户 的映射文件,不同的是现在是使用
<collection></collection>
进行延迟加载
<?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="cn.ykf.mapper.UserMapper">
<!-- 配置 resultMap ,完成实体类与数据库表的映射 -->
<resultMap id="UserWithAccountsMap" type="cn.ykf.pojo.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="address" column="address"/>
<!-- 配置user对象中accounts集合的映射 -->
<collection property="accounts" ofType="cn.ykf.pojo.Account" column="id" select="cn.ykf.mapper.AccountMapper.getAccountByUid"></collection>
</resultMap>
<!-- 配置查询所有用户,延迟加载账户信息 -->
<select id="listAllUsers" resultMap="UserWithAccountsMap">
SELECT * FROM user
</select>
</mapper>
- 在账户实体类对应的接口层及配置文件中添加对应的方法
/**
* 根据用户id查询账户
*
* @param uid 用户id
* @return
*/
Account getAccountByUid(Integer uid);
<mapper namespace="cn.ykf.mapper.AccountMapper">
<!-- 根据用户id查询账户 -->
<select id="getAccountByUid" parameterType="int" resultType="cn.ykf.pojo.Account">
SELECT * FROM account WHERE uid = #{uid}
</select>
</mapper>
- 运行结果
2. Mybatis 中的缓存
- 什么是缓存?
- 缓存就是存在于内存中的临时数据。
- 为什么要使用缓存?
- 为了减少和数据库交互的次数,提高执行效率。
- 适用于缓存的数据
- 经常查询并且不经常改变的数据。
- 数据的正确与否对最终结果影响不大的。
- 不适用于缓存的数据
- 经常改变的数据。
- 数据的正确与否对最终结果影响很大的。例如:商品的库存、银行的汇率、股市的牌价等。
2.1 Mybatis 的一级缓存
- 一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就会存在。当调用 SqlSession 的修改、添加、删除、commit()、close()、clearCache() 等方法时,就会清空一级缓存。
- 一级缓存流程如下图
- 第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
- 得到用户信息,将用户信息存储到一级缓存中。
- 如果
sqlSession
去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 - 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
- Mybatis 默认就是使用一次缓存的,不需要配置。
- 一级缓存中存放的是对象。(一级缓存其实就是 Map 结构,直接存放对象)
2.2 Mybatis 的二级缓存
- 二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 SQL 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
- 二级缓存流程如下图
- 当
sqlSession1
去查询用户信息的时候,Mybatis 会将查询数据存储到二级缓存中。 - 如果
sqlSession3
去执行相同 Mapper 映射下的 SQL 语句,并且执行 commit 提交,那么 Mybatis 将会清空该 Mapper 映射下的二级缓存区域的数据。 sqlSession2
去查询与sqlSession1
相同的用户信息,Mybatis 首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
- 当
- 如果想使用 Mybatis 的二级缓存,那么应该做以下配置
- 首先在 Mybatis 配置文件中添加配置 (这一步其实可以忽略,因为默认值为 true)
<settings> <!-- 开启缓存 --> <setting name="cacheEnabled" value="true"/> </settings>
- 接着在映射文件中配置
<mapper namespace="cn.ykf.mapper.UserMapper"> <!-- 使用缓存 --> <cache/> </mapper>
- 最后在需要使用二级缓存的操作上配置 (针对每次查询都需要最新数据的操作,要设置成
useCache="false"
,禁用二级缓存)
<select id="listAllUsers" resultMap="UserWithAccountsMap" useCache="true"> SELECT * FROM user </select>
- 当我们使用二级缓存的时候,所缓存的类一定要实现
java.io.Serializable
接口,这样才可以使用序列化的方式来保存对象。- 由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象。
3. Mybatis 中的注解开发
- 在 Mybatis 的注解开发中,常用的注解如下表所示:
注解 | 作用 |
---|---|
@Intsert | 实现新增 |
@Update | 实现更新 |
@Delete | 实现删除 |
@Select | 实现查询 |
@Results | 实现结果集封装 |
@ResultMap | 实现引用 @Results 定义的封装 |
@One | 实现一对一结果集封装 |
@Many | 实现一对多结果集封装 |
@SelectProvider | 实现动态 SQL 映射 |
@CacheNamespace | 实现注解二级缓存的使用 |
3.1 Mybatis 使用注解实现单表 CURD
- 用户实体类接口层
UserMapper
代码如下
public interface UserMapper {
/**
* 查询所有用户
*
* @return
*/
@Select("SELECT * FROM user")
List<User> listAllUsers();
/**
* 添加用户
*
* @param user
* @return 成功返回1,失败返回0
*/
@Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
@SelectKey(keyProperty = "id", keyColumn = "id", statement = "SELECT LAST_INSERT_ID()", resultType = Integer.class, before = false)
int saveUser(User user);
/**
* 根据id删除用户
*
* @param userId
* @return 成功返回1,失败返回0
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int removeUserById(Integer userId);
/**
* 修改用户
*
* @param user
* @return 成功返回1,失败返回0
*/
@Update("UPDATE user SET username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address} WHERE id = #{id}")
int updateUser(User user);
/**
* 根据id查询单个用户
*
* @param userId
* @return
*/
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer userId);
/**
* 根据姓名模糊查询多个用户
*
* @param username
* @return
*/
@Select("SELECT * FROM user WHERE username LIKE CONCAT('%',#{username},'%')")
List<User> listUsersByName(String username);
/**
* 查询用户总数
*
* @return
*/
@Select("SELECT COUNT(id) FROM user")
int countUser();
}
- 注意,如果此时实体类的属性与数据库表列名不一致,那么我们应该使用
@Results、@Result、@ResultMap
等注解,如下
public interface UserMapper {
/**
* 查询所有用户
*
* @return
*/
@Select("SELECT * FROM user")
@Results(id = "UserMap",value = {
@Result(id = true,property = "userId",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userBirthday",column = "birthday"),
@Result(property = "userSex",column = "sex"),
@Result(property = "userAddress",column = "address"),
})
List<User> listAllUsers();
/**
* 添加用户
*
* @param user
* @return 成功返回1,失败返回0
*/
@Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
@ResultMap("UserMap")
int saveUser(User user);
@Results
注解用于定义映射结果集,相当于标签<resultMap></resultMap>
。
- 其中,
id
属性为唯一标识。value
属性用于接收@Result[]
注解类型的数组。@Result
注解用于定义映射关系,相当于标签<id />
和<result />
- 其中,
id
属性指定主键。property
属性指定实体类的属性名,column
属性指定数据库表中对应的列。@ResultMap
注解用于引用@Results
定义的映射结果集,避免了重复定义映射结果集。
3.2 Mybatis 使用注解实现多对一(一对一)
- 还是以上一篇笔记的用户和账户为例子。一个用户可以有多个账户,而一个账户只能对应一个用户。
- 在 Mybatis 中,多对一是作为一对一来进行处理的。也就是说,虽然多个账户可以属于同一个用户(多对一),但是在实体类中,我们是在账户类中添加一个用户类的对象引用,以此来表明所属用户。(此时就相当于一对一)
- 账户实体类和用户实体类见上篇笔记,接口层代码如下
public interface AccountMapper {
/**
* 查询所有账户,并查询所属用户,采用立即加载
*
* @return
*/
@Select("SELECT * FROM account")
@Results(id = "AccountMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "uid", column = "uid"),
@Result(property = "user", column = "uid",
one = @One(select = "cn.ykf.mapper.UserMapper.getUserById", fetchType = FetchType.EAGER))
})
List<Account> listAllAccounts();
}
public interface UserMapper {
/**
* 根据id查询单个用户
*
* @param userId
* @return
*/
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer userId);
}
@One
注解相当于标签<association></association>
,是多表查询的关键,在注解中用来指定子查询返回单一对象。
- 其中,
select
属性指定用于查询的接口方法,fetchType
属性用于指定立即加载或延迟加载,分别对应FetchType.EAGER
和FetchType.LAZY
- 在包含
@one
注解的@Result
中,column
属性用于指定将要作为参数进行查询的数据库表列。
3.3 Mybatis 使用注解实现一对多
- 为用户实体类添加包含账户实体类的集合引用,具体代码见上篇笔记。接口层代码如下
public interface UserMapper {
/**
* 查询所有用户,并且查询拥有账户,采用延迟加载
*
* @return
*/
@Select("SELECT * FROM user")
@Results(id = "UserMap", value = {
@Result(id = true, property = "userId", column = "id"),
@Result(property = "userName", column = "username"),
@Result(property = "userBirthday", column = "birthday"),
@Result(property = "userSex", column = "sex"),
@Result(property = "userAddress", column = "address"),
@Result(property = "accounts", column = "id",
many = @Many(select = "cn.ykf.mapper.AccountMapper.getAccountByUid", fetchType = FetchType.LAZY))
})
List<User> listAllUsers();
public interface AccountMapper {
/**
* 根据用户id查询账户列表
*
* @param uid
* @return
*/
@Select("SELECT * FROM account WHERE uid = #{uid}")
List<Account> listAccountsByUid(Integer uid);
}
@Many
注解相当于标签<collection></collection>
,是多表查询的关键,在注解中用来指定子查询返回对象集合。
- 其中,
select
属性指定用于查询的接口方法,fetchType
属性用于指定立即加载或延迟加载,分别对应FetchType.EAGER
和FetchType.LAZY
- 在包含
@Many
注解的@Result
中,column
属性用于指定将要作为参数进行查询的数据库表列。
3.4 Mybatis 使用注解实现二级缓存
- 如果使用注解时想开启二级缓存,那么首先应该在 Mybatis 配置文件中开启全局配置
<settings>
<!-- 开启缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- 接着在持久层接口中使用注解即可
@CacheNamespace(blocking = true)
public interface UserMapper {
// .....
}