《Mybatis使用手册》(第4篇 / 共5篇,持续更新),收藏 + 关注 不迷路,希望对小伙伴们有所帮助哦~
源码链接在文末 ↓ ↓ ↓
1、Mybatis延迟加载策略
1.1、延迟加载介绍
Mybatis延迟加载就是在用到数据时才进行加载,不需要使用时,则不加载数据。延迟加载也叫做懒加载。
- 优点
先进行单表查询,需要使用从表数据时再关联从表进行查询,提高了数据库的性能。
- 缺点
在进行大批量数据查询时,可能造成用户等待时间增长,降低用户体验。
1.1.1、延迟加载案例
在一对多的查询中,一个用户有多个账户。
查询用户时,需要使用账户信息时,再查询账户信息(延迟加载)。
查询账户时,该账户对应的用户信息应该和账户信息一起查询出来(立即加载)。
1.1.2、多表查询使用何种方式查询
一对多、多对多:一般采用延迟加载
多对一、一对一:一般采用立即加载
1.2、案例需求
账户(Account)和用户(User)的一对一(立即加载)和一对多查询(延迟加载)。
1.3、assocation延迟加载
1.3.1、开启Mybatis的延迟加载策略
在Mybatis的主配置文件种开启延迟加载策略
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<!-- aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,
即只要对这个类的任意操作都将完整加载整个类的所有属性即执行级联的SQL语句。 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
1.3.2、账户持久层接口
public interface AccountDao {
/**
* 查询所有账户
*
* @return 返回账户列表,包含账户的用户信息 User(立即加载)
*/
List<Account> findAll();
}
1.3.3、账户持久层映射文件
<mapper namespace="com.junlong.dao.AccountDao">
<!-- 建立对应关系 -->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- select: 需要调用的 select 映射的id -->
<!-- column: 需要传递给 select 映射的参数 -->
<!-- fetchType="eager" 表示立即加载 -->
<!-- 指定从表的引用实体属性 -->
<association
property="user"
javaType="user"
column="uid"
select="com.junlong.dao.UserDao.findById"
fetchType="eager"
/>
</resultMap>
<!-- 查询所有账户信息,立即加载该账户对应的用户信息 -->
<select id="findAll" resultMap="accountUserMap">
select *
from account
</select>
</mapper>
1.3.4、用户持久层接口
public interface UserDao {
/**
* 根据用户id查询用户
*
* @param id 用户id
* @return 返回该用户id对用的用户User
*/
User findById(Integer id);
}
1.3.5、用户持久层映射文件
<mapper namespace="com.junlong.dao.UserDao">
<!-- 根据用户id查询用户 -->
<select id="findById" resultType="user" parameterType="int">
select *
from user
where id = #{id}
</select>
</mapper>
1.3.6、测试查询账户信息及对应的用户信息(立即加载)
public class AccountTest {
private InputStream inputStream;
private SqlSession sqlSession;
private AccountDao accountDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
accountDao = sqlSession.getMapper(AccountDao.class);
}
@After
public void destroy() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
/**
* 测试查询所有账户信息,立即加载账户对应的用户信息
*/
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}
}
正如运行结果所示,立即加载会执行了查询用户的SQL。
1.4、collection延迟加载
1.4.1、开启mybatis的延迟加载策略
在Mybatis的主配置文件种开启延迟加载策略
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<!-- aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,即只要对这个类的任意操作都将完整加载整个类的所有属性即执行级联的SQL语句。 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
1.4.2、用户持久层接口
public interface UserDao {
/**
* 查询所有用户
*
* @return 返回用户列表,包含用户的账户信息 Account(延迟加载)
*/
List<User> findAll();
}
1.4.3、用户持久层映射文件
<mapper namespace="com.junlong.dao.UserDao">
<!-- 建立对应关系 -->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
-->
<!-- fetchType="lazy" 表示延迟加载 -->
<collection
property="accounts"
ofType="account"
column="id"
select="com.junlong.dao.AccountDao.findByUid"
fetchType="lazy"
/>
</resultMap>
<!-- 查询所有用户,延迟加载账户信息 -->
<select id="findAll" resultMap="userAccountMap">
select *
from user
</select>
</mapper>
1.4.4、账户持久层接口
public interface AccountDao {
/**
* 根据用户uid
*
* @param uid 用户id
* @return 返回该用户id的所有账户信息
*/
List<Account> findByUid(Integer uid);
}
1.4.5、账户持久层映射文件
<mapper namespace="com.junlong.dao.AccountDao">
<!-- 根据用户id查询所有账户 -->
<select id="findByUid" resultType="account" parameterType="int">
select *
from account
where UID = #{uid}
</select>
</mapper>
1.4.6、测试只查询用户,不查询账户信息(延迟加载)
public class UserTest {
private InputStream inputStream;
private SqlSession sqlSession;
private UserDao userDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
/**
* 测试查询所有用户,延迟加载用户的账户信息
*/
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user.getUsername());
}
}
}
如运行结果所示,并没有使用Account相关的信息,所以只查询了用户信息,和Account相关的SQL并未执行(延迟加载)。
2、Mybatis缓存
Mybatis提供了缓存策略,用来减少数据库的查询次数,以提高性能。
Mybatis的缓存分为 一级缓存 和 二级缓存。
2.1、一级缓存
一级缓存是 SqlSession 范围的缓存,调用SqlSession的修改、添加、删除、commit()、close()、clearCache()方法时,会清空一级缓存。
2.1.1、代码测试
- 用户持久层接口
public interface UserDao {
/**
* 根据用户id查询用户
*
* @param id 用户id
* @return 返回用户 User 对象
*/
User findById(int id);
/**
* 更新用户信息
*
* @param user 需要更新的用户 User 对象
*/
void updateUser(User user);
}
- 用户持久层映射文件
<mapper namespace="com.junlong.dao.UserDao">
<!-- 根据用户id查询用户 -->
<select id="findById" resultType="user" parameterType="int">
select *
from user
where id = #{id}
</select>
<!-- 更新用户信息 -->
<update id="updateUser" parameterType="user">
update user
set username = #{username},
address = #{address}
where id = #{id}
</update>
</mapper>
- 测试一级缓存
public class UserTest {
private InputStream inputStream;
private SqlSession sqlSession;
private UserDao userDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
/**
* 测试根据用户id查询用户
*/
@Test
public void test1stCache() {
User user1 = userDao.findById(48);
System.out.println("第一次查询的用户:" + user1);
// mybatis一级缓存是SqlSession级别的,只要 SqlSession 没有 close 或 commit 或 clearCache ,它就存在
// sqlSession.clearCache();
// sqlSession.commit();
// 当调用SqlSession的 修改,添加,删除,commit(),close() 等方法时,会清空一级缓存
// user1.setUsername("新增用户");
// user1.setAddress("石家庄");
// userDao.updateUser(user1);
User user2 = userDao.findById(48);
System.out.println("第二次查询的用户:" + user2);
System.out.println(user1 == user2);
}
}
如运行结果所示,mybatis第一次查询了数据库,第二次从一级缓存中拿到了查询结果(并没有查询数据库),两次查询的结果是同一个对象,提高了性能。
2.2、二级缓存
二级缓存是 Mapper 映射级别的缓存,二级缓存是跨 SqlSession 的,多个SqlSession共享二级缓存。
2.2.1、二级缓存的开启与关闭
- Mybatis主配置文件开启二级缓存支持
<!-- 开启二级缓存 -->
<settings>
<!-- cacheEnabled 默认取值为 true,可以省略不配 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- 配置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.junlong.dao.UserDao">
<!-- 开启二级缓存的支持,表示当前这个mapper映射将使用二级缓存,以mapper的namespace值作为区分 -->
<cache/>
</mapper>
- 配置statement的useCache属性
<!-- 根据用户id查询用户信息 -->
<!-- userCache="true" 表示当前statement需要使用二级缓存,如果不使用(每次查询都需要最近数据的查询),则设置为false -->
<select id="findById" parameterType="int" resultType="User" useCache="true">
select *
from user
where id = #{id}
</select>
2.2.2、代码测试
public class UserTest {
private InputStream inputStream;
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@After
public void destroy() throws IOException {
inputStream.close();
}
/**
* 测试二级缓存
*
* 注意:二级缓存在sqlSession关闭或者提交之后才会生效
*/
@Test
public void testFindById() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
User user1 = userDao1.findById(48);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
User user2 = userDao2.findById(48);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
}
如运行结果所示,第一次查询后,关闭了一级缓存,第二次查询并没有执行查询数据库的SQL,而是使用了二级缓存。两次查询的结果不是同一个对象,是因为二级缓存是使用序列化的方式来保存对象的,每一次查询都会产生一个新的对象。
2.2.3、二级缓存注意事项
- 二级缓存在sqlSession关闭或者提交之后才会生效;
- 所缓存的类一定要实现 java.io.Serializable 接口 ,这样才能使用序列化的方式保存对象。
3、源码下载
源码下载链接👉 点击直达下载地址
《Mybatis使用手册》持续更新中…
感谢小伙伴们的关注!收藏 + 关注 不迷路 ~
往期精彩内容
👉 Java小白学习手册 - SE