摘要

学习Mybatis的懒加载、缓存、以及注解开发。

正文

目录

  1. Mybatis的CRUD
  2. Mybatis的缓存及注解开发
  3. 源码

五、懒加载和缓存

5.1 加载(查询)时机

在一对多中,当我们有一个用户,他有100个账户。

在查询用户的时候,要不要把关联的账户查出来?

在查询账户的时候,要不要把关联的用户查出来?

在查询用户的时候,用户的账户信息,什么时候使用,什么时候进行查询,这种的叫做延迟加载,也叫懒加载

在查询账户的时候,账户的所属用户应该是随着账户查询时,一起查询出来,这种的叫做立即加载

通常情况

  • 一对多,多对多:懒加载
  • 多对一,一对一:立即加载

第五章以前的例子,全部都是立即加载,下面介绍懒加载

association懒加载

查询账户时,将所属用户一并查出来,属于一对一。

AccountDao.xml

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?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="top.meethigher.demo10.dao.AccountDao">
    <resultMap id="accountUserMap" type="Account">
        <id property="aid" column="aid"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- column指定的内容就是account中的uid,用来作为findById的参数进行查询
        select指定查询用户的唯一标识,也就是用户配置文件中select的id-->
        <association property="user" column="uid" javaType="user" select="top.meethigher.demo10.dao.UserDao.findById"></association>
    </resultMap>
    <select id="findAll" resultMap="accountUserMap">
        select * from account;
    </select>
</mapper>

UserDao.xml

xml
1
2
3
4
5
6
7
8
9
<?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="top.meethigher.demo10.dao.UserDao">
    <select id="findById" resultType="User" parameterType="Integer">
        select * from user where uid=#{id};
    </select>
</mapper>

同时要在主配置文件中开启懒加载,该属性可以在上面5.1中,找官网链接进去看。

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?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"/>
        <!--关闭立即加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    
    <typeAliases></typeAliases>
    <environments default="mysql"></environments>
    <mappers></mappers>
</configuration>

运行结果

25.png

collection懒加载

查询用户时,对账户列表进行懒加载,属于一对多。

UserDao.xml

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?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="top.meethigher.demo10.dao.UserDao">
    <resultMap id="userAccountMap" type="User">
        <id property="uid" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <result property="address" column="address"></result>
        <collection property="accounts" column="uid" ofType="Account" select="top.meethigher.demo10.dao.AccountDao.findById"></collection>
    </resultMap>
    <select id="findAll" resultMap="userAccountMap">
        select * from user;
    </select>
</mapper>

AccountDao.xml

xml
1
2
3
4
5
6
7
8
9
<?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="top.meethigher.demo10.dao.AccountDao">
    <select id="findById" parameterType="Integer" resultType="Account">
        select * from account where uid=#{uid};
    </select>
</mapper>

总结

实现原理就是在使用到对象时,就会去resultMap中,找到其对应的配置,进行查询

注意association中使用javaType,collection中使用ofType

5.2 缓存

存在于内存中的临时数据

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

适用缓存的数据

  1. 经常查询的数据
  2. 不经常改变
  3. 数据正确与否对最终结果影响不大的

不适用缓存的数据

  1. 经常改变的数据
  2. 数据的正确与否对最终结果影响很大的(比如商品库存、银行汇率、股市牌价)

一级缓存

  • 概念:mybatis中SqlSession对象的缓存

    • 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。
    • 当我们再次查询同样的数据,mybatis会先去SqlSession中查看,有则读取,无则查询数据库
  • 清除一级缓存

    • 当SqlSession执行**close()**时,关闭SqlSession,mybatis的一级缓存就消失了。
    • 当SqlSession执行**clearCache()**时,也能够清除缓存。
  • 触发一级缓存的清空

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

测试一级缓存

java
 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
public class CacheTest {
    private InputStream is;
    private SqlSession sqlSession;
    private UserDao userDao;
    private SqlSessionFactory factory;

    @Before //用于在测试方法之前执行
    public void init() {
        //1.读取配置文件,获取输入流
        try {
            is = Resources.getResourceAsStream("Mybatis.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2.获取SqlSessionFactory对象
        factory = new SqlSessionFactoryBuilder().build(is);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取代理dao对象
        userDao= sqlSession.getMapper(UserDao.class);
    }

    @After //用于在测试方法之后执行
    public void destroy() throws IOException {
        sqlSession.commit();
        if (sqlSession != null)
            sqlSession.close();
        if (is != null)
            is.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
        User byId = userDao.findById(1);
        System.out.println(byId);//top.meethigher.demo11.domain.User@79defdc
        User byId1 = userDao.findById(1);
        System.out.println(byId1);//top.meethigher.demo11.domain.User@79defdc
        //通过debug可知,实际上只查询了一次
        System.out.println(byId==byId1);//true
        //清除缓存法一:关闭并重新开启一个SqlSession
//        sqlSession.close();
//        sqlSession=factory.openSession();

        //清除缓存法二
        sqlSession.clearCache();

        userDao=sqlSession.getMapper(UserDao.class);
        byId1=userDao.findById(1);
        System.out.println(byId1);//top.meethigher.demo11.domain.User@4b86805d
        System.out.println(byId==byId1);//false
    }
}

二级缓存

  • 概念:mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
  • 使用步骤
    1. 让mybatis框架,支持二级缓存。在主配置文件中配置,默认是开启的,具体看官网文档
    2. 让当前的映射文件,支持二级缓存。
    3. 让当前的操作支持二级缓存(在select标签中配置)

第一步,主配置文件

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>
        <package name="top.meethigher.demo11.domain"/>
    </typeAliases>
    <environments default="mysql">
    </environments>
    <mappers>
        <package name="top.meethigher.demo11.dao"/>
    </mappers>
</configuration>

第二步与第三步,映射配置文件

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?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="top.meethigher.demo11.dao.UserDao">
    <!--开启二级缓存-->
    <cache/>

    <!--在操作中开启二级缓存-->
    <select id="findById" resultType="User" parameterType="Integer" useCache="true">
        select * from user where uid=#{id};
    </select>
</mapper>

测试二级缓存

java
 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
public class SecondCacheTest {
    private InputStream in;
    private SqlSessionFactory factory;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("Mybatis.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);

    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        in.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testSecondLevelCache(){
        SqlSession sqlSession1 = factory.openSession();
        UserDao dao1 = sqlSession1.getMapper(UserDao.class);
        User user1 = dao1.findById(1);
        System.out.println(user1);//top.meethigher.demo11.domain.User@1f760b47
        sqlSession1.close();//一级缓存消失

        SqlSession sqlSession2 = factory.openSession();
        UserDao dao2 = sqlSession2.getMapper(UserDao.class);
        User user2 = dao2.findById(1);//这一次就不查询了,只查询第一次
        System.out.println(user2);//top.meethigher.demo11.domain.User@33f676f6
        sqlSession2.close();

        //因为二级缓存中存放的是数据,就是一串字符串,而不是对象。获取数据时,创建一个新的对象,将字符串填充进去,所以是false
        //如果不确定是否使用缓存,那就打开日志,查看是否又再次与数据库进行交互了。
        System.out.println(user1 == user2);//false
    }
}

注意

  1. 使用二级缓存,所缓存的类一定要实现java.io.Serializable接口,这样就可以使用序列化来保存对象
  2. 二级缓存有过期时间,缓存存活时间到达一定值,就会进行清理,参考

六、注解开发

6.1 单表CRUD

修改主配置文件

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
<?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>
    <!--指定实体类所在位置-->
    <typeAliases>
        <package name="top.meethigher.demo12.domain"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定注解的dao接口所在位置-->
    <mappers>
        <package name="top.meethigher.demo12.dao"/>
    </mappers>
</configuration>

注解有四个

  • select
  • update
  • insert
  • delete

当mybatis采用注解开发时,如果又配置了xml,那么项目就报错。

mybatis不知应该采用哪种方式进行加载。

所以在整个开发中,要不全中注解,要不全用xml

UserDao.java

java
 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
public interface UserDao {
    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAll();

    /**
     * 通过id查询
     *
     * @return
     */
    @Select("select * from user where uid=${id}")
    User findById(Integer id);

    /**
     * 保存
     *
     * @param user
     */
    @Insert("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})")
    void saveUser(User user);

    /**
     * 更新
     *
     * @param user
     */
    @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where uid=#{uid}")
    void updateUser(User user);

    @Delete("delete from user where uid=${id}")
    void deleteUser(Integer id);

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

    /**
     * 查询总条数
     *
     * @return
     */
    @Select("select count(*) from user")
    int findTotal();
}

测试

java
 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
public class MybatisTest12 {
    private InputStream is;
    private SqlSession sqlSession;
    private UserDao userDao;

    @Before //用于在测试方法之前执行
    public void init() {
        //1.读取配置文件,获取输入流
        try {
            is = Resources.getResourceAsStream("Mybatis.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2.获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取代理dao对象
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @After //用于在测试方法之后执行
    public void destroy() throws IOException {
        sqlSession.commit();
        if (sqlSession != null)
            sqlSession.close();
        if (is != null)
            is.close();
    }

    
    @Test
    public void testFindAll() {
        List<User> all = userDao.findAll();
        for (User u : all) {
            System.out.println(u);
        }
    }

    @Test
    public void testSaveUser() {
        User user = new User();
        user.setUsername("孙尚香");
        user.setAddress("王者荣耀");
        user.setSex("女");
        user.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        System.out.println(user);

        userDao.saveUser(user);
    }

    @Test
    public void testUpdateUser() {
        User user = new User();
        user.setUid(2);
        user.setAddress("腾讯");
        user.setUsername("肖战");
        userDao.updateUser(user);
    }

    @Test
    public void testDeleteUser() {
        userDao.deleteUser(4);
    }

    @Test
    public void testFindById() {
        User byId = userDao.findById(1);
        System.out.println(byId);
    }

    @Test
    public void testFindByName() {
        List<User> byName = userDao.findByName("%香%");
        System.out.println(byName);
    }


    @Test
    public void testFindTotal() {
        int total = userDao.findTotal();
        System.out.println(total);
    }
}

6.2 别名

如果实体类和数据库中的属性不一致,

在xml中,可以直接通过设置映射配置文件中的resultMap来设置别名。

在注解中,提供了@Results注解

  • id:map对应关系的id
  • value:对应关系

在注解中,也提供了@ResultMap用来引用对应关系

  • value:对应关系@Results的id,可以引用多个
java
 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
public interface UserDao {
    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     *
     * @return
     */
    @Select("select * from user")
    @Results(id="userMap",value={
            @Result(id=true,column = "uid",property = "uid"),
            @Result(column="username",property = "username"),
            @Result(column="birthday",property = "birthday"),
            @Result(column="sex",property = "sex"),
            @Result(column="address",property = "address"),
    })//设置ResultMap的id和对应关系
    List<User> findAll();

    /**
     * 通过id查询
     *
     * @return
     */
    @Select("select * from user where uid=${id}")
    @ResultMap("userMap")//省略写法
    User findById(Integer id);

    /**
     * 模糊查询
     *
     * @param name
     * @return
     */
    @Select("select * from user where username like #{name}")
    @ResultMap(value={"userMap"})//标准写法,value=是可以省略的,如果数组中只有一个元素,{}也可以省略
    List<User> findByName(String name);
}

注意

如果只是单次使用,value=其实是可以省略的。但一般不会,一般会设置id和value。

@Result中,id默认值为false,所以id=false可以省略。

6.3 多表查询

一对一、多对一

FetchType两个属性

  • EAGER:立即加载,通常对一的都是立即加载
  • LAZY:懒加载,通过对多的都是懒加载
java
 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
public interface AccountDao {

    /**
     * 查询所有Account,同时获取其所在的User信息
     *
     * @return
     */
    @Select("select * from account")
    @Results(id = "accountMap", value = {
            @Result(id = true, column = "aid", property = "aid"),
            @Result(column = "uid", property = "uid"),
            @Result(column = "money", property = "money"),
            @Result(column = "uid", property = "user", one = @One(select = "top.meethigher.demo13.dao.UserDao.findById", fetchType = FetchType.EAGER))
    })
    List<Account> findAll();

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    @Select("select * from account where uid=#{id}")
    Account findById(Integer id);

}

一对多

java
 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
public interface UserDao {
    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     *
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(id = true, column = "uid", property = "uid"),
            @Result(column = "username", property = "username"),
            @Result(column = "birthday", property = "birthday"),
            @Result(column = "sex", property = "sex"),
            @Result(column = "address", property = "address"),
            @Result(column = "uid", property = "accounts", many = @Many(select = "top.meethigher.demo13.dao.AccountDao.findById", fetchType = FetchType.LAZY))
    })
    List<User> findAll();

    /**
     * 通过id查询
     *
     * @return
     */
    @Select("select * from user where uid=${id}")
    @ResultMap("userMap")
    User findById(Integer id);
}

6.4 缓存配置

一级缓存其实是不用担心的,同一个sqlSession,基本就没问题

测试一级缓存

java
1
2
3
4
5
6
@Test
public void testFirstLevelCache(){
    User byId = userDao.findById(1);
    User byId1 = userDao.findById(1);
    System.out.println(byId==byId1);//true,一级缓存其实是不用担心的。
}

二级缓存

  • 开启全局缓存,配置文件,默认就是开启的,可以不用管
  • 通过注解开启dao接口缓存
java
1
2
3
4
@CacheNamespace(blocking = true)//开启该dao操作时的二级缓存
public interface UserDao {
    //... ...
}

测试二级缓存

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
public void testSecondLevelCache(){
    User byId = userDao.findById(1);
    System.out.println(byId);
    sqlSession.close();//释放一级缓存
    sqlSession = factory.openSession();
    userDao=sqlSession.getMapper(UserDao.class);
    User byId1 = userDao.findById(1);
    System.out.println(byId1);
    System.out.println(byId==byId1);
}