23

Mybatis延迟加载、缓存

 3 years ago
source link: http://www.cnblogs.com/sun-10387834/p/13656885.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、延迟加载

java项目目录结构

nyymqqj.png!mobile

1、一对一延迟加载

1)创建User和Account实体类(具体代码参见我的之前博客)

2)创建UserDao接口和AccountDao接口

UserDao接口:

fm6jErA.gif!mobile
 1 package sun.dao;
 2 
 3 import sun.domain.User;
 4 
 5 import java.util.List;
 6 
 7 public interface UserDao {
 8     /**
 9      * 查询所有用户和该用户名下的账户信息
10      * @return
11      */
12     List<User> findAll();
13 
14     /**
15      * 根据id查找用户信息
16      * @param id
17      * @return
18      */
19     User findUserById(Integer id);
20 
21 }
UserDao接口

AccountDao接口:

fm6jErA.gif!mobile
 1 package sun.dao;
 2 
 3 import sun.domain.Account;
 4 
 5 import java.util.List;
 6 
 7 /**
 8  * @Classname AccountDao
 9  * @Description TODO
10  * @Date 2020/9/11 13:13
11  * @Created by Administrator
12  */
13 public interface AccountDao {
14     /**
15      * 查询所有账户信息和该账户的所属者
16      */
17     List<Account> findAll();
18 
19     /**
20      * 根据id查找账户信息
21      * @param id
22      * @return
23      */
24     Account findAccountById(Integer id);
25 
26 }
AccountDao接口

3)创建AccountDao.xml映射配置文件

fm6jErA.gif!mobile
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 
 6 <mapper namespace="sun.dao.AccountDao">
 7     <!--封装Account类-->
 8     <resultMap id="accountUserMap" type="account">
 9         <id property="id" column="id"></id>
10         <result property="uid" column="uid"></result>
11         <result property="money" column="money"></result>
12       
13         <!--延迟加载-->
14         <association property="user" column="uid" javaType="user" select="sun.dao.UserDao.findUserById"></association>
15     </resultMap>
16 
17     <!--查询所有-->
18     <select id="findAll" resultMap="accountUserMap">
19         SELECT * from account
20     </select>
21 <?xml version="1.0" encoding="UTF-8"?>
22 <!DOCTYPE mapper
23         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
24         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
25 
26 <mapper namespace="sun.dao.AccountDao">
27     <!--封装Account类-->
28     <resultMap id="accountUserMap" type="account">
29         <id property="id" column="id"></id>
30         <result property="uid" column="uid"></result>
31         <result property="money" column="money"></result>
32 
33         <!--延迟加载 select表示延时加载时执行的操作-->
34         <association property="user" column="uid" javaType="user" select="sun.dao.UserDao.findUserById"></association>
35     </resultMap>
36 
37     <!--查询所有-->
38     <select id="findAll" resultMap="accountUserMap">
39         SELECT * from account
40     </select>
41     <!--根据id查询账户-->
42     <select id="findAccountById" resultType="account">
43         SELECT * from account where uid=#{id}
44     </select>
45 
46 </mapper>
47     <select id="findAccountById" resultType="account">
48         SELECT * from account where uid=#{id}
49     </select>
50 
51 </mapper>
52     <select id="findAccountById" resultType="account">
53         SELECT * from account where uid=#{id}
54     </select>
55 
56 </mapper>
AccountDao.xml

4)在主配置文件中配置延时加载设置

fm6jErA.gif!mobile
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE configuration
 3         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 
 6 
 7 <!--mybatis主配置文件-->
 8 <configuration>
 9     
