前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mybatis-plus 自定义SQL、一对多、分页查询过滤多租户

mybatis-plus 自定义SQL、一对多、分页查询过滤多租户

原创
作者头像
kinbug [进阶者]
修改2021-07-28 18:01:58
4.6K0
修改2021-07-28 18:01:58
举报
文章被收录于专栏:IT进修之路

前言

        这几天在使用的mybatis-plus的时候,在遇见复杂业务的时候遇见的一些租户过滤问题,面对多表关联查询的时候、自定义sql的时候,或者说一对多的时候,其中一个查询等功能过滤过滤租户的解决方案。

        在一个缓存命中率不高的场景中,分页很多时候不能依赖主数据分页查询再遍历查询的方式来组装数据的时候,就会遇见自定义sql 或者是一对多查询。这个时候如果用mybatis-plus的多租户就会很有问题。

自定义sql分页查询方法:

Mapper.xml
代码语言:javascript
复制
<select id="getPageUser" resultMap="userResult">
	select * from user ${ew.customSqlSegment}
</select>

这里的SQL很简单,根据自己的业务变动sql。${ew.customSqlSegment} 很多人不了解这个哈,就是:Wrapper<Material> queryWrapper 转化后的sql。还不明白的话,继续看...

Mapper.java
代码语言:javascript
复制
@Mapper
public interface UserMapper extends BaseMapper<User> {
	 
	 List<User> getPageUser(@Param(Constants.WRAPPER) Wrapper<Material> queryWrapper);
}

看清楚哦,这里返回的是一个list集合。${ew.customSqlSegment} 就是指的这的queryWrapper,ew就是Constants.WRAPPER的值。

ServiceImpl.java
代码语言:javascript
复制
@Override
public PageInfo<User> getPageUser(PageParam<UserQueryParam> param, Wrapper<User> queryWrapper) {
	// TODO Auto-generated method stub
	PageHelper.startPage(param.getCurrent(), param.getPageSize());
	List<User> list = baseMapper.getPageUser(queryWrapper);
	PageInfo<User> pageInfo = new PageInfo<User>(list);
	return pageInfo;
}

PageParam 就是组装了,当前页码与页行数,UserQueryParam 是查询条件:用于组装在queryWrapper中。

Service.java、Controller.java我就直接省了....

多租户面临的情况:

mybatis-plus 多住户配置:

代码语言:javascript
复制
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;

/**
 * @author miemie
 * @since 2018-08-10
 */
@Configuration
public class MybatisPlusConfig {


    @Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		// 分页插件
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
		// 租户拦截 PaginationInterceptor
		interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler()));
		return interceptor;
	}

	/**
	 * 租戶設置
	 * @return SQL解析过滤
	 */
	private TenantLineHandler tenantLineHandler() {
		return new TenantLineHandler() {
			@Override
			public Expression getTenantId() {
				return new StringValue(getTenantNo());
			}

			// 在数据库中租户关联的字段
			@Override
			public String getTenantIdColumn() {
				return "tenant_no";
			}

			// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
			@Override
			public boolean ignoreTable(String tableName) {
				List<String> list = new ArrayList<String>();
				list.add("user");
				list.add("a_region_city");
				return list.contains(tableName);
			}
		};
	}

}

ignoreTable 就是根据表名进行过滤租户,全表所有的sql都不会拼接租户的sql。

那么某个表单独一个sql怎么取消租户过滤呢?

官方方法:就是在Mapper.java的方法上加如下注解

代码语言:javascript
复制
@InterceptorIgnore(tenantLine = "1")

比如加在Mapper.java的getPageUser方法上:

代码语言:javascript
复制
@Mapper
public interface UserMapper extends BaseMapper<User> {
	 @InterceptorIgnore(tenantLine = "1")
	 List<User> getPageUser(@Param(Constants.WRAPPER) Wrapper<Material> queryWrapper);
}

执行分页查询发现报错,页数对不上,原因是分页查询有:select count(*) from user 的语句,这个是分页工具的能力,如何解决呢? 能通过传递租户ID就不自动拼接sql吗? 能让增加、修改、删除需要租户,而查询不需要吗?

统一回答:当然可以

具体思路与方法输入下:

通过mybatis-plus 多住户配置MybatisPlusConfig可看出租户拦截器是TenantLineInnerInterceptor,查看源码发现有如下方法:

  1. processSelect
  2. processSelectBody
  3. processInsert
  4. processUpdate
  5. processDelete

你可以重写对应的方法,就可以实现sql中某一个方法不加租户拼接举例如下:

代码语言:javascript
复制
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {

	/**
	 * 分页插件
	 *
	 * @return
	 */
	@Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		// 分页插件
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
		// 租户拦截 PaginationInterceptor
		interceptor.addInnerInterceptor(tenantLineInnerInterceptor(tenantLineHandler()));
		return interceptor;
	}

	TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
		return new TenantLineInnerInterceptor(tenantLineHandler) {
			
			//重写查询,租户拦截问题
			@Override
			protected void processSelect(Select select, int index, String sql, Object obj) {
				if (sql.contains("tenant_no")) {
					return;
				}
				processSelectBody(select.getSelectBody());
				List<WithItem> withItemsList = select.getWithItemsList();
				if (!CollectionUtils.isEmpty(withItemsList)) {
					withItemsList.forEach(this::processSelectBody);
				}
			}
		};
	}

	/**
	 * 租戶設置
	 * @return SQL解析过滤
	 */
	private TenantLineHandler tenantLineHandler() {
		return new TenantLineHandler() {
			@Override
			public Expression getTenantId() {
				return new StringValue(getTenantNo());
			}

			// 在数据库中租户关联的字段
			@Override
			public String getTenantIdColumn() {
				return "tenant_no";
			}

			// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
			@Override
			public boolean ignoreTable(String tableName) {
				List<String> list = new ArrayList<String>();
				list.add("user");
				list.add("a_region_city");
				return list.contains(tableName);
			}
		};
	}
}

 现在再去查询发现分页的统计是ok的了,数量也能对的上。

最后说说一对多sql实现

VO代码:

代码语言:javascript
复制
/**
 * @Description: 说明
 * @author: kinbug
 * @date: 2021年07月22日
 */
@Data
public class MaterialVO {
	private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

	@ApiModelProperty(value = "id")
	private Long id;

	@ApiModelProperty(value = "编号")
	private String no;

	@ApiModelProperty(value = "租户编号")
	private String tenantNo;

	@ApiModelProperty(value = "产品名字")
	private String name;

	@ApiModelProperty(value = "产品类别")
	private String categoryNo;

	@ApiModelProperty(value = "商品单位")
	private String unit;

	@ApiModelProperty(value = "产品编号")
	private String serialNo;

	@ApiModelProperty(value = "产品型号")
	private String model;

	@ApiModelProperty(value = "产品规格")
	private String standard;

	@ApiModelProperty(value = "产品状态(0未启用,1启用)")
	private Boolean enabled;

	@ApiModelProperty(value = "制造商名")
	private String mfrs;

	@ApiModelProperty(value = "删除标记(0未删除,1是删除)")
	private Boolean deleteFlag;

	@ApiModelProperty(value = "创建人")
	private String createUno;

	@ApiModelProperty(value = "添加时间")
	@JsonFormat(pattern = DATE_FORMAT)
	private Date createTime;

	@ApiModelProperty(value = "修改人")
	private String updateUno;

	@ApiModelProperty(value = "修改时间")
	@JsonFormat(pattern = DATE_FORMAT)
	private Date updateTime;
	
	@ApiModelProperty(value = "商品条码价格")
	private List<MaterialExtend> materialExtends;
	
	@ApiModelProperty(value = "商品图片")
	private List<MaterialImage> materialImages;

	public void copyMaterial(Material material) {
		BeanUtils.copyProperties(material, this);
	}
}
Mapper.XML
代码语言:javascript
复制
<!-- start查询一个商品信息 -->
	<resultMap type="com.turtle.eos.domian.vo.material.MaterialVO" id="materialResult">
		<result column="id" property="id" />
  		<result column="no" property="no" />
  		<result column="name" property="name" />
  		<result column="category_no" property="categoryNo" />
  		<result column="unit" property="unit" />
  		<result column="serial_no" property="serialNo" />
  		<result column="model" property="model" />
  		<result column="standard" property="standard" />
  		<result column="enabled" property="enabled" />
  		<result column="mfrs" property="mfrs" />
  		<result column="delete_flag" property="deleteFlag" />
  		<result column="tenant_no" property="tenantNo" />
  		<result column="create_time" property="createTime" />
  		<result column="update_time" property="updateTime" />
  		<result column="update_uno" property="updateUno" />
  		<result column="create_uno" property="createUno" />
  		
        <collection property="materialExtends" ofType="com.turtle.eos.entity.material.MaterialExtend" select="queryMaterialExtends" column="no">
	          
    	</collection>
		<collection property="materialImages" ofType="com.turtle.eos.entity.material.MaterialImage" select="queryMaterialImages" column="no">
	          
    	</collection>
    </resultMap>
	<select id="getMaterial" resultMap="materialResult">
		select * from m_material ${ew.customSqlSegment}
	</select>
	
	<select id="queryMaterialExtends" resultType="com.turtle.eos.entity.material.MaterialExtend">
		select * from m_material_extend where delete_flag = 0 and material_no = #{no}
	</select>
	
	<select id="queryMaterialImages" resultType="com.turtle.eos.entity.material.MaterialImage">
		select * from m_material_image where delete_flag = 0 and material_no = #{no}
	</select>
	<!-- end查询一个商品信息 -->

值得注意的是collection中的column是给下一个queryMaterialExtends查询传递的值。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 自定义sql分页查询方法:
      • Mapper.xml
      • Mapper.java
      • ServiceImpl.java
      • Service.java、Controller.java我就直接省了....
    • 多租户面临的情况:
      • 最后说说一对多sql实现
        • Mapper.XML
    相关产品与服务
    访问管理
    访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档