摘要
学习Mybatis的懒加载、缓存、以及注解开发。
正文
目录
- Mybatis的CRUD
- Mybatis的缓存及注解开发
- 源码
五、懒加载和缓存
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
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共享其缓存。
- 使用步骤
- 让mybatis框架,支持二级缓存。在主配置文件中配置,默认是开启的,具体看官网文档
- 让当前的映射文件,支持二级缓存。
- 让当前的操作支持二级缓存(在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
}
}
|
注意
- 使用二级缓存,所缓存的类一定要实现java.io.Serializable接口,这样就可以使用序列化来保存对象
- 二级缓存有过期时间,缓存存活时间到达一定值,就会进行清理,参考
六、注解开发
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&useUnicode=true&characterEncoding=UTF-8&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>
|
注解有四个
当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注解
在注解中,也提供了@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);
}
|