10     <properties resource="jdbcConfig.properties"></properties>
11 
12     <!--设置延时加载-->
13     <settings>
14         <setting name="lazyLoadingEnabled" value="true"/>
15         <setting name="aggressiveLazyLoading" value="false"/>
16     </settings>
17 
18     <typeAliases>
19         <package name="sun.domain"></package>
20     </typeAliases>
21 
22     <!--配置环境-->
23     <environments default="mysql">
24         <!--配置mysql环境-->
25         <environment id="mysql">
26             <!--配置事务类型-->
27             <transactionManager type="JDBC"></transactionManager>
28             <!--配置数据库连接池-->
29             <dataSource type="POOLED">
30                 <!--配置连接数据库的四个基本信息-->
31                 <property name="driver" value="${jdbc.driver}"></property>
32                 <property name="url" value="${jdbc.url}"></property>
33                 <property name="username" value="${jdbc.username}"></property>
34                 <property name="password" value="${jdbc.password}"></property>
35             </dataSource>
36 
37 
38         </environment>
39     </environments>
40 
41     <!--指定映射配置文件位置-->
42     <mappers>
43         <!--<mapper resource="sun/dao/UserDao.xml"></mapper>-->
44 
45         <!--package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class-->
46         <package name="sun.dao"></package>
47     </mappers>
48 </configuration>
配置主配置文件延时加载设置

5)测试查询账户时延时加载用户是否生效

fm6jErA.gif!mobile
 1 package sun.test;
 2 
 3 
 4 import org.apache.ibatis.io.Resources;
 5 import org.apache.ibatis.session.SqlSession;
 6 import org.apache.ibatis.session.SqlSessionFactory;
 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 8 import org.junit.After;
 9 import org.junit.Before;
10 import org.junit.Test;
11 import sun.dao.AccountDao;
12 import sun.domain.Account;
13 
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.List;
17 
18 public class AccountTest {
19 
20     private InputStream in;
21     private SqlSession sqlSession;
22     private AccountDao accountDao;
23 
24     @Before
25     public void init() throws IOException {
26         // 读取配置文件
27         in = Resources.getResourceAsStream("SqlMapConfig.xml");
28         // 创建SqlSessionFactory
29         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
30         SqlSessionFactory factory = builder.build(in);
31         // 使用工厂生产sqlsession对象
32         sqlSession = factory.openSession();
33         // 使用sqlsession创建UserDao接口代理对象
34         accountDao = sqlSession.getMapper(AccountDao.class);
35     }
36 
37     @After
38     public void destory() throws IOException {
39         sqlSession.commit();
40         sqlSession.close();
41         in.close();
42     }
43 
44     @Test
45     public void findAllTest() {
46         // 使用代理对象执行方法
47         List<Account> all = accountDao.findAll();
48         for (Account account : all) {
49             System.out.println("----------------");
50             System.out.println(account);
51             System.out.println(account.getUser());
52         }
53     }
54 
55 }
测试类

测试结果:

QbMbiqE.png!mobile

2、一对多延时加载

1)同上

2)同上

3)创建UserDao.xml映射配置文件

fm6jErA.gif!mobile
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 
 6 <mapper namespace="sun.dao.UserDao">
 7     <resultMap id="userAccountMap" type="user">
 8         <id property="id" column="id"></id>
 9         <result property="username" column="username"></result>
10         <result property="sex" column="sex"></result>
11         <result property="birthday" column="birthday"></result>
12         <result property="address" column="address"></result>
13         <!--封装用户名下的账户信息-->
14         <!--延迟加载-->
15         <collection property="accounts" ofType="account" select="sun.dao.AccountDao.findAccountById" column="id"></collection>
16     </resultMap>
17 
18     <!--查询所有-->
19     <select id="findAll" resultMap="userAccountMap">
20         SELECT * from user
21     </select>
22 
23     <!--查询指定id用户-->
24     <select id="findUserById" resultType="user">
25         SELECT * from user where id=#{id}
26     </select>
27 </mapper>
UserDao.xml

4)同上

5)测试类

fm6jErA.gif!mobile
 1 package sun.test;
 2 
 3 
 4 import org.apache.ibatis.io.Resources;
 5 import org.apache.ibatis.session.SqlSession;
 6 import org.apache.ibatis.session.SqlSessionFactory;
 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 8 import org.junit.After;
 9 import org.junit.Before;
