MyBatis的关联映射
实际开发中,对数据库的操作常常会设计多张表,这在面向对象中就设计了对象与对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好地处理对象与对象之间的关联关系。
关联关系概述
在关系型数据库中,多表之间存在三种关系,分别是一对一、一对多、多对多
一对一:在任意一方引入对方主键作为外键。
一对多:在"多"的一方,添加"一"的一方的主键作为外键。
多对多:产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。
通过数据库中的表可以描述数据之间的关系,同样,在Java中,通过对象也可以进行关系描述
一对一
class A{
B b;
}
class B{
A a;
}
一对多
class A{
List<B> b;
}
class B{
A a;
}
多对多
class A{
List<B> b;
}
class B{
List<A> a;
}
一对一
在现实生活中,一对一关联关系是十分常见的。例如,一个人只能有一个身份证,同时一个身份证也只会对应一个人。
MyBatis使用<resultMap>
元素中的<association>
子元素来处理一对一关联关系。
在<association>
元素中,通过可以配置以下属性。
1、property:指定映射到的实体类对象属性,与表字段一一对应。
2、column:指定表中对应的字段。
3、javaType:指定映射到实体对象属性的类型。
4、select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。
5、fetchType:指定在关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy(即默认关联映射延迟加载)。
<association>
元素的使用非常简单
<association property="card" column="card_id"
javaType="com.ex.po.IdCard"
select="com.ex.mapper.IdCardMapper.findCodeById"/>
<association property="card" column="card_id">
<id property="id" colum="card_id"/>
<result property="code" column="code"/>
</association>
查询个人及其关联的身份证信息是先通过查询个人表中的主键来获个人信息,然后通过表中的外键,来获取证件表中的身份证号信息。
1)创建数据表。在mybatis数据库中分别创建名为tb_idcard和tb_person的数据表,同时预先插入两个数据
use mybatis;
create table tb_idcard(
id int primary key auto_increment,
code varchar(18)
);
insert into tb_idcard(code)values('152221198711020624');
insert into tb_idcard(code)values('152201199008150317');
create table tb_person(
id int primary key auto_increment,
name varchar(32),
age int,
sex varchar(8),
card_id int unique,
foreign key(card_id) references tb_idcard(id)
);
insert into tb_person(name,age,sex,card_id) values('Rose',29,'女',1);
insert into tb_person(name,age,sex,card_id) values('tom',27,'男',2);
注:如果产生编码错误,使用命令更改编码格式set names 'gbk'
2)在Eclipse中创建一个名为chapter09的Web项目,然后引入相关JAR包、log4j日志文件、MybatisUtils工具类以及mybatis-config.xml核心配置文件。
3)在项目的com.ex.po包中创建持久化类IdCard和Person
IdCard.java
package com.ex.po;
public class IdCard {
private Integer id;
private String code;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString(){
return "IdCard [id="+id+", code="+code+"]";
}
}
4)com.ex.mapper包中,创建证件映射文件IdCardMapper.xml和个人映射文件PersonMapper.xml,并在两个映射文件中编写一对一关联映射查询的配置信息
IdCardMapper.xml
<?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="com.ex.mapper.UserMapper">
<select id="findCodeById" parameterType="Integer" resultType="IdCard">
select * from tb_idcard where id=#{id}
</select>
</mapper>
PersonMapper.xml
<?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="com.ex.mapper.PersonMapper">
<select id="findCodeById" parameterType="Integer" resultType="IdCardWithPersonResult">
select * from tb_idcard where id=#{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<association property="card" column="card_id" javaType="IdCard"
select="com.ex.mapper.IdCardMapper.findCodeById"/>
</resultMap>
</mapper>
在上述两个映射文件中,使用了MyBatis中的嵌套查询方式进行了个人及其关联的证件信息查询,因为返回的个人对象中除了基本属性外还有一个关联的card属性,所以需要手动编写结果映射。从映射文件PersonMapper.xml中可以看出,嵌套查询的方法是限制性一个简单的SQL语句,然后在进行结束映射时,将关联对象在<association>
元素中使用select属性执行另一条SQL语句。
5)在核心配置文件mybatis-config.xml中,引入Mapper映射文件并定义别名。
<?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="db.properties"/>
<typeAliases>
<package name="com.ex.po"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/ex/mapper/IdCardMapper.xml"/>
<mapper resource="com/ex/mapper/PersonMapper.xml"/>
</mappers>
</configuration>
6)在test包中,创建测试类MybatisAssociatedTest,并在类中编写测试方法
package com.ex.test;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.ex.po.*;
import com.ex.utils.*;
public class MybatisAssociatedTest {
@Test
public void findPersonByIdTest(){
SqlSession session=MybatisUtils.getSession();
Person person=session.selectOne("com.ex.mapper."
+ "PersonMapper.findPersonById", 1);
System.out.println(person);
session.close();
}
}
虽然使用嵌套查询的方式比较简单,MyBatis嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这些可能会导致成百上千条关联的SQL语句被执行,从而极大的消耗数据库性能并会降低查询效率。为此,我们可以使用MyBatis提供的嵌套结果方式,来进行关联查询。
在PersonMapper.xml
<?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="com.ex.mapper.PersonMapper">
<select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult">
select * from tb_person where id=#{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<association property="card" javaType="IdCard">
<id property="id" column="card_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
</mapper>
可以看到MyBatis只执行了一条复杂的SQL。
MyBatis延迟加载的配置
在使用MyBatis嵌套查询方式进行Mybatis关联查询映射时,使用延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在核心配置文件mybatis-config.xml中的<settings>
元素内进行配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
MyBatis关联映射的<association>
元素和<collection>
元素中都已默认配置了延迟加载属性,即默认属性fetchType=“lazy”(fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。
package com.ex.test;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.ex.po.*;
import com.ex.utils.*;
public class MybatisAssociatedTest {
@Test
public void findPersonByIdTest(){
SqlSession session=MybatisUtils.getSession();
Person person=session.selectOne("com.ex.mapper."
+ "PersonMapper.findPersonById", 1);
System.out.println("访问嵌套外的属性name"+person.getName());
System.out.println("访问嵌套内的属性code"+person.getCard().getCode());
System.out.println(person);
session.close();
}
}
可以看到,只有当访问嵌套内内容的时候才会发送嵌套sql,这样可以有效的避免资源浪费,只请求需要的。
一对多
MyBtis映射文件<resultMap>
元素中的<collection>
子元素,用于处理一对多关系。
<collection>
子元素的属性大部分与<association>
元素相同,但其还包括一个特殊属性——offType。offType属性与javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型。
<collection>
元素的使用也非常简单
<!--方式一:嵌套查询-->
<collection property="ordersList" column="id"
ofType="com.ex.po.Orders"
select="com.ex.mapper.OrdersMapper.selectOrders"/>
<!--方式二:嵌套结果-->
<collection property="ordersList"ofType="com.ex.po.Orders">
<id property="id" column="orders_id" />
<result property="number" column="number"/>
</collection>
1)在数据库中创建两个数据表,分别为tb_user和tb_orders
create table tb_user(
id int(32) primary key auto_increment,
username varchar(32),
address varchar(256)
);
insert into tb_user values('1','詹姆斯','克利夫兰');
insert into tb_user values('2','科比','洛杉矶');
insert into tb_user values('3','保罗','洛杉矶');
create table tb_orders(
id int(32) primary key auto_increment,
number varchar(32) not null,
user_id int(32) not null,
foreign key(user_id) references tb_user(id)
);
insert into tb_orders values('1','1000011','1');
insert into tb_orders values('2','1000012','1');
insert into tb_orders values('3','1000013','2');
2)在po包中,创建持久化类Orders和User
Orders.java
package com.ex.po;
import java.util.List;
public class User {
private Integer id;
private String username;
private String address;
private List<Orders> ordersList;
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", address="
+ address + ", ordersList=" + ordersList + "]";
}
}
3)在mapper包中,创建用户实体映射文件UserMapper.xml
<?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="com.ex.mapper.UserMapper">
<select id="findUserWithOrders" parameterType="Integer"
resultMap="UserWithOrdersResult">
select u.*,o.id as orders_id,o.number
from tb_user u,tb_orders o
where u.id=o.user_id
and u.id=#{id}
</select>
<resultMap type="User" id="UserWithOrdersResult">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
</collection>
</resultMap>
</mapper>
4)将映射文件UserMapper.xml的路径配置到核心配置文件mybatis-config.xml中
<mapper resource="com/ex/mapper/UserMapper.xml"/>
5)在测试类中编写测试方法
@Test
public void findUserTest(){
SqlSession sqlSession=MybatisUtils.getSession();
User user =sqlSession.selectOne("com.ex.mapper."
+ "UserMapper.findUserWithOrders", 1);
System.out.println(user);
sqlSession.close();
}
6)修改映射文件,改为嵌套查询
<select id="selectOrders" parameterType="Integer" resultType="Orders">
select *
from tb_orders
where user_id=#{id}
</select>
<select id="findUserWithOrders_1" parameterType="Integer"
resultMap="UserWithOrdersResult_1">
select *
from tb_user
where id=#{id}
</select>
<resultMap type="User" id="UserWithOrdersResult_1">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<collection property="ordersList" column="id"
ofType="Orders" select="com.ex.mapper.UserMapper.selectOrders"/>
</resultMap>
多对多
1)创建数据表。
create table tb_product(
id int(32) primary key auto_increment,
name varchar(32),
price double
);
insert into tb_product values('1','Java基础入门','44.5');
insert into tb_product values('2','Java Web程序开发入门','38.5');
insert into tb_product values('3','SSM框架整合实战','50');
create table tb_ordersitem(
id int(32) primary key auto_increment,
orders_id int(32),
product_id int(32),
foreign key(orders_id) references tb_orders(id),
foreign key(product_id) references tb_product(id)
);
insert into tb_ordersitem values('1','1','1');
insert into tb_ordersitem values('2','1','3');
insert into tb_ordersitem values('3','3','3');
2)在po包中,创建持久化类Product
package com.ex.po;
import java.util.List;
public class Product {
private Integer id;
private String name;
private Double price;
private List<Orders> orders;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public List<Orders> getOrders() {
return orders;
}
public void setOrders(List<Orders> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + ", price=" + price
+ ", orders=" + orders + "]";
}
}
在Orders.java中添加
private List<Product> productList;
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
@Override
public String toString() {
return "Orders [id=" + id + ", number=" + number + ", productList="
+ productList + "]";
}
3)在mapper包中,创建OrdersMapper.xml和ProductMapper.xml
OrdersMapper.xml
<?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="com.ex.mapper.OrdersMapper">
<select id="findOrdersWithProduct" parameterType="Integer"
resultMap="OrdersWithProductResult">
select * from tb_orders where id=#{id}
</select>
<resultMap type="Orders" id="OrdersWithProductResult">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="Product"
select="com.ex.mapper.ProductMapper.findProductById"/>
</resultMap>
</mapper>
ProductMapper.xml
<?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="com.ex.mapper.ProductMapper">
<select id="findProductById" parameterType="Integer"
resultType="Product">
select *
from tb_product
where id in (
select product_id
from tb_ordersitem
where orders_id=#{id}
)
</select>
</mapper>
4)将新创建的映射文件配置到核心配置文件
<mapper resource="com/ex/mapper/OrdersMapper.xml"/>
<mapper resource="com/ex/mapper/ProductMapper.xml"/>
5)编写测试类
@Test
public void findOrdersTest(){
SqlSession sqlSession=MybatisUtils.getSession();
Orders orders =sqlSession.selectOne("com.ex.mapper."
+ "OrdersMapper.findOrdersWithProduct", 1);
System.out.println(orders);
}
6)改为关联查询
OrdersMapper.xml
<select id="findOrdersWithProduct_1" parameterType="Integer"
resultMap="OrdersWithProductResult_1">
select o.*,p.id as pid,p.name,p.price
from tb_orders o,tb_product p,tb_ordersitem oi
where oi.orders_id=o.id
and oi.product_id=p.id
and o.id=#{id}
</select>
<resultMap type="Orders" id="OrdersWithProductResult_1">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" ofType="Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>