MyBatis之多表关联查询操作
上篇文件借助一个案例介绍了MyBatis的CRUD操作,上述的CRUD操作都是单表操作,也就是在访问数据库时只是涉及到了一张表。在实际的生产环境中,达到一个查询目的通常需要综合多张表的信息,也是所谓的多表关联查询。本文首先介绍数据库中表与表之间的三种关系,接着介绍ORM框架中如何利用面向对象的思想体现表与表之间的三种关系以及Mybatis对表查询的支持。
一丶一对一关系的表
1.一对一表关系设置
一对一关系的表是多表查询中最简单的一种情况,那么以个人信息和身份证为例简单介绍一对一关系。
表1.1 个人信息表(person表)
表 1.2 person表字段解释
表1.3身份证表(card表)
表1.4 身份证表字段解释
person表建表sql语句:
createtable person(id int key primary auto_increment,name char(20),addresschar(50),telephone char(20),idcard int,foreign key(idcard) reference card(id))
这里唯一需要留意的两个字段就是id字段以及idcard字段,id字段是person表的主键,idcard是person表的外键指向card表的主键。
card表建表语句:
createtable card(id int key primary auto_increment,user_id int,num char(30),foreignkey(user_id) reference person(id))
这里同样需要注意的两字段就是id主键以及外键user_id,外键user_id指向person表的主键。
到此为止,person表和card表一对一的关系就建立起来了,简单吧,表和表之间对应关系就是通过设计外键连接起来的,一对一的关系是最简单的一种表关系,其外键设计起来也简单。
2.实体类对象设计
其实多表查询的对象设计和单表查询的对象设计基本完全一样,只是在外键的映射上存在不同,在单表查询映射时,字段名类型映射到对应java基本类型或者字符串。但是在多表查询时,外键字段映射到对象引用或者集合。
person表的映射关系:
public class Person {
publicint id;
publicString name;
publicString address;
publicString telePhone;
publicint idcard;
//idcard字段映射成了Card对象引用
publicCard card;
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicString getName() {
returnname;
}
publicvoid setName(String name) {
this.name= name;
}
publicString getAddress() {
returnaddress;
}
publicvoid setAddress(String address){
this.address= address;
}
publicString getTelePhone() {
returntelePhone;
}
publicvoid setTelePhone(StringtelePhone) {
this.telePhone= telePhone;
}
publicCard getCard() {
returncard;
}
publicvoid setCard(Card card) {
this.card= card;
}
publicint getIdCard() {
returnidcard;
}
publicvoid setIdCard(int idCard) {
this.idcard= idCard;
}
@Override
publicString toString() {
return"Person [id=" + id + ", name=" + name + ",address=" + address + ", telePhone=" + telePhone + ",idCard="
+ idcard + ", card=" +card + "]";
}
}
card表的映射关系:
public classCard {
publicint id;
publicint userID;
//userid字段映射成了Person引用
publicPerson person;
publicString num;
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicPerson getPerson() {
returnperson;
}
publicvoid setPerson(Person person) {
this.person= person;
}
publicString getNum() {
returnnum;
}
publicvoid setNum(String num) {
this.num= num;
}
publicint getUserID() {
returnuserID;
}
publicvoid setUserID(int userID) {
this.userID= userID;
}
@Override
publicString toString() {
return"Card [id=" + id + ", userID=" + userID + ",person=" + person + ", num=" + num + "]";
}
}
从上面POJO类的设计来看,表中的外键都被映射成了引用类型,这里必须特别强调一下,假如在Mapper文件下的resultMap元素中配置了POJO对象属性名和表字段的映射关系,那么属性名和字段名不一定需要一一对应。
3. Mybatis完成一对一表关系查询操作
(1).创建一个名为PersonMapper.xml的Maper配置文件,在其中配置查询sql语句以及resultMap的映射关系,配置文件如下:
<?xmlversion="1.0" encoding="UTF-8"?>
<!DOCTYPEmapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.bupt.mapper.PersonMapper">
<!--resultMap这个地方为什么会出现问题-->
<resultMap type="com.bupt.multiquery.Person"id="PersonResultMap">
<idproperty="id" column="id" />
<result property="name" column="name"/>
<result property="address" column="address"/>
<result property="telePhone" column="telePhone"/>
<association property="card"javaType="com.bupt.multiquery.Card">
<id property="id" column="idcard"/>
<result property="num" column="num"/>
<result property="userID" column="id"/>
</association>
</resultMap>
<insert id="insertPerson"parameterType="com.bupt.multiquery.Person" >
insert intoperson(name,address,telePhone,idcard)value(#{name},#{address},#{telePhone},#{idCard})
</insert>
<!--多表查询语句-->
<select id="queryPersonByIdWithCard"parameterType="int" resultMap="PersonResultMap">
select p.*,c.num from person p,idcardc where p.id=#{id} and p.idcard=c.id
</select>
</mapper>
在上述的配置文件中,resultMap元素是配置核心,resultMap元素定义了从数据中查询到的结果集和对象之间的映射关系
<id property="id" column="id"/>
主键映射关系,property是对象中的属性名称,colum对应的数据表的列名。
<result property="id" column="id"/>
普通属性之间映射关系,property是对象中的属性名称,colum对应的数据表的列名。
<associationproperty="card" javaType="com.bupt.multiquery.Card">
<id property="id" column="idcard"/>
<result property="num" column="num"/>
<result property="userID" column="id"/>
</association>
<association>这个元素是引用类型的映射关系,card代表对象中引用类型的属性名称,javaType代表引用类型的权限定类名,其余元素含义不变均是属性名称和列名一一对应。
(2)MyBatis主配置文件和常规一样没有什么修改的地方
<?xmlversion="1.0" encoding="UTF-8" ?>
<!DOCTYPEconfiguration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver"value="com.mysql.jdbc.Driver"/>
<property name="url"value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username"value="root"/>
<property name="password"value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--引入映射配置文件-->
<mapper resource="com/bupt/mapper/CustomerMapper.xml"/>
<mapper resource="com/bupt/mapper/PersonMapper.xml"/>
<mapper resource="com/bupt/mapper/CardMapper.xml"/>
</mappers>
</configuration>
(3)测试代码设计,测试代码,引入了Junit4框架。
@Test
publicvoid queryWithCardTest(){
SqlSession sqlSession =MybatisUtils.getSession();
Person person =sqlSession.selectOne("com.bupt.mapper.PersonMapper"+".queryPersonByIdWithCard",5);
System.out.println(person.toString());
sqlSession.close();
}
二丶一对多关系的表
一对多关系在实际生活中十分常见,比如在商城系统中,个人账户和订单就是一对多的关系,一个账户可以对应多个订单,当然从订单的角度看是一对一的关系,一个订单对应一个账户。
图2.1 一对多关系表的映射
1.账户表和订单表的设计
表2.1账户表(account)设计
表2.2 accout表字段含义说明
表2.3 订单表(order)设计
表2.4 order表字段含义解释
一对多关系和一对一表关系不同之处在于,一对多关系中只有多的一方维护外键而一对一关系中双方都需要维护外键。
2.实体类对象设计
Account表对象设计:
public classAccount {
//account表id字段
publicint id;
//account表name字段
publicString name;
//account表telephone字段
publicString telePhone;
//表单几个体现一对多的关系
publicList<Order> orderList;
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicString getName() {
returnname;
}
publicvoid setName(String name) {
this.name= name;
}
publicString getTelePhone() {
returntelePhone;
}
publicvoid setTelePhone(StringtelePhone) {
this.telePhone= telePhone;
}
@Override
publicString toString() {
return"Account [id=" + id + ", name=" + name + ",telePhone=" + telePhone + ", orderList=" + orderList +"]";
}
}
Order表对象设计:
public classOrder {
//Order表id字段
publicint id;
//Order表total_price字段
publicint totalPrice;
//Order表account_id字段
publicint accountID;
publicAccount getAccount() {
returnaccount;
}
publicvoid setAccount(Account account){
this.account= account;
}
//多对一关系中,多的一方维护外键
publicAccount account;
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicint getTotalPrice() {
returntotalPrice;
}
publicvoid setTotalPrice(int totalPrice) {
this.totalPrice= totalPrice;
}
publicint getAccountID() {
returnaccountID;
}
publicvoid setAccountID(int accountID) {
this.accountID= accountID;
}
@Override
publicString toString() {
return"Order [id=" + id + ", totalPrice=" + totalPrice + ",accountID=" + accountID + "]";
}
}
3. MyBatis 完成一对多的关联查询操作:
(1)创建一个名为AccountMapper.xml配置文件,完成sql语句以及resultMpaer元素的设计:
<?xmlversion="1.0" encoding="UTF-8"?>
<!DOCTYPEmapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.bupt.mapper.AccountMapper">
<insert id="insertAccount"parameterType="com.bupt.onetomulti.Account" >
insert into account(name,telePhone)value(#{name},#{telePhone})
</insert>
<select id="selectAccountByIdWithOrder"parameterType="int" resultMap="AccountMap">
<!--多表查询sql语句-->
selecta.*,o.id as order_id,o.total_price from account a,orders o where a.id=#{id} ando.account_id=a.id
</select>
<resultMap type="com.bupt.onetomulti.Account"id="AccountMap">
<idproperty="id" column="id"/>
<resultproperty="name" column="name"/>
<resultproperty="telePhone" column="telePhone"/>
<collectionproperty="orderList" ofType="com.bupt.onetomulti.Order">
<!--为了简单起见这里并没有映射全部的字段-->
<id property="id"column="order_id"/>
<result property="totalPrice"column="total_price"/>
</collection>
</resultMap>
</mapper>
(2)在Mybatis主配置文件中,将AccountMapper.xml配置文件加入进去
<?xmlversion="1.0" encoding="UTF-8" ?>
<!DOCTYPEconfiguration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver"value="com.mysql.jdbc.Driver"/>
<property name="url"value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username"value="root"/>
<property name="password"value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--引入映射配置文件-->
<mapper resource="com/bupt/mapper/CustomerMapper.xml"/>
<mapper resource="com/bupt/mapper/PersonMapper.xml"/>
<mapper resource="com/bupt/mapper/CardMapper.xml"/>
<mapper resource="com/bupt/mapper/AccountMapper.xml"/>
<mapper resource="com/bupt/mapper/OrderMapper.xml"/>
</mappers>
</configuration>
(3)测试代码,测试代码引入juint4测试框架
@Test
publicvoid queryAccountByIDTest(){
SqlSession sqlSession= MybatisUtils.getSession();
Accountaccount=sqlSession.selectOne("com.bupt.mapper.AccountMapper"+".selectAccountByIdWithOrder",1);
sqlSession.commit();
sqlSession.close();
System.out.print(account);
}
(4)测试结果:
图2.2 Account表情况
图2.3 order表情况
图2.4 查询结果
三丶多对多表关系
1.多对多关系描述
多对多关系是表关系中最复杂的一种,但是也是生活中最常见的一种,在商城系统中,多对多关系同样具有应用,比如订单和商品,一个订单可以包含多种商品,同时一种商品可能存在在多种订单当中。多对多关系比较特殊一点就是需要引入一个中间表才能描述多对多关系,以订单和商品为例的多对多关系描述如下:
图3.1 多对多表关系描述
在图3.1所示的多对多关系中,可以看见中间表的作用,OrderItem中间表,维护两个外键,第一一个外键OrderItem指向Order表id字段,第二个外键product_id指向Product表的id字段。在查询Order表和其关系的product表关系时,首先利用order表中id字段查询OrderItem表得到product_id字段,接着利用product_id字段查询product表得到商品信息,也就是得到了订单关联的所有商品信息。
在数据库中建立三张表,一张Orders表代表订单,一张OrderItem表充当中间表,一张Product表记录商品信息,表结构如下
表3-1orders表结构
表3-2 orders表内容介绍
表3-3 product表结构
表3-4 product表内容介绍
表3-5 order_item表结构
表3-6 order_item表内容介绍
2.实体类对象设计
Order对象设计
public classOrder {
//Order表id字段
publicint id;
//Order表total_price字段
publicint totalPrice;
//Order表account_id字段
publicint accountID;
//多对多关系中,维护相关product选项
publicList<Product> productList;
publicList<Product> getProductList() {
returnproductList;
}
publicvoidsetProductList(List<Product> productList) {
this.productList= productList;
}
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicint getTotalPrice() {
returntotalPrice;
}
publicvoid setTotalPrice(int totalPrice) {
this.totalPrice= totalPrice;
}
publicint getAccountID() {
returnaccountID;
}
publicvoid setAccountID(int accountID) {
this.accountID= accountID;
}
@Override
publicString toString() {
return"Order [id=" + id + ", totalPrice=" + totalPrice + ",accountID=" + accountID + "]";
}
}
product对象设计:
publicclass Product {
publicint id;
publicString name;
publicString price;
//多对多关系,相关联的订单选项
List<Order> orderList;
publicList<Order> getOrderList() {
returnorderList;
}
publicvoidsetOrderList(List<Order> orderList) {
this.orderList= orderList;
}
publicint getId() {
returnid;
}
publicvoid setId(int id) {
this.id= id;
}
publicString getName() {
returnname;
}
publicvoid setName(String name) {
this.name= name;
}
publicString getPrice() {
returnprice;
}
publicvoid setPrice(String price) {
this.price= price;
}
}
这里说明一下,orderItem表示查询的中间表,在此不需要设计实体类。
3.Mybatis中完成多对多关联查询
(1) 创建一个名为ProductMapper1的映射文件,在里面配置sql语句以及resultMap映射元素,这个Mapper配置文件和一对多时配置文件并无太大差别,只是在sql语句查询的时候借助了orderItem中间表
<?xmlversion="1.0" encoding="UTF-8"?>
<!DOCTYPEmapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.bupt.mapper.ProductMapper">
<insert id="insertProduct"parameterType="com.bupt.multitomulti.Product" >
insert into product(id,name,price)value(#{id},#{name},#{price})
</insert>
<select id="quertOrderByIDWithProduct"parameterType="int" resultMap="productMapper">
select o.*,p.id asp_id,p.name,p.price from
orders o,order_item oi,productp where
o.id=#{id} and o.id=oi.order_id andoi.product_id=p.id
</select>
<resultMap type="com.bupt.onetomulti.Order"id="productMapper">
<id property="id"column="id"/>
<result property="totalPrice"column="total_price"/>
<collection property="productList"ofType="com.bupt.multitomulti.Product">
<id property="id" column="p_id"/>
<result property="name" column="name"></result>
<result property="price" column="price"></result>
</collection>
</resultMap>
</mapper>
(2)在Mybatis主配置文件中加入ProductMapper.xml文件
<?xmlversion="1.0" encoding="UTF-8" ?>
<!DOCTYPEconfiguration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver"value="com.mysql.jdbc.Driver"/>
<property name="url"value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username"value="root"/>
<property name="password"value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--引入映射配置文件-->
<mapper resource="com/bupt/mapper/CustomerMapper.xml"/>
<mapper resource="com/bupt/mapper/PersonMapper.xml"/>
<mapper resource="com/bupt/mapper/CardMapper.xml"/>
<mapper resource="com/bupt/mapper/AccountMapper.xml"/>
<mapper resource="com/bupt/mapper/OrderMapper.xml"/>
<mapper resource="com/bupt/mapper/ProductMapper.xml"/>
</mappers>
</configuration>
(3)测试代码,测试代码引入Junit4测试框架
@Test
publicvoidqueryOrderByidWithPriduct(){
SqlSession sqlSession = MybatisUtils.getSession();
Order order=sqlSession.selectOne("com.bupt.mapper.ProductMapper"+".quertOrderByIDWithProduct",1);
sqlSession.commit();
sqlSession.close();
System.out.println(order);
}
图3-2order表情况
图3-3 orderItem表情况
图3-4 product表情况
测试打印语句如下:
Order [id=1,totalPrice=300, accountID=0, productList=[Product [id=1, name=product1,price=100.00, orderList=null], Product [id=2, name=product2, price=200.00,orderList=null]]]