spring boot集成mybatis-plus 配置字段自动填充,主键id使用雪花算法,逻辑删除,多租户实现

2022-08-18 14:28:15

一、添加依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

<!--引入阿里的TransmittableThreadLocal -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.12.1</version>
</dependency>

二、编写yaml 配置 文件:application-dev.yaml

#mybatis-plus配置项
mybatis-plus:
    mapperLocations:
    typeAliasesPackage:
    configuration:
        map-underscore-to-camel-case: true
        cache-enabled: true
    global-config:
        db-config:
            #配置全局逻辑删除,指定实体类的逻辑删除字段
            logic-delete-field: delFlag
            logic-delete-value: 1
            logic-not-delete-value: 0
            #配置主键id生成策略采用雪花算法
            id-type: assign-id


#自定义多租户配置
exam:
   tenant:
       #启用多租户
       enable: true
       #指定哪些表不走多租户
       ignoreTables: 
           # - exam_subject
           # - exam_subject_category
       #数据库租户id字段
       column: tenant_id

三、主键id使用雪花算法生成策略

在配置文件上面配置了之后,在对应的实体类的id属性上添加 如下注解

@TableId(value="id",type=IdType.ASSIGN_ID)

四、配置字段自动填充

编写一个自定义类实现 MetaObjectHandler 接口

public class  MateMetaObjectHandler implements MetaObjectHandler{

 
      //此处的createTime 和 modifiedTime 也是对应实体类的属性名称,而非数据库字段名
     @Override
     public void insertFill(MetaObject metaObject){
        this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now()); 
       
          this.strictInsertFill(metaObject,"modifiedTime",LocalDateTime.class,LocalDateTime.now()); 

     }

    
     @Override
     public void updateFill(MetaObject metaObject){
       this.strictUpdateFill(metaObject,"modifiedTime",LocalDateTime.class,LocalDateTime.now()); 
     }


}

 然后需要在需要进行自动填充的实体类上添加如下注解

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
@TableFidld(value="create_time",fill = FieldFill.INSERT)
private LocalDateTime createTime;


@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
@TableFidld(value="modified_time",fill = FieldFill.INSERT_UPDATE)
private LocalDateTime modifiedTime;

五、配置分页,多租户,防止全表更新或删除插件

package com.dzx.ccb.exam.exammanage.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
//import org.springframework.cloud.context.config.annotation.RefreshScope;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author DuanZhaoXu
 * @ClassName:
 * @Description:
 * @date 2021年06月03日 17:36:15
 */
@Getter
@Setter
//@RefreshScope
@ConfigurationProperties(prefix = "exam.tenant")
public class TenantProperties {

    /**
     * 是否开启租户模式
     */
    private Boolean enable = false;

    /**
     * 需要排除的多租户的表
     */
    private List<String> ignoreTables = new ArrayList();

    /**
     * 多租户字段名称
     */
    private String column = "tenant_id";

//    /**
//     * 排除不进行租户隔离的sql
//     * 样例全路径:vip.mate.system.mapper.UserMapper.findList
//     */
//    private List<String> ignoreSqls = new ArrayList<>();
}
package com.dzx.ccb.exam.exammanage.config;

import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;

/**
 * 多租户Holder
 *
 * @author pangu
 * @since 2020-9-8
 */
@UtilityClass
public class TenantContextHolder {

	/**
	 * 支持父子线程之间的数据传递
	 */
	private final ThreadLocal<String> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();

	/**
	 * TTL 设置租户ID<br/>
	 * <b>谨慎使用此方法,避免嵌套调用。尽量使用 {@code TenantBroker} </b>
	 *
	 * @param tenantId 租户ID
	 */
	public void setTenantId(String tenantId) {
		THREAD_LOCAL_TENANT.set(tenantId);
	}

	/**
	 * 获取TTL中的租户ID
	 *
	 * @return String
	 */
	public String getTenantId() {
		return THREAD_LOCAL_TENANT.get();
	}

	/**
	 * 清除tenantId
	 */
	public void clear() {
		THREAD_LOCAL_TENANT.remove();
	}
}
package com.dzx.ccb.exam.exammanage.config;

/**
 * 多租户常量
 * @author pangu
 * @date 2020-9-7
 */
public interface TenantConstant {

    /**
     * header 中租户ID
     */
    String MATE_TENANT_ID = "mate-tenant";

    /**
     * 租户id参数
     */
    String MATE_TENANT_ID_PARAM = "tenantId";

    /**
     * 租户ID
     */
    String TENANT_ID_DEFAULT = "1";

}
package com.dzx.ccb.exam.exammanage.config;

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 多租户配置中心
 *
 * @author pangu
 * @Date 2020-9-7
 */
@Configuration
@AllArgsConstructor
@AutoConfigureBefore(MybatisPlusConfiug.class)
@EnableConfigurationProperties(TenantProperties.class)
public class TenantConfiguration {

	private final TenantProperties tenantProperties;

	/**
	 * 新多租户插件配置,一缓和二缓遵循mybatis的规则,
	 * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
	 * 避免缓存万一出现问题
	 *
	 * @return TenantLineInnerInterceptor
	 */
	@Bean
	public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
		return new TenantLineInnerInterceptor(new TenantLineHandler() {
			/**
			 * 获取租户ID
			 * @return Expression
			 */
			@Override
			public Expression getTenantId() {
				String tenant = TenantContextHolder.getTenantId();
				if (tenant != null) {
					return new StringValue(TenantContextHolder.getTenantId());
				}
				return new NullValue();
			}

			/**
			 * 获取多租户的字段名
			 * @return String
			 */
			@Override
			public String getTenantIdColumn() {
				return tenantProperties.getColumn();
			}

			/**
			 * 过滤不需要根据租户隔离的表
			 * 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
			 * @param tableName 表名
			 */
			@Override
			public boolean ignoreTable(String tableName) {
				return tenantProperties.getIgnoreTables().stream().anyMatch(
						(t) -> t.equalsIgnoreCase(tableName)
				);
			}
		});
	}
}
package com.dzx.ccb.exam.exammanage.config;

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;


import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 租户上下文过滤器
 *
 * @author pangu
 * @date 2020-9-7
 */
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties(TenantProperties.class)
@AllArgsConstructor
public class TenantContextHolderFilter extends GenericFilterBean {

    private final TenantProperties tenantProperties;

    @Override
    @SneakyThrows
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (tenantProperties.getEnable()) {
            try {
                //优先取请求参数中的tenantId值
                String tenantId = request.getHeader(TenantConstant.MATE_TENANT_ID);
                if (StringUtils.hasLength(tenantId)) {
                    TenantContextHolder.setTenantId(tenantId);
                } else {
                    TenantContextHolder.setTenantId(TenantConstant.TENANT_ID_DEFAULT);
                }
                log.info("获取到的租户ID为:{}", TenantContextHolder.getTenantId());
                filterChain.doFilter(request, response);
            } finally {
                TenantContextHolder.clear();
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

最后 编写 MybatisPlusConfig类 把上面的配置类进行整合

package com.dzx.ccb.exam.exammanage.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.LongValue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.util.Arrays;
import java.util.List;

/**
 * @author 80004819
 * @ClassName:
 * @Description:
 * @date 2020年08月29日 09:43:44
 */
@AllArgsConstructor
@EnableTransactionManagement
@Configuration
@MapperScan(value = "com.ccb.cloud.cu.exam.admin.mapper")
public class MybatisPlusConfiug {

    private final TenantProperties tenantProperties;

    private final TenantLineInnerInterceptor tenantLineInnerInterceptor;

    /**
     * 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
     */
    private static final Long MAX_LIMIT = 1000L;

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,
     * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
     * 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        boolean enableTenant = tenantProperties.getEnable();
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //如果启用了多租户,则把自定义的租户插件添加进来
        if (enableTenant) {
            interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
        }
        //分页插件: PaginationInnerInterceptor
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(MAX_LIMIT);
        //防止全表更新与删除插件: BlockAttackInnerInterceptor
        BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor);
        return interceptor;
    }

    /**
     * 自动填充数据
     */
    @Bean
//    @ConditionalOnMissingBean(MateMetaObjectHandler.class)
    public MateMetaObjectHandler mateMetaObjectHandler() {
        return new MateMetaObjectHandler();
    }


    /**
     * mybatis-plus 乐观锁拦截器
     */
    @Bean
    public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }

}

如果需要对某一个指定的mapper 或者 mapper 层的 sql 进行 不走多租户模式,则可以在mapper 或者 mapper.method 上 添加

true 表示不走插件(在配置了插件的情况下,不填则默认表示为false)
@InterceptorIgnore(tenantLine="true")
  • 作者:小米吃辣椒2022
  • 原文链接:https://dqy20200405.blog.csdn.net/article/details/117693555
    更新时间:2022-08-18 14:28:15