8.MyBatis 的关联映射
8.1关联映射的概念
在关系型数据库中,多表之间存在三种关联关系,分别为一对一,一对多,多对多。
在数据库中的表现:
-
一对一:在任意一方引入对方主键作为外键
-
一对多:在“多”的一方引入“一”的一方作为主键
-
多对多:产生中间关系表,引入两个表的主键作为外键,两个主键成为联合 主键或使用新的字段作为主键。
在Java实现时的表现: -
一对一:在本类中定义对方类型的对象
-
一对多:一个A类型对应多个B类型,在A类中以集合的方式引入B类型,在B类中定义A类型的属性a
-
多对多:均定义对方类型的集合
8.2一对一
案例:每个人都有唯一的身份证
创建好对应的数据库,然后创建对应的实体类,以及对应的get和set方法
public class IdCard {
private Integer id;
private String code;
}
public class Person {
private Integer id;
private String name;
private Integer age;
private String sex;
private IdCard card;
}
为了实现一对一查询,总共有两种方法,一个是嵌套查询,一个是嵌套结果
方法一:嵌套查询
相当于需要编写两个SQl语句,首先创建IdCardMapper,编写findCardById
<mapper namespace="com.itheima.mapper.IdCardMapper">
<!-- 根据卡号查询-->
<select id="findCardById" parameterType="Integer"
resultType="com.itheima.po.IdCard">
select * from tb_idcard where id = #{id}
</select>
</mapper>
然后编写
<mapper namespace="com.itheima.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" column="card_id" javaType="IdCard"
select="com.itheima.mapper.IdCardMapper.findCardById"></association>
</resultMap>
</mapper>
在association元素中,
property对应实体类对象属性
column对应表中的字段
javaType对应实体类的类型
select对应嵌套的子查询语句
方法二:嵌套结果
<select id="findPersonById2" parameterType="Integer"
resultMap="IdCardWithPersonResult2">
select p.*,idcard.code
from tb_person p,tb_idcard idcard
where p.card_id=idcard.id and p.id = #{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult2">
<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>
编写测试方法,运行
方法一
方法二
8.3一对多
案列:一个顾客有多个订单
编写测试数据库,编写实体类,并在顾客实体类中定义订单列表
public class Orders {
private Integer id;
private String number;
}
public class User {
private Integer id;
private String username;
private String address;
private List<Orders> orderlist;
}
编写查询方法
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="findUserWithOrders" parameterType="Integer"
resultMap="UserWithOrderResult">
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="UserWithOrderResult">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<collection property="orderlist" ofType="Orders">
<id property="id" column="orders_id" />
<result property="number" column="number"/>
</collection>
</resultMap>
这里与上一个不同的属性时ofType
编写测试方法测试
在测试过程中,出现错误
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.itheima.mapper.PersonMapper.UserMapper.findUserWithOrders
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.itheima.mapper.PersonMapper.UserMapper.findUserWithOrders
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
at com.itheima.test.MyBatisAssTest.findUser(MyBatisAssTest.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12).
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.itheima.mapper.PersonMapper.UserMapper.findUserWithOrders
at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:875)
at org.apache.ibatis.session.Configuration.getMappedStatement(Configuration.java:708)
at org.apache.ibatis.session.Configuration.getMappedStatement(Configuration.java:701)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
... 26 more
这种情况有四种可能性;
1、mapper.xml中没有加入namespace
2、mapper.xml中的方法和接口mapper的方法不对应
3、mapper.xml没有加入到mybatis-config.xml中(即总的配置文件),例外:配置了mapper文件的包路径的除外
4、mapper.xml文件名和所写的mapper名称不相同
但是我的问题是,com.itheima.mapper.UserMapper.findUserWithOrders路径写错了。
所以大家一定要仔细。
8.4多对多
案例:一个商品对应多个订单,一个订单包含多个商品
编写测试数据库,编写实体类,两个实体类中都需要添加列表
public class Orders {
private Integer id;
private String number;
private List<Product> productList;
}
public class Product {
private Integer id;
private String name;
private Double price;
private List<Orders> orders;
}
编写多表查询的语句
<mapper namespace="com.itheima.mapper.OrdersMapper">
<select id="findProductWithOrders" parameterType="Integer"
resultMap="ProductWithOrderResult">
select o.*,p.id as pid,p.name,p.price
from tb_orders o,tb_product p,tb_ordersitem oi
where o.id=oi.orders_id
and p.id=oi.product_id
and o.id = #{id}
</select>
<resultMap type="Orders" id="ProductWithOrderResult">
<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>
</mapper>
运行测试如下: