Mybatis学习笔记04

Mybatis 中的延迟加载

问题:

在一对多中,当我们有一个用户,它有100个账户。在查询用户的时候,要不要把关联的账户查出来?在查询账户的时候,要不要把关联的用户查出来?

在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询

在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来的

  • 延迟加载

    在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)

  • 立即加载

    不管用不用,只要一调用方法,马上发起查询

在对应的四种表关系中:一对多,多对一,一对一,多对多

  • 一对多,多对多(关联的对象是多):通常情况下我们都是采用延迟加载。
  • 多对一,一对一(关联的对象是一):通常情况下我们都是采用立即加载

association、collection 具备延迟加载功能

使用 assocation 实现延迟加载

需求:

查询账户信息的同时查询用户信息(一对一)

账户的持久层 DAO 接口

src\main\java\com\conv\dao\IAccountDao.java

1
2
3
4
5
6
7
8
9
10
package com.conv.dao;

public interface IAccountDao {

/**
* 查询所有账户,同时还要获取到当前账户的所属用户信息
* @return
*/
List<Account> findAll();
}

账户的持久层映射文件

src\main\resources\com\conv\dao\IAccountDao.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
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IAccountDao">

<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 一对一的关系映射,配置封装user的内容 -->
<!-- javaType用于提示封装到那个对象(好像删了也没事) -->
<!-- select属性指定的内容,查询用户的唯一标识 -->
<!-- column属性指定的内容,用户根据id查询时,所需要的参数的值,可自行命名 -->
<association property="user" column="uid" javaType="user" select="com.conv.dao.IUserDao.findById"/>
</resultMap>

<!--配置查询所有-->
<!--id是dao的方法名称,resultMap是resultMap的id-->
<select id="findAll" resultMap="accountUserMap">
select * from account
</select>

</mapper>

用户的持久层接口

src\main\java\com\conv\dao\IUserDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.conv.dao;

public interface IUserDao {

/**
* 查询所有用户
*
* @return
*/
List<User> findAll();

/**
* 根据id查询用户信息
*
* @param userId
* @return
*/
User findById(Integer userId);
}

用户的持久层映射文件

src\main\resources\com\conv\dao\IUserDao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IUserDao">
<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultType="com.conv.domain.User">
select * from user u left outer join account a on u.id = a.uid
</select>

<!--根据id查询用户-->
<select id="findById" parameterType="Int" resultType="com.conv.domain.User">
select * from user where id = #{uid}
</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
package com.conv.test;

public class AccountTest {
private InputStream in;
private SqlSession session;
IAccountDao accountDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
accountDao = session.getMapper(IAccountDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

/**
* 因为本次只是将Account对象查询出来放入List集合中,并没有涉及到User对象,
* 所以就没有 发出 SQL 语句查询账户所关联的 User 对象的查询
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
}
}

使用 Collection 实现延迟加载

同样我们也可以在一对多关系配置的 <collection> 结点中配置延迟加载策略。

<collection> 结点中也有 select 属性,column 属性。

需求:

完成加载用户对象时,查询该用户所拥有的账户信息。(一对多)

在 User 实体类中加入 List<Account>属性

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
package com.conv.domain;

public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

// 一对多关系映射:主表实体应该包含从表实体的集合引用
private List<Account> accounts;

public List<Account> getAccounts() {
return accounts;
}

public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}

账户的持久层 DAO 接口

src\main\java\com\conv\dao\IAccountDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.conv.dao;

public interface IAccountDao {

/**
* 查询所有账户,同时还要获取到当前账户的所属用户信息
* @return
*/
List<Account> findAll();

/**
* 根据用户 id 查询账户信息
* @param uid
* @return
*/
List<Account> findByUid(Integer uid);
}

编写用户持久层映射配置

src\main\resources\com\conv\dao\IUserDao.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
27
28
29
30
31
32
33
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IUserDao">
<resultMap id="userMap" type="user">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>

<!-- collection是用于建立一对多集合属性的对应关系
ofType用于指定集合元素的数据类型
select用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名)
column用于指定哪个字段的值作为条件查询
-->

