添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

该问题是如何引起的?(确定最新版也有问题再提!!!)

添加新注解 @unique
我的业务需求比较特殊,数据库除了主键约束外基本没有其他约束,一些约束都是在java程序里面实现的.例如校验某个字段的唯一性(希望官方也加一个吧)

package com.example.unique.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.unique.common.annotation.unique.Unique;
import lombok.Data;
 * @author anganing
 * @version 1.0.0
 * @date 2021/7/31 10:56
@Data
@TableName("user")
@Unique(tableName = "user", columns = {"username", "name"}, message = "用户名或姓名已存在!")
public class UserDO {
	@TableId(type = IdType.AUTO)
	private Long id;
	private String username;
	private String password;
	private String name;
package com.example.unique.common.annotation.unique;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
 * 字段唯一约束注解
 * @author anganing
 * @version 1.0.0
 * @date 2021/7/31 11:19
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Constraint(validatedBy = {UniqueValidator.class})
public @interface Unique {
	 * 实体对应的表名
	String tableName() default "";
	 * 唯一字段列名(数据库字段名)
    String[] columns() default {};
	 * 校验不通过消息提示
	String message() default "已存在该字段!";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
package com.example.unique.common.annotation.unique;
import lombok.SneakyThrows;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
 * @author anganing
 * @version 1.0.0
 * @date 2021/7/31 12:15
public class UniqueValidator implements ConstraintValidator<Unique, Object> {
	@SneakyThrows
	@Override
	public boolean isValid(Object obj, ConstraintValidatorContext context) {
		Class<?> aClass = obj.getClass();
		Field[] fields = aClass.getDeclaredFields();
		// 用户提交的id
		Long id = null;
		// 唯一约束列名和值
		Map<String, Object> uniqueColumnsMap = new HashMap<>(3);
		// 获取用户提交的id值, 唯一约束字段名及值
        for (Field field : fields) {
	        field.setAccessible(true);
	        String fieldName = field.getName();
	        if ("id".equals(fieldName)) {
		        id = (Long) field.get(obj);
            for (String column : columns) {
	            if (Objects.equals(column, fieldName)) {
		            uniqueColumnsMap.put(camel2Underline(column), field.get(obj));
		// 检查唯一约束字段是否重复
		boolean isUnique = false;
		for (String column : uniqueColumnsMap.keySet()) {
			// 查询sql select id from tableName where column='value' limit 1
			String sb = "select id from " + tableName + " where " + column + "=" + "'" + uniqueColumnsMap.get(column) + "'" + " limit 1";
			List<Long> dbIds = jdbcTemplate.queryForList(sb, Long.class);
			// 如果数据库的id和用户提交的id相等则认为该字段唯一
			if (dbIds.isEmpty() || Objects.equals(dbIds.get(0), id) || Objects.equals(id, null)) {
				isUnique = true;
			}else {
				isUnique = false;
				break;
        // 主观认为用户输入的都是不唯一的
		return isUnique;
	@Override
	public void initialize(Unique unique) {
		this.tableName = unique.tableName();
		this.columns = unique.columns();
	 * 驼峰转下划线
	 * @param str
	 * @return
	public String camel2Underline(String str) {
		if (str == null || str.trim().isEmpty()){
			return "";
		int len = str.length();
		StringBuilder sb = new StringBuilder(len);
		sb.append(str.substring(0, 1).toLowerCase());
		for (int i = 1; i < len; i++) {
			char c = str.charAt(i);
			if (Character.isUpperCase(c)) {
				sb.append("_");
				sb.append(Character.toLowerCase(c));
			} else {
				sb.append(c);
		return sb.toString();
	 * 实体对应的表名
	private String tableName;
	 * 唯一字段列名(数据库字段名)
	private String[] columns;
	 * jdbcTemplate
	@Resource
	private JdbcTemplate jdbcTemplate;

重现步骤(如果有就写完整)

  • 优化(逻辑删除和普通字段)
  • public class UniqueValidator implements ConstraintValidator<Unique, Object> {
        //处理逻辑删除
        private static final String DELETE_FLAG = "deleteFlag";
        @Autowired
        private DataSource dataSource;
        @SneakyThrows
        @Override
        public boolean isValid(Object obj, ConstraintValidatorContext context) {
            Class<?> aClass = obj.getClass();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(aClass);
            String tableName = tableInfo.getTableName();
            //如果有逻辑删除列,则增加到查询中
            String sqlLoginDelete = tableInfo.getLogicDeleteSql(true, true);
            String idColumn = tableInfo.getKeyProperty();
            // field列表中竟然不包含keyfield的信息
            Object id = null;//tableInfo.getFieldList().stream().filter(r->r.getColumn().equalsIgnoreCase(tableInfo.getKeyColumn())).findFirst().get().getField().get(obj);
            List<Field> fields = new ArrayList<>();
            fields.addAll(Arrays.asList(aClass.getDeclaredFields()));
            if (!Objects.isNull(aClass.getSuperclass()))
                fields.addAll(Arrays.asList(aClass.getSuperclass().getDeclaredFields()));
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.getName().equalsIgnoreCase(tableInfo.getKeyColumn())) {
                    id = field.get(obj);
                    break;
            // 检查唯一约束字段是否重复
            boolean isUnique = false;
            for (UniqueConfig config : configs) {
                // 唯一约束列名和值
                List<FieldWhere> uniqueColumnsMap = new ArrayList<>();
                for (String column : config.getColumns()) {
                    TableFieldInfo tableField = tableInfo.getFieldList().stream().filter(r -> r.getColumn().equalsIgnoreCase(column)).findFirst().orElse(null);
                    //不在列表中的都是未绑定数据库字段
                    if (Objects.isNull(tableField)) {
                        uniqueColumnsMap.add(FieldWhere.of(camel2Underline(column), "=?", tableField.getField().get(obj)));
                    } else {
                        uniqueColumnsMap.add(FieldWhere.of(tableField.getProperty(), "=?", tableField.getField().get(obj)));
                //组合条件查询,出错立即跳出
                if (!Objects.isNull(id))
                    uniqueColumnsMap.add(FieldWhere.of(idColumn, "<>?", id));
                String sql = String.format("select 1 from %s where %s   %s limit 1;", tableName,
                        uniqueColumnsMap.stream().map(r -> r.name + r.op).collect(Collectors.joining(" and ")),
                        sqlLoginDelete
                //多数据源支持
                JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
                //List<Long> dbIds = jdbcTemplate.queryForList(sql, paras, Long.class);
                List<Long> dbIds = jdbcTemplate.queryForList(sql, uniqueColumnsMap.stream().map(r -> r.v).toArray(), Long.class);
                if (dbIds.isEmpty()) //不存在另一个符合条件的,可以认为是唯一的
                    continue;
                Stream<FieldWhere> mm = uniqueColumnsMap.stream().filter(r -> !r.name.equalsIgnoreCase(idColumn));
                context.disableDefaultConstraintViolation();//禁用默认的message的值
               //重新添加错误提示语句,通过MSG向调用方返回冲突的值,消息包装在外部进行: name 是检查的名称(多语言键值),一般可以认为是第一个列名
                String outParas = JSON.toJSONString(new String[]{
                        Strings.isNullOrEmpty(config.name) ? mm.map(r -> r.name).collect(Collectors.toList()).get(0) : L.t(getRequestLang(), config.name),
                        mm.map(r -> r.v + "").collect(Collectors.joining(","))});
                context.buildConstraintViolationWithTemplate(outParas).addConstraintViolation();
                return false;
            return true;
        static String getRequestLang() {
            String lang = CommonServletUtil.getRequest().getHeader("lang");
            if (Strings.isNullOrEmpty(lang))
                return "en";
                return lang.toLowerCase(Locale.ROOT).replace("_", "-");
        @Override
        public void initialize(Unique unique) {
            configs = new UniqueConfig[unique.columns().length];
            for (int i = 0; i < unique.columns().length; i++) {
                configs[i] = UniqueConfig.of(unique.names()[i], unique.columns()[i].split(","));
    //        this.tableName = unique.tableName();
    //        this.columns = unique.columns();
    ////        this.msg = unique.message();
    //        this.name = unique.name();
         * 驼峰转下划线
         * @param str
         * @return
        public String camel2Underline(String str) {
            if (str == null || str.trim().isEmpty()) {
                return "";
            int len = str.length();
            StringBuilder sb = new StringBuilder(len);
            sb.append(str.substring(0, 1).toLowerCase());
            for (int i = 1; i < len; i++) {
                char c = str.charAt(i);
                if (Character.isUpperCase(c)) {
                    sb.append("_");
                    sb.append(Character.toLowerCase(c));
                } else {
                    sb.append(c);
            return sb.toString();
        UniqueConfig[] configs;
    
    //每个字段表示一个unique检查,如果unique由多个列组成,则使用逗号分隔,如{"no","name,time"}
    @Unique(columns = {"CardNo","CardName"}, names=  { R.Common.TableColumn.CardNo,R.Common.TableColumn.CardName })
    
     @Override
        public boolean save(T entity) {
            if(validator.supports(this.entityClass)) {
                BindException errors = new BindException(entity, this.entityClass.getSimpleName());
                validator.validate(entity, errors);
                if(errors.hasErrors()){
                    throw CommonException.of(R.Common.Global.WelComText);
            return super.save(entity);
            //https://www.cnblogs.com/niugang0920/p/12689224.html
    
    # 生成的语句
     use patroldb_GH000F ; 
    select 1 from card where cardNo='005FFEC2' and id<>'1686190791473471490'    AND delete_flag='0' limit 1;
    # 出错提示
    >>> 卡号已存在,005FFEC2,