1. Ligação de três níveis (tomando como exemplo a sala de exame província-cidade)
1) design de mesa
A mesma tabela existe para províncias e cidades. Um parentId de 0 representa uma província e um parentId diferente de zero representa uma cidade sob a província correspondente.
package com.example.jiakao.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.persistence.*;
@Entity
@Data
@TableName("plate_region")
@Table(name="plate_region")
public class PlateRegionPo {
@Id
@TableId(type = IdType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column( columnDefinition = "int4" )
private Integer id;
@Column( columnDefinition = "varchar(64)" )
private String name;
@Column( columnDefinition = "varchar(64)" )
private String plate;
@Column( columnDefinition = "int4 default 0 " )
private Integer parentId;
}
Tabela da sala de exames, parentId corresponde ao id da cidade
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.persistence.*;
@Entity
@Data
@TableName("exam_place")
@Table(name="exam_place")
public class ExamPlacePo {
@Id
@TableId(type = IdType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column( columnDefinition = "bigint" )
private Long id;
@Column( columnDefinition = "varchar(64)" )
private String name;
@Column( columnDefinition = "int4" )
private Integer parentId;
}
2) Defina o tipo de retorno vo
package com.example.jiakao.pojo.vo.list;
import lombok.Data;
import java.util.List;
@Data
public class ProvinceVo {
private Long id;
private String name;
private String plate;
private List<CityVo> children;
}
package com.example.jiakao.pojo.vo.list;
import lombok.Data;
import java.util.List;
@Data
public class CityVo {
private Long id;
private String name;
private String plate;
private List<ExamPlaceVo> children;
}
package com.example.jiakao.pojo.vo.list;
import lombok.Data;
@Data
public class ExamPlaceVo {
private Long id;
private String name;
}
3) instrução de consulta 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">
<mapper namespace="com.example.jiakao.mapper.ExamPlacePoMapper">
<resultMap id="selectRegionExamPlaces" type="com.example.jiakao.pojo.vo.list.ProvinceVo">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="plate" property="plate"></result>
<collection property="children" ofType="com.example.jiakao.pojo.vo.list.CityVo">
<id column="city_id" property="id"></id>
<result column="city_name" property="name"></result>
<result column="city_plate" property="plate"></result>
<collection property="children" ofType="com.example.jiakao.pojo.vo.list.ExamPlaceVo">
<id column="exam_id" property="id"></id>
<result column="exam_name" property="name"></result>
</collection>
</collection>
</resultMap>
<select id="listRegionExamPlaces" resultMap="selectRegionExamPlaces">
select
p.id,
p.name,
p.plate,
c.id city_id,
c.name city_name,
c.plate city_plate,
e.id exam_id,
e.name exam_name
from plate_region p
join plate_region c on c.parent_id = p.id
join exam_place e on e.parent_id = c.id
where p.parent_id = 0
</select>
</mapper>
4) Resultados da consulta
{
"code": 200,
"message": "成功",
"data": [
{
"id": 2,
"name": "湖北",
"plate": "鄂",
"children": [
{
"id": 3,
"name": "武汉",
"plate": "A",
"children": [
{
"id": 1,
"name": "hfgg"
},
{
"id": 5,
"name": "hgi"
}
]
}
]
},
{
"id": 1,
"name": "广东",
"plate": "粤",
"children": [
{
"id": 7,
"name": "深圳",
"plate": "B",
"children": [
{
"id": 6,
"name": "hgiG"
}
]
},
{
"id": 8,
"name": "广州",
"plate": "A",
"children": [
{
"id": 7,
"name": "hgU"
},
{
"id": 8,
"name": "hgUU"
}
]
}
]
}
]
}
5) Pontos a observar
A diferença entre join, left join e right join é brevemente resumida: join pega a interseção de duas tabelas e mantém apenas as entradas de interseção; left join usa a tabela antes da join como a tabela principal, independentemente de haver ou não uma interseção ou não, as entradas da tabela principal existem; A junção à direita toma a tabela após a junção como a tabela principal.
2. Sobre o processamento de consulta de tabelas conjuntas com consulta difusa e paginação (tomando como exemplo a sala de exame-sujeitos de exame)
1) A definição de vo é usada para passar parâmetros ao salvar dados, correspondentes aos campos do objeto po, e apenas ignora os campos do objeto po que não requerem operações do usuário.
package com.example.jiakao.pojo.vo.req.save;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("保存考场信息的参数")
public class ExamPlaceReqVo {
@ApiModelProperty(value = "考场id")
private Long id;
@ApiModelProperty(value = "考场名称", required = true)
@NotBlank(message = "名称不能为空")
private String name;
@ApiModelProperty(value = "考场所属城市id", required = true)
@NotNull(message = "所属城市不能为空")
private Integer cityId;
@ApiModelProperty(value = "考场所属省份id", required = true)
@NotNull(message = "所属省份不能为空")
private Integer provinceId;
}
package com.example.jiakao.pojo.vo.req.save;
import com.example.jiakao.common.validator.BoolNumber;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("保存课程信息")
public class ExamCourseReqVo {
@ApiModelProperty("课程id")
private Long id;
@ApiModelProperty(value = "课程名称", required = true)
@NotBlank(message = "课程名不能为空")
private String name;
@ApiModelProperty(value = "课程类型",required = true)
@NotNull(message = "课程类型不能为空")
@BoolNumber(message = "课程类型值为0或1")
private Short type;
@ApiModelProperty(value = "课程价格",required = true)
@NotNull(message = "课程价格不能为空")
@Min(value = 0, message = "价格不能为负数")
private Double price;
@ApiModelProperty(value = "课程所属考场",required = true)
@NotNull(message = "所属考场不能为空")
private Long examPlaceId;
}
A definição vo da lista retornada é usada para construir a entidade de resultado retornada.
package com.example.jiakao.pojo.vo.list;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("课程信息返回值")
public class ExamCourseVo {
@ApiModelProperty("课程id")
private Long id;
@ApiModelProperty(value = "课程名称")
private String name;
@ApiModelProperty(value = "课程类型")
private Short type;
@ApiModelProperty(value = "课程价格")
private Double price;
@ApiModelProperty(value = "课程所属考场")
private Long examPlaceId;
@ApiModelProperty(value = "考场所属省份")
private Integer provinceId;
@ApiModelProperty(value = "考场所属城市")
private Integer cityId;
}
2) Serviço e Mapeador
Como existem campos de nome nas duas tabelas que usamos, precisamos marcar claramente o campo de nome de qual tabela ao construir o QueryWrapper, então usamos QueryWrapper em vez de LambdaQueryWrapper aqui.
package com.example.jiakao.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.jiakao.common.enhance.MpPage;
import com.example.jiakao.common.enhance.MpQueryWrapper;
import com.example.jiakao.mapper.ExamCourseMapper;
import com.example.jiakao.pojo.entity.ExamCoursePo;
import com.example.jiakao.pojo.vo.json.PageVo;
import com.example.jiakao.pojo.vo.list.ExamCourseVo;
import com.example.jiakao.pojo.vo.req.query.KeyWordReqVo;
import org.springframework.stereotype.Service;
@Service
public class ExamCourseService extends ServiceImpl<ExamCourseMapper, ExamCoursePo> {
public PageVo<ExamCourseVo> listCoursesPage(KeyWordReqVo query) {
MpPage<ExamCourseVo> mpPage= new MpPage<>(query);
MpQueryWrapper<ExamCourseVo> wrapper = new MpQueryWrapper<>();
wrapper.likes(query.getKeyword(),"c.name");
baseMapper.selectCourses(mpPage,wrapper);
return mpPage.buildVo();
}
} }
}
package com.example.jiakao.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.jiakao.common.enhance.MpPage;
import com.example.jiakao.pojo.entity.ExamCoursePo;
import com.example.jiakao.pojo.vo.list.ExamCourseVo;
import org.apache.ibatis.annotations.Param;
//官方推荐前缀Constants.WRAPPER => ew
public interface ExamCourseMapper extends BaseMapper<ExamCoursePo> {
public MpPage<ExamCourseVo> selectCourses(MpPage<ExamCourseVo> page,
@Param(Constants.WRAPPER) QueryWrapper wrapper);
}
Quando o tipo que passamos e retornamos no método Mapper for a implementação da interface IPage, o Mybatis-plus nos ajudará automaticamente a paginar, ou seja, unir o limite após a declaração da consulta. No entanto, as condições de consulta não serão implementadas automaticamente para nós. Precisamos chamar manualmente o customSqlSegment do tipo QueryWrapper para obter a instrução SQL e, em seguida, uni-la em xml. Observe que ${} é usado para união em vez de #{}, $ é uma substituição direta e # será pré-compilado.
Claro, também podemos construir LambdaQueryWrapper para entidades personalizadas diferentes de entidades genéricas BaseMapper quando necessário.Nesse caso, precisamos adicionar a configuração afterPropertiesSet.
public class MyBatisPlusConfig implements InitializingBean {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
return interceptor;
}
/**
* 拥有lambda cache的实体类,才能使用LambdaQueryWrapper<Entity>
* 默认情况下只有BaseMapper<Entity>中泛型的entity才拥有lambda cache
* 其他可以通过TableInfoHelper手动添加lambda cache
*/
@Override
public void afterPropertiesSet() throws Exception {
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(),"");
TableInfoHelper.initTableInfo(assistant, ExamCourseVo.class);
}
}
3)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">
<mapper namespace="com.example.jiakao.mapper.ExamCourseMapper">
<resultMap id="selectCourses" type="com.example.jiakao.pojo.vo.list.ExamCourseVo">
</resultMap>
<!-- ${wrapper.customSqlSegment} 获取自定义SQL-->
<select id="selectCourses" resultMap="selectCourses">
select
c.id,
c.name,
c.exam_place_id,
c.price,
c.type,
e.city_id,
e.province_id
from exam_course c
left join exam_place e on c.exam_place_id = e.id
${ew.customSqlSegment}
</select>
</mapper>