10 import org.junit.Test;
11 import sun.dao.UserDao;
12 import sun.domain.User;
13 
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.Date;
17 import java.util.List;
18 
19 public class UserTest {
20 
21     private InputStream in;
22     private SqlSession sqlSession;
23     private UserDao userDao;
24 
25     @Before
26     public void init() throws IOException {
27         // 读取配置文件
28         in = Resources.getResourceAsStream("SqlMapConfig.xml");
29         // 创建SqlSessionFactory
30         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
31         SqlSessionFactory factory = builder.build(in);
32         // 使用工厂生产sqlsession对象
33         sqlSession = factory.openSession();
34         // 使用sqlsession创建UserDao接口代理对象
35         userDao = sqlSession.getMapper(UserDao.class);
36     }
37 
38     @After
39     public void destory() throws IOException {
40         sqlSession.commit();
41         sqlSession.close();
42         in.close();
43     }
44 
45     @Test
46     public void findAllTest() {
47         // 使用代理对象执行方法
48         List<User> all = userDao.findAll();
49         for (User user : all) {
50             System.out.println("---------------");
51             System.out.println(user);
52             System.out.println(user.getAccounts());
53         }
54     }
55 
56 }
测试类

测试结果:

jiyiMjQ.png!mobile

二、缓存

java项目目录结构

3AVZb2Z.png!mobile

1、一级缓存

概述:一级缓存指的是sqlSession对象的缓存。当第一次查询之后,查询结果会同时存入sqlSession为我们提供的一块区域中,该区域的结构是一个HashMap集合,当我们再次查询相同数据时,mybatis会先去该区域查询是否存在,如果存在直接获取。当sqlSession对象消失,一级缓存也就不存在了。一级缓存默认是开启的。

原理图:

yUziYnM.png!mobile

1)在domain包下创建User实体类

fm6jErA.gif!mobile
 1 package sun.domain;
 2 
 3 import java.io.Serializable;
 4 import java.util.Date;
 5 import java.util.List;
 6 
 7 public class User implements Serializable {
 8     private Integer id;
 9     private String username;
10     private Date birthday;
11     private String sex;
12     private String address;
13 
14     public Integer getId() {
15         return id;
16     }
17 
18     public void setId(Integer id) {
19         this.id = id;
20     }
21 
22     public String getUsername() {
23         return username;
24     }
25 
26     public void setUsername(String username) {
27         this.username = username;
28     }
29 
30     public Date getBirthday() {
31         return birthday;
32     }
33 
34     public void setBirthday(Date birthday) {
35         this.birthday = birthday;
36     }
37 
38     public String getSex() {
39         return sex;
40     }
41 
42     public void setSex(String sex) {
43         this.sex = sex;
44     }
45 
46     public String getAddress() {
47         return address;
48     }
49 
50     public void setAddress(String address) {
51         this.address = address;
52     }
53 
54 }
User实体类

2)在dao包下创建UserDao接口

fm6jErA.gif!mobile
 1 package sun.dao;
 2 
 3 import sun.domain.User;
 4 
 5 import java.util.List;
 6 
 7 public interface UserDao {
 8     /**
 9      * 查询所有用户和该用户名下的账户信息
10      * @return
11      */
12     List<User> findAll();
13 
14     /**
15      * 根据id查询用户
16      * @param id
17      * @return
18      */
19     User findUserById(Integer id);
20 
21 }
UserDao接口

3)测试进行相同的两次查询时获取对象是否是同一个

fm6jErA.gif!mobile
 1 package sun.test;
 2 
 3 
 4 import org.apache.ibatis.io.Resources;
 5 import org.apache.ibatis.session.SqlSession;
 6 import org.apache.ibatis.session.SqlSessionFactory;
 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 8 import org.junit.After;
 9 import org.junit.Before;
