10.1 MyBatis关联映射
什么是关联关系
客观世界中的对象很少有孤立存在的,例如班级,往往与班级的学生存在关联关系,如果得到某个班级的实例,那么应该可以直接获取班级对应的全部学生。反过来,如果已经得到一个学生的实例,那么也应该可以访问该学生对应的班级。这种实例之间的互相访问就是关联关系。
关联关系分类
关联关系是面向对象分析、面向对象设计最重要的知识,MyBatis
完全可以理解这种关联关系,如果映射得当,MyBatis
的关联映射将可以大大简化持久层数据的访问。关联关系大致有如下分类。
10.1.1 一对一联系
在实际项目开发中,经常存在一对一关系,比如一个人只能有一个身份证,一个身份证只能给一个人使用,这就是一对一的关系。
一对一关系推荐使用唯一主外键关联,即两张表使用外键关联关系,由于是一对一关联,因此还需要给外键列增加unique
唯一约束。下面我们就用一个示例来看看MyBatis
怎么处理一对一关系。
示例: OneToOneTest
项目结构
展开/折叠
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\10\OneToOneTest
├─lib\
│ └─mysql-connector-java-5.1.44-bin.jar
├─src\
│ ├─db.properties
│ ├─log4j.xml
│ ├─mybatis-config.xml
│ └─org\
│ └─fkit\
│ ├─domain\
│ │ ├─Card.java
│ │ └─Person.java
│ ├─factory\
│ │ └─FKSqlSessionFactory.java
│ ├─mapper\
│ │ ├─CardMapper.xml
│ │ ├─PersonMapper.java
│ │ └─PersonMapper.xml
│ └─test\
│ ├─OneToOneTest.java
│ └─OneToOneTest2.java
└─WebContent\
├─META-INF\
│ └─MANIFEST.MF
└─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-8.0.17.jar
│ ├─ognl-3.1.15.jar
│ ├─slf4j-api-1.7.25.jar
│ └─slf4j-log4j12-1.7.25.jar
└─web.xml
创建数据库表
首先,给之前创建的mybatis
数据库创建两个表tb_card
和tb_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 25 26 27 28 29 30 31
| use mybatis;
SET FOREIGN_KEY_CHECKS=0;
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=utf8;
INSERT INTO `tb_card` VALUES ('1', '43280119980091');
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=utf8;
INSERT INTO `tb_person` VALUES ('1', '小明', '男', '22', '1');
|
tb_person
表的card_id
作为外键参照tb_card
表的主键id
,因为是一对一关系,即一个card_id
只能让一个person
使用,所以把card_id
做成了唯一键约束。如此一来,person
使用了一个card_id
之后,其他的person
就不能使用该card_id
了.
所以,一对一关系就是外键约束
加上唯一约束
在mybatis
数据库中执行SQL
脚本,完成创建数据库和表的操作。
创建持久化类
接下来,创建一个Card
对象和一个Person
对象分别映射tb_card
和tb_Person
表
Card.java
/OneToOneTest/src/org/fkit/domain/Card.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.fkit.domain; import java.io.Serializable; public class Card implements Serializable { private static final long serialVersionUID = 1164164518527094243L; private Integer id; private String code; public Card() { super(); } @Override public String toString() { return "Card [id=" + id + ", code=" + code + "]"; } }
|
Person.java
/OneToOneTest/src/org/fkit/domain/Person.java1 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
| package org.fkit.domain; import java.io.Serializable;
public class Person implements Serializable { private static final long serialVersionUID = -6685680170973264712L; private Integer id; private String name; private String sex; private Integer age; private Card card; public Person() { super(); } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]"; } }
|
人和身份证号码之间是一对一的关系,即一个人只有一个身份证。在Person
类中定义了一个card
属性,该属性是一个card
类型,用来映射一对一的关联关系,表示这个人的身份证。
XML映射文件
再接下来是XML
映射文件。
PersonMapper.xml
/OneToOneTest/src/org/fkit/mapper/PersonMapper.xml1 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 mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.fkit.mapper.PersonMapper"> <select id="selectPersonById" parameterType="int" resultMap="personMapper"> SELECT * from tb_person where id = #{id} </select> <resultMap type="org.fkit.domain.Person" id="personMapper"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> <association property="card" column="card_id" select="org.fkit.mapper.CardMapper.selectCardById" javaType="org.fkit.domain.Card"/> </resultMap> </mapper>
|
在PersonMapper.xml
中定义了一个select
标签,其根据id
查询Person
信息,由于Person
类除了简单的属性id
、name
、sex
和age
之外,还有一个关联对象card
,所以返回的是一个名为personMapper
的resultMap
。personMapper
中使用了association
元素映射一对一的关联关系,
select
属性表示会使用column
属性的card_id
值作为参数执行CardMapper.xml
中定义的selectCardById
查询对应的card
数据.
- 查询出的数据将被封装到
property
表示的card
对象当中.
CardMapper.xml
/OneToOneTest/src/org/fkit/mapper/CardMapper.xml1 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="org.fkit.mapper.CardMapper"> <select id="selectCardById" parameterType="int" resultType="org.fkit.domain.Card"> SELECT * from tb_card where id = #{id} </select> </mapper>
|
mapper接口
之前的测试都是使用SqlSession
对象调用insert
、update
、delete
和select
方法进行测试。实际上,Mybatis
官方手册建议通过mapper
接口的代理对象访问Mybatis
,该对象关联了SqlSession
对象,开发者可以通过mapper
接口的代理对象直接调用方法操作数据库。
mapper接口类命名规则
下面定义一个mapper
接口对象,需要注意的是:
mapper
接口的类名必须和之前的XML
文件中的mapper
的namespace一致,
mapper
接口的方法的方法名必须和XML
文件中的select
元素的id属性值一致
- 方法的参数名必须和
XML
文件中的select
元素的parameterType属性值一致。
PersonMapper.java
/OneToOneTest/src/org/fkit/mapper/PersonMapper.java1 2 3 4 5 6 7 8 9 10 11 12 13
| package org.fkit.mapper;
import org.fkit.domain.Person;
public interface PersonMapper {
Person selectPersonById(Integer id); }
|
测试 使用mapper接口 OneToOneTest.java
/OneToOneTest/src/org/fkit/test/OneToOneTest.java1 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
| package org.fkit.test;
import org.apache.ibatis.session.SqlSession; import org.fkit.domain.Person; import org.fkit.factory.FKSqlSessionFactory; import org.fkit.mapper.PersonMapper;
public class OneToOneTest {
public static void main(String[] args) { SqlSession sqlSession = null; try { sqlSession = FKSqlSessionFactory.getSqlSession(); PersonMapper pm = sqlSession.getMapper(PersonMapper.class); Person p = pm.selectPersonById(1); System.out.println(p); System.out.println(p.getCard()); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); e.printStackTrace(); } finally { if (sqlSession != null) sqlSession.close(); } }
}
|
运行OneToOneTest
类的main
方法,通过SqlSession
的getMapper(Class<T> type)
方法获得mapper
接口的代理对象PersonMapper
,调用selectPersonById
方法时会执行PersonMapper.xml
中id="selectPersonById"
的select
元素中定义的SQL
语句。控制台显示如下:
1 2 3 4 5 6 7 8
| 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=43280119980091]
|
可以看到,查询Person
信息时Person
对应的Card
对象也被查询出来了。
测试 使用mapper文件
/OneToOneTest/src/org/fkit/test/OneToOneTest2.java1 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
| package org.fkit.test;
import org.apache.ibatis.session.SqlSession; import org.fkit.domain.Person; import org.fkit.factory.FKSqlSessionFactory;
public class OneToOneTest2 { public static void main(String[] args) { SqlSession sqlSession = FKSqlSessionFactory.getSqlSession(); try { Person p = sqlSession.selectOne("org.fkit.mapper.PersonMapper.selectPersonById", 1); System.out.println(p); System.out.println(p.getCard()); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); sqlSession.rollback(); } finally { sqlSession.close(); } }
}
|
运行结果:
1 2 3 4 5 6 7 8
| 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=43280119980091]
|
可以看到,使用Mapper接口方式时,通过鼠标点击就可以跳转到要调用的方法,这种方式调试起来更方便。