11.2 注解的使用示例2 一对一关联关系

11.2 注解的使用示例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
E:\workspace_web2\MyAOneToOneTest
├─src
│ ├─db.properties
│ ├─domain
│ │ ├─Card.java
│ │ └─Person.java
│ ├─fractory
│ │ └─SqlSessionFratoryTools.java
│ ├─log4j.xml
│ ├─mapper
│ │ ├─CardMapper.java
│ │ └─PersonMapper.java
│ ├─mybatis-config.xml
│ ├─mybatis_tb_card_tb_person.sql
│ └─test
│ └─OneToOneTest.java
└─WebContent
├─META-INF
└─WEB-INF
└─lib
├─ant-1.9.6.jar
├─ant-launcher-1.9.6.jar
├─asm-5.2.jar
├─cglib-3.2.5.jar
├─commons-logging-1.2.jar
├─javassist-3.22.0-CR2.jar
├─log4j-1.2.17.jar
├─log4j-api-2.3.jar
├─log4j-core-2.3.jar
├─mybatis-3.4.5.jar
├─mysql-connector-java-5.1.44-bin.jar
├─ognl-3.1.15.jar
├─slf4j-api-1.7.25.jar
└─slf4j-log4j12-1.7.25.jar

创建数据库表

首先,给之前创建的mybatis数据库创建两个表tb_cardtb_person,并插入测试数据。SQL脚本如下使用方式:
保存成.sql文件,然后使用Navicat导入(设置编码格式utf-8,免得乱码).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建tb_card表
DROP TABLE IF EXISTS `tb_card`;
CREATE TABLE `tb_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(18) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 插入一条数据
INSERT INTO `tb_card` VALUES ('1', '43280119980091');
# 创建tb_person表
DROP TABLE IF EXISTS `tb_person`;
CREATE TABLE `tb_person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(18) NOT NULL,
`sex` varchar(18) NOT NULL,
`age` int(11) DEFAULT NULL,
`card_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `card_id` (`card_id`),
CONSTRAINT `tb_person_ibfk_1` FOREIGN KEY (`card_id`)
REFERENCES `tb_card` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 插入一条数据
INSERT INTO `tb_person` VALUES ('1', '小明', '男', '22', '1');

创建持久化类

Card.java

/MyAOneToOneTest/src/domain/Card.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package domain;
public class Card {
private Integer id;
private String code;
public Card()
{
// TODO Auto-generated constructor stub
}
public Card(Integer id, String code)
{
this.id = id;
this.code = code;
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "Card [id=" + id + ", code=" + code + "]";
}
}

Person.java

/MyAOneToOneTest/src/domain/Person.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package domain;
public class Person {
private Integer id;
private String name;
private String sex;
private Integer age;
private Card card;
public Person()
{
super();
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", card="
+ card + "]";
}
}

mapper接口

CardMapper.java

/MyAOneToOneTest/src/mapper/CardMapper.java
1
2
3
4
5
6
7
8
9
package mapper;

import org.apache.ibatis.annotations.Select;
import domain.Card;

public interface CardMapper {
@Select("select * from tb_card where id=#{id}")
Card selectCardById(Integer id);
}

PersonMapper.java

/MyAOneToOneTest/src/mapper/PersonMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package mapper;

import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;
import domain.Person;

public interface PersonMapper {
@Select("select * from tb_person where id=#{id}")
@Results({
@Result(column = "id",property = "id", id = true),
@Result(column = "name",property = "name"),
@Result(column = "sex",property = "sex"),
@Result(column = "age",property = "age"),
@Result(column = "card_id",property = "card",
one = @One(select = "mapper.CardMapper.selectCardById", fetchType = FetchType.LAZY)
)
})
Person selectPersonById(@Param("id") Integer id);
}

selectPersonById方法使用了@Select注解,其根据id查询对应的Person数据。因为需要将Person对应的Card数据也查询出来,所以PersonCard属性使用了一个@Result结果映射。column="card_id",property="card"表示PersonCard属性对应tb_person表的card_id列,one属性表示是一个一对一关联关系,
@one注解的select属性表示需要关联执行的SQL语句,
fetchType表示查询的类型是立即加载(EAGER)还是懒加载(LAZY)。

测试类

/MyAOneToOneTest/src/test/OneToOneTest.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
package test;

import org.apache.ibatis.session.SqlSession;
import domain.Person;
import fractory.SqlSessionFactoryTools;
import mapper.PersonMapper;

public class OneToOneTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryTools.getSqlSession();
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
// 执行查询
Person person = personMapper.selectPersonById(1);
// 设置懒加载,立即加载Card
testLazyUseCardNow(person);
// 设置懒加载,不立即加载Card
// testLazyUseCardLazy(person);
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 出错回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally {
// 关闭会话
if (sqlSession != null)
sqlSession.close();
}
}

/**
* 当设置为懒加载时,立即访问card成员变量的情况。
*
* @param person
*/
private static void testLazyUseCardNow(Person person) {
// 调用toString方法会立即加载Card
System.out.println(person.toString());
}

/**
* 当设置为懒加载时,不立即访问card成员变量的。
*
* @param person 查询数据库得到的对象
*/
private static void testLazyUseCardLazy(Person person) {
System.out.println(person.toStringSimple());
System.out.println("=================================");
System.out.println(person.getCard());
}
}

运行效果

运行OneToOneTest类的main方法,该方法通过SqlSessiongetMapper(Class<T> type)方法获得mapper接口的代理对象PersonMapper。调用PersonMapperselectPersonById方法时会执行该方法上的注解。需要注意的是,Person的一对一关联使用的注解@oneselect属性,要执行的SQL语句在CardMapper接口的selectCardById方法的注解中。控制台显示如下所示:

1
2
3
4
5
6
7
DEBUG [main] ==>  Preparing: select * from tb_person where id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
DEBUG [main] ==> Preparing: select * from tb_card where id=?
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
Person [id=1, name=小明, sex=男, age=22, card=Card [id=1, code=450101199701243862]]

可以看到,查询Peson信息时Person对应的Card对象也被查询出来了.
在上面PersonMapper接口中的设置的是懒加载。
由于Person类的toString方法里面有访问到card属性,所以会执行关联查询。

懒加载验证

如果把上面的testLazyUseCardNow(person);语句注释掉,打开testLazyUseCardLazy(person);语句。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryTools.getSqlSession();
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
// 执行查询
Person person = personMapper.selectPersonById(1);
// 设置懒加载,立即加载Card
// testLazyUseCardNow(person);
// 设置懒加载,不立即加载Card
testLazyUseCardLazy(person);
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 出错回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally {
// 关闭会话
if (sqlSession != null)
sqlSession.close();
}
}

则先执行一条SQL语句,取出Person,当需要访问Person类的card成员时,才会执行下一条SQL语句。

运行结果如下:

1
2
3
4
5
6
7
8
9
DEBUG [main] ==>  Preparing: select * from tb_person where id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
Person [id=1, name=小明, sex=男, age=22]
=================================
DEBUG [main] ==> Preparing: select * from tb_card where id=?
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
Card [id=1, code=450101199701243862]

这就是懒加载的效果。

立即加载验证

再PersonMapper接口中使用立即加载

public interface PersonMapper {
    @Select("select * from tb_person where id=#{id}")
    @Results({
        @Result(column = "id",property = "id", id = true),
        @Result(column = "name",property = "name"),
        @Result(column = "sex",property = "sex"),
        @Result(column = "age",property = "age"),
        @Result(column = "card_id",property = "card", 
            one = @One(select = "mapper.CardMapper.selectCardById", fetchType = FetchType.EAGER)
        ) 
    })
    Person selectPersonById(@Param("id") Integer id);
}

再次运行上面的测试类,运行结果如下:

1
2
3
4
5
6
7
8
9
DEBUG [main] ==>  Preparing: select * from tb_person where id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] ====> Preparing: select * from tb_card where id=?
DEBUG [main] ====> Parameters: 1(Integer)
DEBUG [main] <==== Total: 1
DEBUG [main] <== Total: 1
Person [id=1, name=小明, sex=男, age=22]
=================================
Card [id=1, code=450101199701243862]

可以看到在立即加载模式下,就算还没有访问Person类的card属性,也会执行关联查询,加载card内容。