10 import org.junit.Test;
11 import sun.dao.UserDao;
12 import sun.domain.User;
13 
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.Date;
17 import java.util.List;
18 
19 public class firstLevelCacheTest {
20 
21     private InputStream in;
22     private SqlSession sqlSession;
23     private UserDao userDao;
24 
25     @Before
26     public void init() throws IOException {
27         // 读取配置文件
28         in = Resources.getResourceAsStream("SqlMapConfig.xml");
29         // 创建SqlSessionFactory
30         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
31         SqlSessionFactory factory = builder.build(in);
32         // 使用工厂生产sqlsession对象
33         sqlSession = factory.openSession();
34         // 使用sqlsession创建UserDao接口代理对象
35         userDao = sqlSession.getMapper(UserDao.class);
36     }
37 
38     @After
39     public void destory() throws IOException {
40         sqlSession.commit();
41         sqlSession.close();
42         in.close();
43     }
44 
45     @Test
46     public void findOneTest(){
47         User user1 = userDao.findUserById(41);
48         System.out.println(user1);
49         User user2 = userDao.findUserById(41);
50         System.out.println(user2);
51         System.out.println(user1==user2);
52     }
53 
54 }
测试类

测试结果:

3MBVruf.png!mobile

4)调用sqlSession对象的clearCache方法清理一级缓存

fm6jErA.gif!mobile
 1 package sun.test;
 2 
 3 
 4 import org.apache.ibatis.io.Resources;
 5 import org.apache.ibatis.session.SqlSession;
 6 import org.apache.ibatis.session.SqlSessionFactory;
 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 8 import org.junit.After;
 9 import org.junit.Before;
10 import org.junit.Test;
11 import sun.dao.UserDao;
12 import sun.domain.User;
13 
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.Date;
17 import java.util.List;
18 
19 public class firstLevelCacheTest {
20 
21     private InputStream in;
22     private SqlSession sqlSession;
23     private UserDao userDao;
24 
25     @Before
26     public void init() throws IOException {
27         // 读取配置文件
28         in = Resources.getResourceAsStream("SqlMapConfig.xml");
29         // 创建SqlSessionFactory
30         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
31         SqlSessionFactory factory = builder.build(in);
32         // 使用工厂生产sqlsession对象
33         sqlSession = factory.openSession();
34         // 使用sqlsession创建UserDao接口代理对象
35         userDao = sqlSession.getMapper(UserDao.class);
36     }
37 
38     @After
39     public void destory() throws IOException {
40         sqlSession.commit();
41         sqlSession.close();
42         in.close();
43     }
44 
45     @Test
46     public void findOneTest(){
47         User user1 = userDao.findUserById(41);
48         System.out.println(user1);
49         User user2 = userDao.findUserById(41);
50         System.out.println(user2);
51         System.out.println(user1==user2);
52     }
53 
54     @Test
55     public void findOneClearCacheTest(){
56         User user1 = userDao.findUserById(41);
57         System.out.println(user1);
58 //        调用sqlSession对象的clearCache方法清理一级缓存
59         sqlSession.clearCache();
60         User user2 = userDao.findUserById(41);
61         System.out.println(user2);
62         System.out.println(user1==user2);
63     }
64 
65 }
清理一级缓存

测试结果:

vU7fQjv.png!mobile

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

2、二级缓存

概述:指的是mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession对象共享该缓存。

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

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

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

<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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>

3)配置 statement 上面的 useCache 属性

<!-- 根据 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,禁用二级缓存。

4)测试

fm6jErA.gif!mobile
package sun.test;


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.After;
import org.junit.Before;
import org.junit.Test;
import sun.dao.UserDao;
import sun.domain.User;

import java.io.IOException;
import java.io.InputStream;

public class secondLevelCacheTest {

    private InputStream in;
    private SqlSessionFactory factory;

    @Before
    public void init() throws IOException {
        // 读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 创建SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);

    }

    @After
    public void destory() throws IOException {
        in.close();
    }

    @Test
    public void findOneTest(){
        SqlSession sqlSession1 = factory.openSession();
        UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
        User user1 = userDao1.findUserById(41);
        System.out.println(user1);
        sqlSession1.close(); // 一级缓存消失 二级缓存启动

        SqlSession sqlSession2 = factory.openSession();
        UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
        User user2 = userDao2.findUserById(41);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1==user2);
    }

}
View Code

z222Mrm.png!mobile

问题:虽然二级缓存已经生效,但是两次取出的结果为什么是false?

答:因为SqlSessionFactory中的二级缓存存储的是数据信息,不是对象信息。因此每次从缓存中获取数据都会重新封装成新的对象。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK