MyBatis 高级知识
- 开发环境
[Java环境] :jdk1.8.0_91
[开发工具] : IDEA 2016.2.5
[数据库] : MySQL 5.7.13
[项目管理工具]:Maven 3.1.1
- 数据库
--Table structure for tb_items
DROP TABLE IF EXISTS `tb_items`;
CREATE TABLE `tb_items` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL COMMENT '商品名',
`price` float(10,1) DEFAULT NULL COMMENT '价格',
`detail` varchar(255) DEFAULT NULL COMMENT '商品介绍',
`stock` int(255) DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Table structure for tb_orderdetail
DROP TABLE IF EXISTS `tb_orderdetail`;
CREATE TABLE `tb_orderdetail` (
`id` int(11) NOT NULL,
`order_id` int(11) NOT NULL COMMENT '订单id',
`item_id` int(11) NOT NULL COMMENT '商品id',
`count` int(11) DEFAULT NULL COMMENT '订单量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Table structure for tb_orders
DROP TABLE IF EXISTS `tb_orders`;
CREATE TABLE `tb_orders` (
`id` int(11) NOT NULL,
`userId` int(11) NOT NULL,
`number` varchar(255) DEFAULT NULL COMMENT '订单号',
`createtime` date DEFAULT NULL,
`note` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Table structure for tb_user
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`sex` varchar(10) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`address` varchar(500) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
用户表 tb_user:记录用户信息
订单表 tb_orders:记录用户所创建的订单(购买商品的订单)
订单明细表 tb_orderdetail:记录了订单的详细信息即购买商品的信息
商品表 tb_items:记录了商品信息
表之间的关联关系
实际业务中,实体与实体之间存在一定的业务关系(一对一,一对多,多对多),而关系性数据库通常是根据业务实体建表,所以上述几个关系也可以用来形容表之间的关联关系。
那么根据上述所建的 订单类业务表分析:
- 用户(tb_user) 和 订单 (tb_orders)
一个用户可以有多个订单(一对多)
一个订单只能由一个用户创建(一对一) - 订单(tb_orders) 和 订单详情(tb_orderdetail)
一个订单可以有多个订单明细(一对多)
一个订单明细只能属于一个订单(一对一) - 订单详情(tb_orderdetail) 和 商品(tb_items)
一个订单明细只能对应一个商品(一对一)
一个商品可以包含在多个订单明细中(一对多) - 订单(tb_orders) 和 商品(tb_items)
一个订单可以包含多个商品,同理一个商品可以被多个订单包含(多对多)
订单(tb_orders) 和 商品(tb_items)之间的这种多对多关系在很多实际业务中不利于DTO数据建模及开发维护,此时,使用订单详情(tb_orderdetail) 可以将 一个多对多关系拆分成两个一对多关系。
将多对多关系拆分成一对多关系,是简化数据库实体建模常用的一种方式
复杂数据模型
在实际开发过程中,经常从多个有关联关系的表中提取出符合业务需求的数据,此时需要将返回的数据封装成一个对象,此时这个对象就需要针对表关联关系(一对一,一对多,多对多)进行设计。
如何使用 MyBatis 将这个包含关联关系的复杂对象与 sql 返回数据进行绑定,使程序运行时自动映射成该对象实例呢?
一对一查询
业务需求:查询订单列表用户及下订单的用户信息
sql 实现业务:
SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
使用 resultType
1.创建实体对象(pojo)
Order.java
import cn.guan.mybatis.example2.User;
import java.util.Date;
import java.util.List;
/**
* 订单实体类
**/
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
//订单明细
private List<Orderdetail> orderdetails;
//setter and getter and toString
···
}
Orderdetail.java
**
* 订单详情实体类
**/
public class Orderdetail {
private Integer id;
private Integer order_id;
private Integer itemsId;
private Integer count;
//明细对应的商品信息
private Item items;
//setter and getter and toString
···
}
Item.java
/**
* 商品实体类
**/
public class Item {
private Integer id;
private String name;
private Float price;
private String detail;
private Integer stock;
//setter and getter and toString
···
}
OrderCustom.java
/**
* @Description : 通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
*/
public class OrderCustom extends Order{
//用户属性
private String username;
private String sex;
private String address;
//setter and getter and toString
···
}
2.创建 Mapper 接口
OrderCustomMapper.java
/**
*@Description : mapper 接口
*/
public interface OrderCustomMapper {
OrderCustom findOrdersUserByResultType() throws Exception;
}
3.创建 Mapper 接口对应 *-mapper.xml 并配置 路径
ordercustom-mapper.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">
<!--namespace :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
<!--命名空间设置为 mapper 接口地址 -->
<mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
<select id="findOrdersUserByResultType" resultType="cn.guan.mybatis.example4.OrderCustom">
SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
</select>
</mapper>
mybatis-config.xml 中配置加载路径
<!--加载 mapper 文件 路径-->
<mappers>
<mapper resource="mapperDir2/ordercustom-mapper.xml" />
</mappers>
4.测试
MyBatisMappingTest.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
/**
* @Description : MyBatis 多种映射关系测试
*/
public class MyBatisMappingTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws Exception
{
//创建sqlSessionFactory
//Mybatis 配置文件
String resource = "mybatis-config.xml";
//得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传入Mybatis的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testOne2One(){
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建usermapper对象,mybatis自动生成代理对象
OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
//调用UserMapper的方法
OrderCustom orderCustom = null;
try {
orderCustom = orderCustomMapper.findOrdersUserByResultType();
System.out.println(orderCustom);
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用 resultMap
使用 resultMap 将查询结果中的订单信息映射到 Orders 对象中,在 orders 类中添加 User 属性,将关联查询出来的用户信息映射到 orders 对象中的 user 属性中。
1.修改 ordercustom-mapper.xml
定义 resultMap
<?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">
<!--namespace :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
<!--命名空间设置为 mapper 接口地址 -->
<mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
<!--定义查询订单关联查询用户信息的resultMap
将整个查询结果映射到cn.guan.mybatis.example4.Order
-->
<resultMap id="OrderUserResultMap" type="cn.guan.mybatis.example4.Order">
<!--配置映射的订单信息-->
<!--id表示查询结果中的唯一标识 在这里是订单的唯一标识 如果是由多列组成的唯一标识,那么就需要配置多个id
column:id 是订单信息中的唯一标识列
property:id 是订单信息唯一标识列所映射到orders中的id属性
最终resultMap对column和property做一个映射关系(对应关系)
-->
<id column="id" property="id"/>
<result column="userId" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!--配置映射的关联用户信息
association 用于映射关联查询单个对象的信息
property 将要关联查询的用户信息映射到 orders中的属性中去
-->
<association property="user" javaType="cn.guan.mybatis.example2.User">
<!--id 关联用户信息的唯一标识
column: 指定唯一标识用户的信息
property:映射到user的那个属性
-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<select id="findOrdersUserByResultMap" parameterType="int" resultMap="OrderUserResultMap">
SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
</select>
</mapper>
2.新增一个接口
OrderCustomMapper.java
/**
*@Description : mapper 接口
*/
public interface OrderCustomMapper {
OrderCustom findOrdersUserByResultType() throws Exception;
List<Order> findOrdersUserByResultMap() throws Exception;
}
3.测试
···
@Test
public void testOne2OneByResultMap(){
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建usermapper对象,mybatis自动生成代理对象
OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
//调用UserMapper的方法
List<Order> orderList = null;
try {
orderList = orderCustomMapper.findOrdersUserByResultMap();
System.out.println(orderList);
} catch (Exception e) {
e.printStackTrace();
}
}
一对多查询
需求:查询订单及订单明细
使用sql 实现业务:
SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.item_id as itemId,b.count
FROM tb_orders a
LEFT JOIN tb_orderdetail b on a.id = b.order_id
LEFT JOIN tb_user c on a.userId = c.id;
使用 resultType
分析:
如果使用 resultType 将查询结果映射到 pojo 中,订单信息就会重复。
使用 resultMap
预期:
对 order 的映射不能出现重复记录。
在 Order 中添加 一个 Orderdetail 的 List 属性,将查询结果中的订单信息映射到 Order 中,订单明细信息映射到 List<Orderdetail> 中,这样订单信息就不会重复。
1.修改 ordercustom-mapper.xml
定义 resultMap
<?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">
<!--namespace :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
<!--命名空间设置为 mapper 接口地址 -->
<mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
<!--定义查询订单关联查询用户信息的resultMap
将整个查询结果映射到cn.guan.mybatis.example4.Order
-->
<resultMap id="OrderUserResultMap" type="cn.guan.mybatis.example4.Order">
<!--配置映射的订单信息-->
<!--id表示查询结果中的唯一标识 在这里是订单的唯一标识 如果是由多列组成的唯一标识,那么就需要配置多个id
column:id 是订单信息中的唯一标识列
property:id 是订单信息唯一标识列所映射到orders中的id属性
最终resultMap对column和property做一个映射关系(对应关系)
-->
<id column="id" property="id"/>
<result column="userId" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!--配置映射的关联用户信息
association 用于映射关联查询单个对象的信息
property 将要关联查询的用户信息映射到 orders中的属性中去
-->
<association property="user" javaType="cn.guan.mybatis.example2.User">
<!--id 关联用户信息的唯一标识
column: 指定唯一标识用户的信息
property:映射到user的那个属性
-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<!--定义查询订单关联查询用户信息的resultMap
将整个查询结果映射到cn.guan.mybatis.example4.Order
-->
<resultMap id="OrderAndOrderdetailsResultMap" type="cn.guan.mybatis.example4.Order" extends="OrderUserResultMap">
<!-- 订单信息 -->
<!-- 用户信息 -->
<!-- 使用extends继承,不用在中配置订单信息和用户信息的映射 -->
<!-- 订单明细信息
一个订单关联查询出了多条明细,要使用collection进行映射
collection:对关联查询到多条记录映射到集合对象中
property:将关联查询到多条记录映射到cn.zhisheng.mybatis.po.Orders哪个属性
ofType:指定映射到list集合属性中pojo的类型
-->
<collection property="orderdetails" ofType="cn.guan.mybatis.example4.Orderdetail">
<!-- id:订单明细唯 一标识
property:要将订单明细的唯 一标识 映射到cn.zhisheng.mybatis.po.Orderdetail的哪个属性-->
<id column="orderdetailId" property="id"/>
<result column="itemId" property="itemsId"/>
<result column="count" property="count"/>
<result column="orderId" property="order_id"/>
</collection>
</resultMap>
<select id="findAllOrdersAndOrderdetails" parameterType="int" resultMap="OrderAndOrderdetailsResultMap">
SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count
FROM tb_orders a
LEFT JOIN tb_orderdetail b on a.id = b.order_id
LEFT JOIN tb_user c on a.userId = c.id;
</select>
</mapper>
2.新增一个接口
OrderCustomMapper.java
/**
*@Description : mapper 接口
*/
public interface OrderCustomMapper {
OrderCustom findOrdersUserByResultType() throws Exception;
List<Order> findOrdersUserByResultMap() throws Exception;
List<Order> findAllOrdersAndOrderdetails() throws Exception;
}
3.测试
···
@Test
public void testOne2MoreByResultMap(){
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建usermapper对象,mybatis自动生成代理对象
OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
//调用UserMapper的方法
List<Order> orderList = null;
try {
orderList = orderCustomMapper.findOrdersUserByResultMap();
System.out.println(orderList);
} catch (Exception e) {
e.printStackTrace();
}
}
多对多查询
需求:查询用户及用户购买的商品信息
sql 实现业务:
SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count,d.`name` as itemName,d.price as itemPrice,d.detail as itemDetail
FROM tb_orders a
LEFT JOIN tb_orderdetail b on a.id = b.order_id
LEFT JOIN tb_user c on a.userId = c.id
LEFT JOIN tb_items d on b.item_id = d.id;
映射思路:
用户信息映射到 User 中,
在 User 中添加 List<Order> 属性,用于映射用户下的订单
Order 中的 List<Orderdetail> 用于映射 Order 对应的订单明细
Orderdetail 中添加 Item 属性用于映射明细对应商品的信息
1.pojo 类
User.java
public class User {
//user 基本属性
···
private List<Order> orders;
//setter and getter and toString
···
}
Order.java
public class Order {
//Order 基本属性
···
//订单明细
private List<Orderdetail> orderdetails;
//setter and getter and toString
···
}
Orderdetail.java
public class Orderdetail {
//Orderdetail 基本属性
···
//明细对应的商品信息
private Item items;
//setter and getter and toString
···
}
2.映射文件
userorders-mapper.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">
<!--namespace :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
<!--命名空间设置为 mapper 接口地址 -->
<mapper namespace="cn.guan.mybatis.example4.UserOrdersMapper">
<!--定义查询用户及用户购买商品信息的 resultMap-->
<resultMap id="UserAndOrdersResultMap" type="cn.guan.mybatis.example4.User">
<!--用户信息-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
<!--订单信息
一个用户对应多个订单,使用collection映射
-->
<collection property="orders" ofType="cn.guan.mybatis.example4.Order">
<id column="id" property="id"/>
<result column="userId" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 订单明细
一个订单包括 多个明细
-->
<collection property="orderdetails" ofType="cn.guan.mybatis.example4.Orderdetail">
<id column="orderdetailId" property="id"/>
<result column="itemId" property="itemsId"/>
<result column="count" property="count"/>
<result column="orderId" property="order_id"/>
<!-- 商品信息
一个订单明细对应一个商品
-->
<association property="items" javaType="cn.guan.mybatis.example4.Item">
<id column="itemId" property="id"/>
<result column="itemName" property="name"/>
<result column="itemPrice" property="price"/>
<result column="itemDetail" property="detail"/>
</association>
</collection>
</collection>
</resultMap>
<select id="findUserAndOrdersByUserId" parameterType="int" resultMap="UserAndOrdersResultMap">
SELECT c.id,c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count,d.`name` as itemName,d.price as itemPrice,d.detail as itemDetail
FROM tb_orders a
LEFT JOIN tb_orderdetail b on a.id = b.order_id
LEFT JOIN tb_user c on a.userId = c.id
LEFT JOIN tb_items d on b.item_id = d.id
where c.id = #{value};
</select>
</mapper>
加载映射文件:
mybatis-config.xml
<?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>
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务由 Mybatis 控制-->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由Mybatis管理,db_mybatis,Mysql用户名root,123456 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db_mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!--加载 mapper 文件 路径-->
<mappers>
···
<mapper resource="mapperDir2/userorders-mapper.xml" />
</mappers>
</configuration>
2.接口定义
UserOrdersMapper.java
import java.util.List;
/用户及所下订单信息查询接口
public interface UserOrdersMapper {
List<User> findUserAndOrdersByUserId(int userid) throws Exception;
}
3.测试
···
@Test
public void testMore2MoreByResultMap(){
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建usermapper对象,mybatis自动生成代理对象
UserOrdersMapper userOrdersMapper = sqlSession.getMapper(UserOrdersMapper.class);
//调用UserMapper的方法
List<User> users = null;
try {
users = userOrdersMapper.findUserAndOrdersByUserId(1);
System.out.println(users);
} catch (Exception e) {
e.printStackTrace();
}
}
结果集映射总结
resultType
按照sql 查询结果的字段名(column)与 resultType 定义的 pojo 类的属性名一对一映射,完成映射。
通常用于简单的不包含表连接关系的结果映射。resultMap
需要完成一对一,一对多,多对多这些特殊的包含表连接关系的复杂结果映射。association
将一对一的关联查询信息映射到 pojo 中,使用 resultType 无法完成成员属性为对象的自动映射。
例:订单 pojo 中包含一个 User 对象,使用 resultType 无法将 sql 的查询结果中的 User 信息自动映射到 User 成员变量中。collection
将一对多的关联查询信息映射到一个 List 集合中
使用 resultMap 能对复杂的查询结果集进行面向对象设计,而且可以通过成员变量的形式,大大减少 pojo 类的创建,同时也可以减少冗余信息的存储,方便对结果集进行遍历查询。