多表查询
一对一查询
我们通常在查询数据库时一般是一个实体类对应一张表,但是如果查询的字段不在一张表中该怎么办呢?
先看一个案例吧,从头开始捋------>>
由于是对数据库进行操作,先搞一下数据库,
备好以下两张表,当然你也可用自己的其他表,
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`DEPTNO` int(2) NOT NULL,
`DNAME` varchar(14) DEFAULT NULL,
`LOC` varchar(13) DEFAULT NULL,
PRIMARY KEY (`DEPTNO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `dept` VALUES ('10', 'ACCOUNTING', 'NEW YORK');
INSERT INTO `dept` VALUES ('20', 'RESEARCH', 'DALLAS');
INSERT INTO `dept` VALUES ('30', 'SALES', 'CHICAGO');
INSERT INTO `dept` VALUES ('40', 'OPERATIONS', 'BOSTON');
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
`EMPNO` int(4) NOT NULL,
`ENAME` varchar(10),
`JOB` varchar(9),
`MGR` int(4),
`HIREDATE` date,
`SAL` int(7),
`COMM` int(7),
`DEPTNO` int(2),
PRIMARY KEY (`EMPNO`),
KEY `FK_DEPTNO` (`DEPTNO`),
CONSTRAINT `FK_DEPTNO` FOREIGN KEY (`DEPTNO`) REFERENCES `dept` (`DEPTNO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `emp` VALUES ('7369', 'SMITH', 'CLERK', '7902', '1980-12-17', '800', null, '20');
INSERT INTO `emp` VALUES ('7499', 'ALLEN', 'SALESMAN', '7698', '1981-02-20', '1600', '300', '30');
INSERT INTO `emp` VALUES ('7521', 'WARD', 'SALESMAN', '7698', '1981-02-22', '1250', '500', '30');
INSERT INTO `emp` VALUES ('7566', 'JONES', 'MANAGER', '7839', '1981-04-02', '2975', null, '20');
INSERT INTO `emp` VALUES ('7654', 'MARTIN', 'SALESMAN', '7698', '1981-09-28', '1250', '1400', '30');
INSERT INTO `emp` VALUES ('7698', 'BLAKE', 'MANAGER', '7839', '1981-05-01', '2850', null, '30');
INSERT INTO `emp` VALUES ('7782', 'CLARK', 'MANAGER', '7839', '1981-06-09', '2450', null, '10');
INSERT INTO `emp` VALUES ('7788', 'SCOTT', 'ANALYST', '7566', '1987-04-19', '3000', null, '20');
INSERT INTO `emp` VALUES ('7839', 'KING', 'PRESIDENT', null, '1981-11-17', '5000', null, '10');
INSERT INTO `emp` VALUES ('7844', 'TURNER', 'SALESMAN','7698', '1981-09-08', '1500', '0', '30');
INSERT INTO `emp` VALUES ('7876', 'ADAMS', 'CLERK', '7788', '1987-05-23', '1100', null, '20');
INSERT INTO `emp` VALUES ('7900', 'JAMES', 'CLERK', '7698', '1981-12-03', '950', null, '30');
INSERT INTO `emp` VALUES ('7902', 'FORD', 'ANALYST', '7566', '1981-12-03', '3000', null, '20');
INSERT INTO `emp` VALUES ('7934', 'MILLER', 'CLERK', '7782', '1982-01-23', '1300', null, '10');
新建一个maven模块或者新建一个maven工程都可以;
然后准备实体类和对应映射;
这么多字段,就不用手动去编写实体类了,通过mybatis逆向工程自动生成是一个不错的选择;
基本架构就出来了
初步检查一下映射文件,实体类路径是否匹配;
然后开始写mapper映射文件
先分析以一下要查询的数据
发现并不在一个实体类上,那么怎么办?
新建一个实体类,作用是用于接收查询的数据
新实体类暂且叫EmpDept吧,同时还要配置映射文件
根据要查询的数据来编写实体类并配置映射文件
你会发现,要原来的实体类和mapper有何用?
如果查询不同的数据,那么每次都要有一个新的实体类???
很显然是不可以的;
那怎吗办呢?
可不可以在一个实体里面引入另一个实体类?
这样就可以减少新建立一个实体类
不妨这么干----从员的角度出发,一个员工对应一个部门
在emp实体类中添加一个dept属性;
这样我们减少一些不必要的类与映射
一对多查询
如果从部门的角度出发,一个部门对应好几个员工,那么我们的实体类该怎么修改呢?
首先更改dept实体中的属性----增加 emp属性
然后配置映射文件
最后测试---->>
小结
一对一 resultmap用的是association 标签
在 association 元素中通常使用以下属性------>>>
property:指定映射到实体类的对象属性。
column:指定表中对应的字段(即查询返回的列名)。
javaType:指定映射到实体对象属性的类型。
一对多 resultmap用的是 collection 标签
在 collection 元素中通常使用以下属性。
property:指定映射到实体类的对象属性。
column:指定表中对应的字段(即查询返回的列名)。
javaType:指定映射到实体对象属性的类型。
select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
多对多查询
首先准备一个数据库然后建表,当然不用自己建,网上有很多可以拿过来练习的虚拟表格,拿来用就可以了’
下面是完整的sql数据,有些顺序需要自己调整,添加外加约束的表要放在后面创建;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for userinfo
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo` (
`uid` int(0) NOT NULL AUTO_INCREMENT,
`uname` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`upwd` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of userinfo
-- ----------------------------
INSERT INTO `userinfo` VALUES (1, '李二狗', '123');
INSERT INTO `userinfo` VALUES (2, '张化胡', '456');
INSERT INTO `userinfo` VALUES (3, '赵小红', '123');
INSERT INTO `userinfo` VALUES (4, '李晓明', '345');
INSERT INTO `userinfo` VALUES (5, '杨小胤', '123');
INSERT INTO `userinfo` VALUES (6, '谷小乐', '789');
SET FOREIGN_KEY_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for orderinfo
-- ----------------------------
DROP TABLE IF EXISTS `orderinfo`;
CREATE TABLE `orderinfo` (
`oid` int(0) NOT NULL AUTO_INCREMENT,
`ordernum` int(0) NULL DEFAULT NULL,
`userId` int(0) NULL DEFAULT NULL,
PRIMARY KEY (`oid`) USING BTREE,
INDEX `userId`(`userId`) USING BTREE,
CONSTRAINT `orderinfo_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `userinfo` (`uid`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of orderinfo
-- ----------------------------
INSERT INTO `orderinfo` VALUES (1, 20200107, 1);
INSERT INTO `orderinfo` VALUES (2, 20200806, 2);
INSERT INTO `orderinfo` VALUES (3, 20206702, 3);
INSERT INTO `orderinfo` VALUES (4, 20200645, 1);
INSERT INTO `orderinfo` VALUES (5, 20200711, 2);
INSERT INTO `orderinfo` VALUES (6, 20200811, 2);
INSERT INTO `orderinfo` VALUES (7, 20201422, 3);
INSERT INTO `orderinfo` VALUES (8, 20201688, 4);
SET FOREIGN_KEY_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for ordersdetail
-- ----------------------------
DROP TABLE IF EXISTS `ordersdetail`;
CREATE TABLE `ordersdetail` (
`odid` int(0) NOT NULL AUTO_INCREMENT,
`orderId` int(0) NULL DEFAULT NULL,
`productId` int(0) NULL DEFAULT NULL,
PRIMARY KEY (`odid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of ordersdetail
-- ----------------------------
INSERT INTO `ordersdetail` VALUES (1, 1, 1);
INSERT INTO `ordersdetail` VALUES (2, 1, 2);
INSERT INTO `ordersdetail` VALUES (3, 1, 3);
INSERT INTO `ordersdetail` VALUES (4, 2, 3);
INSERT INTO `ordersdetail` VALUES (5, 2, 1);
INSERT INTO `ordersdetail` VALUES (6, 3, 2);
SET FOREIGN_KEY_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`pid` int(0) NOT NULL AUTO_INCREMENT,
`pname` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`price` double NULL DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1, 'JavaWeb', 128);
INSERT INTO `product` VALUES (2, 'C##', 138);
INSERT INTO `product` VALUES (3, 'Python', 132.35);
SET FOREIGN_KEY_CHECKS = 1;
分析表关系
所以在userinfo实体类添加 orderinfo的集合,
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
private Integer uid;
private String uname;
private String upwd;
private List<OrderInfo> orderInfo;
}
在orderinfo中添加orderdetail集合
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderInfo implements Serializable {
private static final long serialVersionUID = 1L;
private Integer oid;
private Integer ordernum;
private Integer userid;
// 一对一 一条订单 对应一条订单信息
List <OrdersDetail> ordersdetail;
// //一条订单信息对商品 一对多
// private List<Product>products;
}
然后在orderdetail中添加productj集合属性,
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrdersDetail implements Serializable {
private static final long serialVersionUID = 1L;
private Integer odid;
private Integer orderid;
private Integer productid;
//一条订单信息对商品 一对多
private List<Product>products;
}
product类不做任何操作
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pid;
private String pname;
private Double price;
}
接下来写mapper接口中的方法了;
简单写一个 一个用户下的所有订单信息;
从用户角度出发,所以 使用 userinfo的实体类作为入口
定义查询方法----->>>
import com.gavin.pojo.UserInfo;
import java.util.List;
public interface UserInfoMapper {
List<UserInfo> findUserOrder (Integer uid);
}
开始搞mapper.xm中的映射;
先写sql语句,然后在构造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">
<mapper namespace="com.gavin.mapper.UserInfoMapper">
<resultMap id="userRef" type="userInfo">
...............看下面
</resultMap>
<select id="findUserOrder" resultMap="userRef" parameterType="int">
SELECT
u.*,
o.*,
od.*,
p.*
FROM
userinfo u
inner JOIN orderinfo o ON u.uid = o.userid
inner JOIN ordersdetail od ON o.oid = od.orderid
inner JOIN product p ON od.productid = p.pid
<where>
u.uid = #{uid}
</where>
</select>
</mapper>
自定义映射----
<resultMap id="userRef" type="userInfo">
<!-- 用户表-->
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="upwd" column="upwd"/>
<!--订单表-->
<collection property="orderInfo" ofType="orderInfo">
<id column="oid" property="oid"/>
<result column="ordernum" property="ordernum"/>
<result column="userid" property="userid"/>
<!--订单信息表-->
<collection property="ordersdetail" ofType="ordersDetail">
<id column="odid" property="odid"/>
<result column="orderid" property="orderid"/>
<result column="productid" property="productid"/>
<collection property="products" ofType="product">
<id property="pid" column="pid"/>
<result property="pname" column="pname"/>
<result property="price" column="price"/>
</collection>
</collection>
</collection>
</resultMap>
测试结果----->>
异常解决----查询时遇到异常
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
查看异常原因—查询到多条数据,但是mybais要求只能显示一条,这说明方法的返回类型不匹配,尝试改为集合或者数组 然后测试,一般问题就是出现在这里;
自定义映射最主要的是嵌套的问题,以及实体类引入属性的问题
如果是非常复杂的映射,需要嵌套好几层;
使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率,MyBatis默认没有开启延迟加载,需要在核心配置文件mybatis-config.xml中的元素内进行配置,具体配置方式如下。
在映射文件中,MyBatis关联映射的<association元素和<collection元素中都已默认配置了延迟加载属性,即默认属性fetchType=“lazy”(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无须在映射文件中再做配置。
mybatis分页实现
准备一个分页工具文件----
package com.gavin.util;
import com.gavin.pojo.UserInfo;
import java.util.List;
public class page {
// 表数据
private Integer dataCount;
//每页显示多少条数据
private Integer showData;
//一共多少个页
private Integer pageCount;
//当前页
private Integer pageIndex;
// 当前页现实的集合信息
private List<UserInfo> list;
public page() {
}
public page(Integer dataCount, Integer showData, Integer pageCount, Integer pageIndex, List<UserInfo> list) {
this.dataCount = dataCount;
this.showData = showData;
this.pageCount = pageCount;
this.pageIndex = pageIndex;
this.list = list;
}
public Integer getDataCount() {
return dataCount;
}
public void setDataCount(Integer dataCount) {
this.dataCount = dataCount;
}
public Integer getShowData() {
return showData;
}
public void setShowData(Integer showData) {
this.showData = showData;
}
public Integer getPageCount() {
// 总条数除以每页显示数据,能整除就取这个值,否则+1
return this.dataCount % this.showData == 0 ? this.dataCount / this.showData : this.dataCount / this.showData + 1;
}
/* public void setPageCount(Integer pageCount) {
this.pageCount = pageCount;
}*/
public Integer getPageIndex() {
return pageIndex;
}
public void setPageIndex(Integer pageIndex) {
this.pageIndex = pageIndex;
}
public List<UserInfo> getList() {
return list;
}
public void setList(List<UserInfo> list) {
this.list = list;
}
}
测试后
参数设置-----
查询所有记录数%每页显示的数,如果能整除,则 页数为记录数/每页显示数,否则加一,但是这里有一个坑,如果是数组或者集合,由于是下标为零开始,所以可以不用加1,需要灵活处理;
也不用这么麻烦,其实还有一个工具类----RowBounds用它就可以直接
直线分页;
@Test
public void Test() {
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
RowBounds rowBounds= new RowBounds(0,2);
List<UserInfo> userInfos = mapper.selectBypage(rowBounds);
for (UserInfo u :
userInfos) {
System.out.println(u.getUid() + "--" + u.getUname());
}
}