Mybatis-Plus:持久层数据的高效处理(新手笔记五)--多表查询的实现
🤚我的博客
- 欢迎光临我的博客 https://blog.csdn.net/qq_52434217?type=blog
🥛前言
上一章将了多表映射的基础:手写SQL。文章发布之后才发现并没有讲完要讲的内容,比如在第一章就出现的映射图。不过还好是新手笔记,想来没有多少人看。这一章将补上这点知识,然后介绍多表映射如何实现。
📄多表映射的实现
🔷mapper关系映射文件
resultMap标签
在手写sql指定数据输出格式时,除了resultType
属性之外还有resultMap
属性。而resultMap
属性与标签之间是相辅相成。resultMap
标签是定义一个实体类与数据表映射关系的核心,规定了实体类的属性与数据表字段之间的对应关系。通过resultMap
定义映射关系并对关系进行命名,就可以在resultMap
属性接受映射关系名字所对应的结构。
id
标签是设置主键列和逐渐属性之间的对应关系。
column
属性指定字段名,property
属性指定java实体类的属性名。
result
标签设置普通字段和java实体类属性之间的关系。
在这里不再测试,读者知道即可。
🔷多表查询
为什么要先介绍映射关系,是因为在多表查询时得到的返回值是可以与一个实体类对应的,而如果我们仅仅使用之前的简单返回类型相当繁琐。所以为了简单和工程开发效率,在实际开发过程中直接定义一个resultMap
用于接收返回值。多表查询需要考究的地方有很多,比如定义单独的多表映射resultMap
,再比如多表查询的对一情况与对多情况。
模型准备
创建数据表
这里采用顾客与订单之间的映射关系做演示。
CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );
CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) );
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');
生成模型
利用mybatis plus
插件逆向生成模型,这里不用勾选hashCode
等方法的重写。这里需要对实体类进行修改,以满足在订单类中包含客户实体类,同理需要在客户实体类中添加订单列表属性。
TOrder
类。
package com.vinta.my_test.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
/**
** @TableName t_order
*/@TableName(value ="t_order")
@Data
public class TOrder implements Serializable {
/**
*
* */
@TableId(type = IdType.AUTO)
private Integer orderId;
/**
*
* */
private String orderName;
/**
* 需要修改的字段
* */
private Integer customerId;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
TCustomer
类
package com.vinta.my_test.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
/**
** @TableName t_customer
*/@TableName(value ="t_customer")
@Data
public class TCustomer implements Serializable {
/**
*
* * /
@TableId(type = IdType.AUTO)
private Integer customerId;
/**
*
*
* */
private String customerName;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
DTO模型准备
DTO模型是为了接收联表查询的结果。
CustomerDTO
类
package com.vinta.my_test.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class CustomerDTO implements Serializable {
private Integer customerId;
private String customerName;
private List orderList;
private static final long serialVersionUID = 1L;
}
OrderDTO
类
package com.vinta.my_test.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class OrderDTO implements Serializable {
private Integer orderId;
private String orderName;
private TCustomer customer;
private static final long serialVersionUID = 1L;
}
多表查询测试
在多表映射时,除了需要定义resultMap
中的id
标签和result
标签外,还有定义连表关系。
对一映射
对一关系映射时需要在TOrderMapper.xml
中的resultMap
标签中添加association
标签指定连表映射关系。
<?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.vinta.my_test.mapper.TOrderMapper">
<resultMap id="BaseResultMap" type="com.vinta.my_test.entity.OrderDTO">
<id property="orderId" column="order_id" jdbcType="INTEGER"/>
<result property="orderName" column="order_name" jdbcType="CHAR"/>
<!-- 指定映射关系-->
<association property="customer" column="customer_id" javaType="com.vinta.my_test.entity.TCustomer">
<id property="customerId" column="customer_id" jdbcType="INTEGER"/>
<result property="customerName" column="customer_name" jdbcType="CHAR"/>
</association>
</resultMap>
<select id="selectOrderWithCustomer" resultMap="BaseResultMap">
SELECT order_id, order_name, c.customer_id, customer_name
FROM t_order o
LEFT JOIN t_customer c
ON o.customer_id = c.customer_id
WHERE o.order_id = #{orderId}
</select>
<sql id="Base_Column_List">
order_id
,order_name,customer_id
</sql>
</mapper>
抽象方法
package com.vinta.my_test.mapper;
import com.vinta.my_test.entity.OrderDTO;
import com.vinta.my_test.entity.TOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author VINTA
* @description 针对表【t_order】的数据库操作Mapper
* @createDate 2024-03-03 17:50:42
* @Entity com.vinta.my_test.entity.TOrder
*/
public interface TOrderMapper extends BaseMapper<TOrder> {
OrderDTO selectOrderWithCustomer(Integer orderId);
}
测试代码
package com.vinta.my_test;
import com.vinta.my_test.mapper.TOrderMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MyTestApplicationTests {
@Resource
private TOrderMapper tOrderMapper;
@Test
void multi2one(){
Integer customerId= 1;
System.out.println(tOrderMapper.selectOrderWithCustomer(customerId));
}
}
对多映射
对多关系映射时,需要在TCustomerMapper.xml
中添加collection
标签和ofType
属性
<?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.vinta.my_test.mapper.TCustomerMapper">
<resultMap id="BaseResultMap" type="com.vinta.my_test.entity.CustomerDTO">
<id property="customerId" column="customer_id" jdbcType="INTEGER"/>
<result property="customerName" column="customer_name" jdbcType="CHAR"/>
<collection property="orderList" ofType="com.vinta.my_test.entity.OrderDTO">
<id property="orderId" column="order_id" jdbcType="INTEGER"/>
<result property="orderName" column="order_name" jdbcType="CHAR"/>
</collection>
</resultMap>
<select id="selectCustomerWithOrderList" resultMap="BaseResultMap">
SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
FROM t_customer c
LEFT JOIN t_order o
ON c.customer_id=o.customer_id
WHERE c.customer_id=#{customerId}
</select>
<sql id="Base_Column_List">
customer_id,customer_name
</sql>
</mapper>
抽象方法
package com.vinta.my_test.mapper;
import com.vinta.my_test.entity.CustomerDTO;
import com.vinta.my_test.entity.TCustomer;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author VINTA
* @description 针对表【t_customer】的数据库操作Mapper
* @createDate 2024-03-03 17:50:42
* @Entity com.vinta.my_test.entity.TCustomer
*/
public interface TCustomerMapper extends BaseMapper<TCustomer> {
CustomerDTO selectCustomerWithOrderList(Integer customerId);
}
测试代码
package com.vinta.my_test;
import com.vinta.my_test.mapper.TCustomerMapper;
import com.vinta.my_test.mapper.TOrderMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MyTestApplicationTests {
@Resource
private TCustomerMapper tCustomerMapper;
@Test
void multi2one(){
Integer customerId= 1;
System.out.println(tCustomerMapper.selectCustomerWithOrderList(customerId));
}}
📄动态SQL语句
实际开发中的条件查询时往往不止一个条件且条件并非固定的,而这就需要我们采用动态SQL,实现一个函数满足多个不同条件的查询语句。在使用动态查询语句时,要切记不能在sql语句的末尾加分号。下面详细介绍动态sql语句的标签。
SQL片段提取
为了重复利用相同的sql语句段,我们采用sql
标签将可以重复利用的sql语句包裹起来,然后通过include
标签引用
<select id="selectTCustomerById" resultType="com.vinta.my_test.entity.TCustomer">
SELECT
<include refid="Base_Column_List"></include>
FROM t_customer WHERE customer_id=#{customerId}
</select>
<sql id="Base_Column_List">
order_id
,order_name,
customer_id
</sql>
抽象方法
TCustomer selectTCustomerById(Integer customerId);
测试代码
@Test
void duplicateSQLTest(){
Integer customerId= 1;
System.out.println(tCustomerMapper.selectTCustomerById(customerId));
}
if与where标签
if
标签是条件判断,需要在标签的test属性中写下判断表达式,由于是在xml中书写的判断语句,所以在比较大小时要采用xml语法,如大于符号为>
和小于符号为<
<select id="selectWhereDIY" resultMap="tOrderBaseMap">
SELECT
<include refid="Base_Column_List"></include>
from t_order o
<where>
<if test="orderName != null">
o.order_name=#{orderName}
</if>
<if test="orderId > 0">
o.order_id>#{orderId}
</if>
</where>
</select>
抽象方法
List<TOrder> selectWhereDIY(@Param("orderId") Integer orderId, @Param("orderName") String orderName);
测试代码
@Test
void test1(){
Integer customerId= 1;
System.out.println(tCustomerMapper.selectTCustomerById(customerId));
}
set标签
set标签与where标签相同,这里不过多介绍
<update id="updateCustomerIdInt">
UPDATE t_customer
<set>
<!-- '&' 为 &-->
<if test="customerId != null && customerId != 0">
customer_name=#{customerName}
</if>
</set>
</update>
抽象方法
int updateCustomerIdInt(@Param("customerId")Integer customerId, @Param("customerName") String customerName);
测试代码
@Test
void test1(){
Integer customerId= 1;
System.out.println(tCustomerMapper.updateCustomerIdInt(customerId,"张三"));
}
foreach标签
批量操作
collection属性:要遍历的集合
item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
index属性:这里起一个名字,便于后面引用 遍历List集合,这里能够得到List集合的索引值 遍历Map集合,这里能够得到Map集合的key
<update id="updateTOrderByBatch">
<foreach collection="orderList" item="order" separator=";">
update t_order set order_name=#{order.orderName} where order_id=#{order.orderId}
</foreach>
</update>
抽象方法
int updateTOrderByBatch(@Param("orderList") List<TOrder> orderList);
上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置。
关于foreach标签的collection属性
如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collection或list来引用这个list集合。这一点可以通过异常信息看出来:
Parameter 'orderList' not found. Available parameters are [arg0, collection, list]
在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用@Param注解明确声明变量的名称,然后在foreach标签的collection属性中按照@Param注解指定的名称来引用传入的参数。
choose/when/otherwise标签
该标签与switch
语句有异曲同工之妙。在多个分支条件中,仅执行一个。
- 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的when分支都不满足,那么就执行otherwise分支
<select id="selectOrderByConditionByChoose" resultType="com.vinta.my_test.entity.TOrder">
select *
where
<choose>
<when test="orderName != null">order_name=#{orderName}</when>
<when test="orderId < 2">order_id < 3000</when>
<otherwise>1=1</otherwise>
</choose>
<!--
第一种情况:第一个when满足条件 where order_name=?
第二种情况:第二个when满足条件 where order_id < 2
第三种情况:两个when都不满足 where 1=1 执行了otherwise
-->
</select>
抽象方法
List<TOrder> selectOrderByConditionByChoose(TOrder order)
END
至此mybatis plus的大部分内容就介绍完了,关于mybatis plus的篇章就告一段落。下面的时间将更新我的项目笔记,当然如果有时间也想分享一下深度学习的笔记,最近正好在学习yolo模型。等我完全学透了就会整理出笔记发布到我的博客。如果有什么问题可以关注我的微信公众号。
公众号
更多内容请关注小夜的公众号,将持续更新java spring全家桶,vue全家桶,python数据分析与爬虫以及js与爬虫逆向。
欢迎关注小夜的公众号