言成言成啊 | Kit Chen's Blog

Mybatis缓存和注解开发

发布于2021-02-16 03:23:12,更新于2021-02-17 04:23:40,标签:sql mybatis  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

目录

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

五、懒加载和缓存

5.1 加载(查询)时机

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

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

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

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

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

通常情况

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

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

association懒加载

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

AccountDao.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

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中,找官网链接进去看。

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>

运行结果

collection懒加载

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

UserDao.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

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()等方法时,就会清空一级缓存

测试一级缓存

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标签中配置)

第一步,主配置文件

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>

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

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>

测试二级缓存

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

修改主配置文件

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

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();
}

测试

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,可以引用多个
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:懒加载,通过对多的都是懒加载
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);

}

一对多

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,基本就没问题

测试一级缓存

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接口缓存
1
2
3
4
@CacheNamespace(blocking = true)//开启该dao操作时的二级缓存
public interface UserDao {
//... ...
}

测试二级缓存

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);
}
发布:2021-02-16 03:23:12
修改:2021-02-17 04:23:40
链接:https://meethigher.top/blog/2021/mybatis-2/
标签:sql mybatis 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