Spring Boot+Postgres实体:基于类型的条件非空约束实现问询
问题解答
1. Javax Constraints 能不能实现?
不行。Javax Constraints(也就是Bean Validation)是应用层的验证逻辑,只能在代码调用验证接口时生效。如果同事直接用INSERT语句操作数据库,这些约束根本管不到,满足不了你要表级约束的需求。
2. Hibernate 可以实现表级条件约束吗?
可以,得靠Hibernate的@Check注解生成数据库层面的CHECK约束,配合PostgreSQL的条件语法就能搞定,而且ddl-auto=true会自动把约束同步到数据库里。
具体怎么做:
第一步:给Message实体加@Check注解
在Message类上添加@Check,写好对应的SQL约束逻辑,要对应嵌入式对象生成的数据库字段名:
@Entity @Table(name = "message") // 这里的字段名要和Hibernate生成的数据库字段一致,比如location的latitude会变成location_latitude @Check(constraints = "((type = 'LOCATION' AND location_latitude IS NOT NULL AND location_longitude IS NOT NULL) OR type != 'LOCATION') AND ((type = 'IMAGE' AND image_url IS NOT NULL AND image_width IS NOT NULL AND image_height IS NOT NULL) OR type != 'IMAGE')") public class Message { // 原有代码不动 public enum MessageType { TEXT, LOCATION, IMAGE, REPLY, LIST, BUTTON } @Id @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") private String id; @Column(nullable = false) private MessageType type; private String text; @Embedded private LocationRequest location; @Embedded private ImageRequest image; @Embedded private ContextRequest reply; }
第二步:确认嵌入式对象的字段映射
比如LocationRequest要正确定义字段:
@Embeddable public class LocationRequest { private Double latitude; private Double longitude; // getter、setter等方法自行补充 }
ImageRequest同理,把需要非空的字段都定义好。
第三步:验证约束是否生成
把spring.jpa.hibernate.ddl-auto设为create或update,启动应用后,去PostgreSQL里查询message表结构,执行\d message就能看到生成的CHECK约束,此时直接INSERT非法数据会被数据库拦截。
注意点:
- 不同数据库的CHECK语法可能不一样,这里是针对PostgreSQL编写的,换数据库需要调整SQL。
ddl-auto=update只会添加新约束,不会修改已有约束,要是需要更新约束,要么手动删除旧约束,要么在开发环境用create-drop。- 嵌入式对象的所有必填字段都要写到CHECK条件里,不能遗漏。
3. 额外建议:应用层也加验证
为了双重保障,最好同时在应用层添加Bean Validation的自定义验证,比如写个验证器:
@Target({TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = MessageTypeValidator.class) public @interface ValidMessageType { String message() default "当前消息类型对应的字段不完整"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } public class MessageTypeValidator implements ConstraintValidator<ValidMessageType, Message> { @Override public boolean isValid(Message message, ConstraintValidatorContext context) { if (Message.MessageType.LOCATION.equals(message.getType())) { LocationRequest loc = message.getLocation(); return loc != null && loc.getLatitude() != null && loc.getLongitude() != null; } if (Message.MessageType.IMAGE.equals(message.getType())) { ImageRequest img = message.getImage(); return img != null && img.getUrl() != null && img.getWidth() != null && img.getHeight() != null; } return true; } }
然后在Message类上添加@ValidMessageType注解,这样应用层保存数据时也会先做验证,加上数据库的约束,形成双重保障。
内容的提问来源于stack exchange,提问作者Marco
相关产品推荐
相关产品推荐