<!-- collection标签主要用于加载关联的集合对象 -->
<!-- select属性用于指定查询account列表的sql语句,所以填写的是该sql映射的id -->
<!-- column属性用于指定select属性的参数来源,上面的参数来源于user的id列,所以就写id这个字段名 -->
<collection property="accounts" ofType="account"
select="com.conv.dao.IAccountDao.findByUid" column="id"/>
</resultMap>


<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultMap="userMap">
select * from user
</select>

</mapper>

编写账户持久层映射配置

1
2
3
4
<!-- 根据用户的 id 查询账户信息 -->
<select id="findByUid" resultType="account" parameterType="int">
select * from account where uid = #{uid}
</select>

测试只加载用户信息

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
package com.conv.test;

public class UserTest {
private InputStream in;
private SqlSession session;
IUserDao userDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

/**
* 测试查询所有
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
}

}

Mybatis 中的缓存

使用缓存可以减少和数据库的交互次数,提高执行效率。

  • 适用于缓存的:
    • 经常查询并且不经常改变的数据
    • 数据的正确与否对最终结果影响不大的数据

Mybatis 中缓存分为一级缓存,二级缓存。

一级缓存

它指的是 Mybatis 中 SqlSession 对象的缓存。

当我们执行查询之后,查询的结果会同时存入到 SqlSession 为我们提供一块区域中。

该区域的结构是一个Map。当我们再次查询同样的数据,Mybatis 会先去 Sqlsession 中查询是否有,有的话直接拿出来用。

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flushclose,它就存在。

分析:

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

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    @Test
public void testFirstCache() {
User user1 = userDao.findById(41);
System.out.println(user1);

/* 1. 使用 close 实现清空缓存 */
// session.close();
// //再次获取 Session 对象
// session = factory.openSession();

/* 2. 使用 clearCache 实现清空缓存 */
session.clearCache();

userDao = session.getMapper(IUserDao.class);

User user2 = userDao.findById(41);
System.out.println(user2);

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

二级缓存

它指的是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。

二级缓存的使用步骤:

  1. 让 Mybatis 框架支持二级缓存(在 SqlMapConfig.xml 中配置)
1
2
3
4
<!-- cacheEnabled 的默认值就是 true,所以不配置也行 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
  1. 让当前的映射文件支持二级缓存(在 IUserDao.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
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IUserDao">
<!-- 开启user支持二级缓存 -->
<!--<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值 -->
<cache/>

<!--配置查询所有-->
<select id="findAll" resultType="user">
select * from user
</select>

<!--根据id查询用户-->
<select id="findById" parameterType="Int" resultType="com.conv.domain.User" useCache="true">
select * from user where id = #{uid}
</select>

<!-- 更新用户信息 -->
<update id="updateUser" parameterType="user">
update user set username = #{username},address = #{address} where id = #{id}
</update>
</mapper>
  1. 让当前的操作支持二级缓存(在 select 标签中配置)

    UserDao.xml 映射文件中的 <select> 标签中设置 useCache=”true” 代表当前这个 statement 要使用 二级缓存,如果不使用二级缓存可以设置为 false。

    注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

  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
package com.conv.test;

public class SecondLevelCacheTest {
private InputStream in;
SqlSessionFactory factory;

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

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

@Test
public void testSecondCache() {
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close(); //一级缓存消失

SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();

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

经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二 次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。

值得注意的是,最终输出的结果是 false,这是因为在二级缓存中寸的并不是对象,而是一些散装数据,每次查询就用这些数据封装一个对象返回,所以两个对象是不同的。

Mybatis 中的注解开发

Mybatis 使用注解开发的方式可以减少编写 Mapper 映射文件。

mybatis 的常用注解说明

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与 @Result 一起使用,封装多个结果集

@ResultMap:实现引用 @Results 定义的封装

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

@SelectProvider:实现动态 SQL 映射

@CacheNamespace:实现注解二级缓存的使用

使用 Mybatis 注解实现单表 CRUD

单表的 CRUD 操作是最基本的操作,前面我们的学习都是基于 Mybaits 的映射文件来实现的

编写实体类

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
package com.conv.domain;

public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}

使用注解方式开发持久层接口

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
package com.conv.dao;

public interface IUserDao {

/**
* 查询所有用户
*
* @return
*/
@Select("select * from user")
List<User> findAll();

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

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

/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id = #{id}")
void deleteUser(Integer userId);

/**
* 根据ID查询用户
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
User findById(Integer userId);

/**
* 根据用户名模糊查询
* @param userName
* @return
*/
@Select("select * from user where username like #{username}")
// @Select("select * from user where username like '%${value}%'")
List<User> findUserByName(String userName);

/**
* 查询用户数量
* @return
*/
@Select("select count(id) from user")
int findTotal();
}

通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了。

编写 SqlMapConfig 配置文件

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
<?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>
<!-- 引入外部配置文件 -->
<properties resource="jdbcConfig.properties"/>

<!-- 使用 typeAliases 配置别名,它只能配置 domain 中类的别名 -->
<typeAliases>
<package name="com.conv.domain"/>
</typeAliases>

<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的基本信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!-- 指定带有注解的dao接口所在的位置 -->
<mappers>
<package name="com.conv.dao"/>
</mappers>
</configuration>

编写测试方法

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
package com.conv.test;

public class MybatisAnnoTest {
private InputStream in;
private SqlSession session;
SqlSessionFactory factory;
IUserDao userDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

@Test
@Ignore
public void testFindAll() {
List<User> users = userDao.findAll();
users.forEach(System.out::println);
}

@Test
@Ignore
public void testSaveUser() {
User user = new User();
user.setUsername("mybatis annotation");
user.setAddress("北京市昌平区");

userDao.saveUser(user);
}

@Test
@Ignore
public void testUpdateUser() {
User user = new User();
user.setUsername("mybatis annotation");
user.setAddress("北京市昌平区");
user.setId(86);
user.setSex("女");
user.setBirthday(new Date());

userDao.updateUser(user);
}

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

@Test
@Ignore
public void testFindOneUser() {
User user = userDao.findById(41);
System.out.println(user);
}

@Test
@Ignore
public void testFindByName(){
List<User> users = userDao.findUserByName("%王%");
users.forEach(System.out::println);
}

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

使用注解实现复杂关系映射(多表)开发及延迟加载

实现复杂关系映射之前我们可以在映射文件中通过配置 <resultMap> 来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。

复杂关系映射的注解说明

@Results 注解

  • 代替的是标签 <resultMap>
  • 该注解中可以使用单个 @Result 注解,也可以使用 @Result 集合 @Results({@Result(),@Result()})@Results(@Result())

@Result 注解

  • 代替了 <id> 标签和 <result> 标签
  • @Result 中 属性介绍:
    • id:是否是主键字段
    • column:数据库的列名
    • property:需要装配的属性名
    • one:需要使用的 @One 注解(@Result(one = @One)()))
    • many:需要使用的 @Many 注解(@Result(many = @many)()))

@One 注解(一对一)

  • 代替了 <assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

  • @One 注解属性介绍:

    • select 指定用来多表查询的 sqlmapper (全限定方法名)

    • fetchType 会覆盖全局的配置参数 lazyLoadingEnabled(配置延迟加载或立即加载)

    • 使用格式:

      @Result(column = " ", property = "", one = @One(select=""))

@Many 注解(多对一)

  • 代替了 <Collection> 标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。

  • 注意:聚集元素用来处理 ”一对多” 的关系。需要指定映射的 Java 实体类的属性,属性的 javaType (一般为 ArrayList)但是注解中可以不定义;

  • 使用格式:

    @Result(property = "", column = "", many = @Many(select=""))

需求:

一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

一对一关系映射:

加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

一对多关系映射:

查询用户信息时,也要查询他的账户列表。使用注解方式实现

Account 实体类加入 User

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
package com.conv.domain;

public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

// 多对一(mybatis中称为一对一),一个账户只能属于一个用户
private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}
}

User 实体类加入 List<Account>

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
package com.conv.domain;

public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;

//一对多关系映射,一个用户对应多个账户
private List<Account> accounts;

public List<Account> getAccounts() {
return accounts;
}

public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Date getUserBirthday() {
return userBirthday;
}

public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}

public String getUserSex() {
return userSex;
}

public void setUserSex(String userSex) {
this.userSex = userSex;
}

public String getUserAddress() {
return userAddress;
}

public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}

@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}
}

编写用户的持久层接口并使用注解配置

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
package com.conv.dao;

// 开启二级缓存
// 加 @CacheNamespace 注解
@CacheNamespace(blocking = true)
public interface IUserDao {
// 故意让数据库的列名和类中的属性名不对应
// 使用@Results注解可以使其一一对应

/**
* 对一的时候用立即加载
* 对多的时候用懒加载
* <p>
* 查询所有用户
*
* @return
* @Results 注解中的id属性可以自行指定,这样其他只用引入@ResultMap注解即可
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, column = "id", property = "userId"),
@Result(column = "username", property = "userName"),
@Result(column = "sex", property = "userSex"),
@Result(column = "address", property = "userAddress"),
@Result(column = "birthday", property = "userBirthday"),
@Result(column = "id", property = "accounts",
many = @Many(select = "com.conv.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();

/**
* 根据ID查询用户
*
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
@ResultMap(value = {"userMap"})
User findById(Integer userId);

/**
* 根据用户名模糊查询
*
* @param userName
* @return
*/
@Select("select * from user where username like #{username}")
@ResultMap(value = {"userMap"})
List<User> findUserByName(String userName);
}

添加账户的持久层接口并使用注解配置

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
package com.conv.dao;

public interface IAccountDao {

/**
* 查询所有账户,并且获取每个账户所属的用户信息
* 对一的时候用立即加载
* 对多的时候用懒加载
*
* @return
*/
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "uid", property = "uid"),
@Result(column = "money", property = "money"),
@Result(property = "user", column = "uid", one = @One(select = "com.conv.dao.IUserDao.findById", fetchType = FetchType.EAGER))
})
List<Account> findAll();

/**
* 根据用户id查询账户信息
* @param userId
* @return
*/
@Select("select * from account where uid = #{uid}")
List<Account> findAccountByUid(Integer userId);
}

测试一对一关联及延迟加载

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
package com.conv.test;

public class AccountTest {
private InputStream in;
private SqlSession session;
SqlSessionFactory factory;
IAccountDao accountDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
accountDao = session.getMapper(IAccountDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
// accounts.forEach(account -> {
// System.out.println("-----------每个账户的信息-------------");
// System.out.println(account);
// System.out.println(account.getUser());
// });
}
}

测试一对多关联

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
package com.conv.test;

@Ignore
public class UserTest {
private InputStream in;
private SqlSession session;
SqlSessionFactory factory;
IUserDao userDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

@Test
public void testFindAll() {
List<User> users = userDao.findAll();
users.forEach(user -> {
System.out.println("----------每个用户的信息-------------");
System.out.println(user);
System.out.println("账户信息:");
System.out.println(user.getAccounts());
});
}

@Test
public void testFindOneUser() {
User user = userDao.findById(41);
System.out.println(user);
}

@Test
public void testFindByName(){
List<User> users = userDao.findUserByName("%王%");
users.forEach(System.out::println);
}

}

mybatis 基于注解的二级缓存

在 SqlMapConfig 中开启二级缓存支持

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
<?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>
<!-- 引入外部配置文件 -->
<properties resource="jdbcConfig.properties"/>

<!-- 配置开启二级缓存 -->
<!-- 默认就是 true,可不配置 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

<!-- 使用 typeAliases 配置别名,它只能配置 domain 中类的别名 -->
<typeAliases>
<package name="com.conv.domain"/>
</typeAliases>

<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的基本信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!-- 指定带有注解的dao接口所在的位置 -->
<mappers>
<package name="com.conv.dao"/>
</mappers>
</configuration>

在持久层接口中使用注解配置二级缓存

1
2
3
4
5
6
7
8
9
package com.conv.dao;

// 开启二级缓存
// 加 @CacheNamespace 注解
@CacheNamespace(blocking = true)
public interface IUserDao {
//...
//...
}

测试

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
package com.conv.test;

public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;

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

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

@Test
public void testFindOne(){
//第一次打开
SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
User user = userDao.findById(41);
System.out.println(user);

session.close(); //释放一级缓存

//再次打开
SqlSession session1 = factory.openSession();
IUserDao userDao1 = session1.getMapper(IUserDao.class);
User user1 = userDao1.findById(41);
System.out.println(user1);

session1.close();
}
}

完结撒花~

坚持原创技术分享,感谢您的支持和鼓励!