【问题标题】:How to specify constraint for exception如何为异常指定约束
【发布时间】:2022-01-18 09:22:45
【问题描述】:

我有一个 spring 应用程序,并为我的用户实体添加了用户名和电子邮件的唯一约束。因此,用户不能使用相同的用户名或电子邮件创建多个帐户。当有人使用已经存在的用户名创建新用户时,我的应用程序会抛出 ConstraintViolationException

我的异常详情:

org.postgresql.util.PSQLException: 错误: 重复键值违反 唯一约束“ukdfui7gxngrgwn9ewee3ogtgym”详细信息:关键 (用户名) = (dev123) 已经存在。

我的用户实体:

@Table(name = "usr", uniqueConstraints={
@UniqueConstraint(columnNames ={"username"}),
@UniqueConstraint(columnNames={"email"})})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="usr_seq")
    @SequenceGenerator(name = "usr_seq", sequenceName = "usr_seq", initialValue = 1, allocationSize=1)
    @Column(name="id")
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "role")
    private Role role;

    @Column(name = "email")
    private String email;

    @Column(name = "status")
    private Status status;

//    @OneToMany(mappedBy = "author")
//    private List<Message> messages;

//    @OneToMany(mappedBy = "author")
//    private List<Comment> comments;

    @OneToOne
    @JoinColumn(name = "gender_id")
    private Gender gender;

    @OneToOne
    @JoinColumn(name = "address_id")
    private Address address;

    @ManyToMany
    @JoinTable(
            name = "user_subscriptions",
            joinColumns =  @JoinColumn(name = "channel_id") ,
            inverseJoinColumns =  @JoinColumn(name = "subscriber_id"))
    private Set<User> subscribers;

    @ManyToMany
    @JoinTable(
            name = "user_subscriptions",
            joinColumns =  @JoinColumn(name = "subscriber_id") ,
            inverseJoinColumns =  @JoinColumn(name = "channel_id"))
    private Set<User> subscriptions;

    @OneToOne
    @JoinColumn(name = "file_id")
    private FileEntity fileEntity;

}

我的用户表:

create table usr
(
    id         bigint not null
        constraint usr_pkey
            primary key,
    email      varchar(255)
        constraint ukg9l96r670qkidthshajdtxrqf
            unique,
    first_name varchar(255),
    last_name  varchar(255),
    password   varchar(255),
    role       integer,
    status     integer,
    username   varchar(255)
        constraint ukdfui7gxngrgwn9ewee3ogtgym
            unique,
    address_id integer
        constraint fkilsqnqkb7dlk6s5gqedb6lk3r
            references address,
    file_id    bigint
        constraint fkabswqn807logqymtak5wfktfr
            references file,
    gender_id  integer
        constraint fkp89wdnbeu22hjl41g38rk8a1q
            references gender
);

alter table usr
    owner to postgres;

我尝试在 @RestControllerAdvice 类中处理该异常:

@ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolationException(){
        ExceptionDetails exceptionDetails = new ExceptionDetails(Errors.ERROR9.getMessage(),timestamp);
        return new ResponseEntity<>(exceptionDetails,HttpStatus.FORBIDDEN);
    }

但这会捕获所有约束问题(不是 null 违规、主键违规等),我只想为我的用户名和电子邮件捕获特定的约束违规

【问题讨论】:

  • 不确定是否可以创建要抛出的自定义约束异常。你读过这个stackoverflow.com/questions/9842672/… 吗?
  • 仅仅因为在持久层中引发了异常,并不一定意味着您必须在任何地方传播该异常。即使您可以创建自定义异常,在这种情况下也不适合抛出新异常。在尝试创建数据库中已经存在的用户时,您可以简单地忽略该异常并继续前进。除了记录失败的尝试之外,应用程序没有理由继续执行,就好像什么都没发生一样。

标签: java spring postgresql hibernate exception


【解决方案1】:

如果您依赖 Hibernate Validator,则无法执行自定义异常。

要解释原因,请看监听所有 Create/Update/Delete 事件的 BeanValidationEventHandler,它使用 Validator validate 方法并且只处理 ConstraintViolation

private <T> void validate(T object, EntityMode mode, EntityPersister persister,
                              SessionFactoryImplementor sessionFactory, GroupsPerOperation.Operation operation) {
        if ( object == null || mode != EntityMode.POJO ) {
            return;
        }
        TraversableResolver tr = new HibernateTraversableResolver(
                persister, associationsPerEntityPersister, sessionFactory
        );
        Validator validator = factory.usingContext()
                .traversableResolver( tr )
                .getValidator();
        final Class<?>[] groups = groupsPerOperation.get( operation );
        if ( groups.length > 0 ) {
            final Set<ConstraintViolation<T>> constraintViolations = validator.validate( object, groups );
            if ( constraintViolations.size() > 0 ) {
                Set<ConstraintViolation<?>> propagatedViolations =
                        new HashSet<ConstraintViolation<?>>( constraintViolations.size() );
                Set<String> classNames = new HashSet<String>();
                for ( ConstraintViolation<?> violation : constraintViolations ) {
                    LOG.trace( violation );
                    propagatedViolations.add( violation );
                    classNames.add( violation.getLeafBean().getClass().getName() );
                }
                StringBuilder builder = new StringBuilder();
                builder.append( "Validation failed for classes " );
                builder.append( classNames );
                builder.append( " during " );
                builder.append( operation.getName() );
                builder.append( " time for groups " );
                builder.append( toString( groups ) );
                builder.append( "\nList of constraint violations:[\n" );
                for (ConstraintViolation<?> violation : constraintViolations) {
                    builder.append( "\t" ).append( violation.toString() ).append("\n");
                }
                builder.append( "]" );

                throw new ConstraintViolationException(
                        builder.toString(), propagatedViolations
                );
            }
        }
    }

https://github.com/hibernate/hibernate-orm/blob/main/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java


Validator 接口也处理 ConstraintViolation:

public interface Validator {

    /**
     * Validates all constraints on {@code object}.
     *
     * @param object object to validate
     * @param groups the group or list of groups targeted for validation (defaults to
     *        {@link Default})
     * @return constraint violations or an empty set if none
     * @throws IllegalArgumentException if object is {@code null}
     *         or if {@code null} is passed to the varargs groups
     * @throws ValidationException if a non recoverable error happens
     *         during the validation process
     */
    <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);

【讨论】:

    【解决方案2】:

    在保存用户之前,您可以检查给定的用户名是否已经存在。如果它已经存在,则不要保存用户并要求输入另一个用户名,否则保存用户。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-16
      • 2019-10-28
      • 1970-01-01
      • 2015-11-09
      • 1970-01-01
      • 1970-01-01
      • 2019-02-04
      • 1970-01-01
      相关资源
      最近更新 更多