由于NotNull列设置为空,因此出现Spring JPA数据完整性违规异常

csga3l58  于 2022-11-14  发布在  Spring
关注(0)|答案(1)|浏览(172)

我有一个实体OptionGroup,它与其他实体有关系。其中一个关系是制造麻烦:OptionGroup有一个所有者(用户)。当我删除OptionGroup时,由于某种原因,JPA提供程序hib试图将OptionGroup的owner_id设置为null,这违反了为所有者字段定义的NotNull约束。我不知道为什么hib要这样做,但我可以在日志中看到它这样做:

2022-08-30 20:17:53.008 DEBUG 17488 --- [nio-8081-exec-1] org.hibernate.SQL                        : update option_group set description=?, option_group_name=?, owner_id=? where id=?
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [null]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Männliche Vornamen]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [null]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [BIGINT] - [20001]
2022-08-30 20:17:53.012  WARN 17488 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23502
2022-08-30 20:17:53.012 ERROR 17488 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: NULL value in column »owner_id« of relation »option_group« violates Not-Null-Constraint

如果我在owner字段上定义了级联删除,我可以想象Hibernate可能会先删除owner,将OptionGroup中的owner设置为null,然后删除OptionGroup --尽管先将owner设置为null,然后删除OptionGroup没有多大意义......
你知道为什么hib要把owner_id设置为null吗?顺便说一句,如果我删除NotNull约束,行为就和预期的一样了:将删除OptionGroup并保留用户(所有者)。
这是选项组类:

@Entity
@Table(name = "option_group"/*, uniqueConstraints = {
        @UniqueConstraint(columnNames = {  "owner_id", "option_group_name" }) }*/)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class OptionGroup {

    /**
     * Id of the Option Group. Generated by the database
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * Name of the Option Group. Unique in the context of a user.
     */
    @NotBlank(message = "Option Group name is mandatory")
    @Column(name = "option_group_name")
    private String optionGroupName;

    /**
     * Description for the Option Group
     */
    private String description;

    /**
     * User that is the owner of the Option Group.
     */
    @NotNull(message = "Owner cannot be null")
    @ManyToOne(fetch = FetchType.LAZY, cascade={CascadeType.PERSIST})
    @JoinColumn(name = "ownerId")
    private User owner;

    /**
     * List of options that belong to the Option Group.
     */
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "optionGroup", orphanRemoval = true)
    @NotEmpty(message = "Options cannot be empty")
    private List<Option> options;

    /**
     * List of invitations that belong to the Option Group.
     */
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "optionGroup", orphanRemoval = true)
    private List<Invitation> invitations;

    @Override
    public int hashCode() {
        return Objects.hash(description, id, optionGroupName,
                options == null ? null : options.stream().map(option -> option.getId()).toList(), owner,
                invitations == null ? null : invitations.stream().map(invitation -> invitation.getId()).toList());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        OptionGroup other = (OptionGroup) obj;
        return Objects.equals(description, other.description) && Objects.equals(id, other.id)
                && Objects.equals(optionGroupName, other.optionGroupName)
                && Objects.equals(options == null ? null : options.stream().map(option -> option.getId()).toList(),
                        other.options == null ? null : other.options.stream().map(option -> option.getId()).toList())
                && Objects.equals(owner, other.owner)
                && Objects.equals(
                        invitations == null ? null
                                : invitations.stream().map(invitation -> invitation.getId()).toList(),
                        other.invitations == null ? null
                                : other.invitations.stream().map(invitation -> invitation.getId()).toList());
    }

    @Override
    public String toString() {
        return "OptionGroup [id=" + id + ", optionGroupName=" + optionGroupName + ", description=" + description
                + ", owner=" + owner + ", options="
                + (options == null ? null : options.stream().map(option -> option.getId()).toList()) + ", invitations="
                + (invitations == null ? null : invitations.stream().map(invitation -> invitation.getId()).toList())
                + "]";
    }
}

如您所见,所有者的级联被限制为持久。如果创建了OptionGroup,则也会创建所有者User。但如果删除了OptionGroup,则不应删除所有者User。
这是User类别:

/**
 * Entity that represents a user
 * 
 * Primary key: id
 */
@Entity
@Table(name = "usert", uniqueConstraints = {
        @UniqueConstraint(columnNames = { "email"}) })
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
    /**
     * Id of the User. Generated by the database
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * Email address of the invitee.
     */
    @Email(message = "Email is not valid", regexp = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])")
    private String email;

    /**
     * Option Groups of which the user is the owner.
     */
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner", orphanRemoval = true)
    private List<OptionGroup> ownedOptionGroups;

    /**
     * Invitations of the user.
     */
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "invitee", orphanRemoval = true)
    private List<Invitation> invitations;

}

这是触发删除的类

/**
 * Service related to Option Groups.
 */
@Service
@Transactional
@AllArgsConstructor
public class OptionGroupService {

    /**
     * Repository used to access Option Groups.
     */
    @Autowired
    private OptionGroupRepository optionGroupRepository;

    /**
     * Deletes the Option Group with the given id.
     * 
     * @param id Id of the Option Group to delete.
     * @throws ObjectWithNameDoesNotExistException
     * @throws ObjectWithIdDoesNotExistException
     */
    public void deleteOptionGroupById(Long id) throws ObjectWithIdDoesNotExistException {
        if (optionGroupRepository.existsById(id)) {
            optionGroupRepository.deleteById(id);
        } else {
            throw new ObjectWithIdDoesNotExistException("Option Group", id);
        }
    }

}

和存储库

public interface OptionGroupRepository extends JpaRepository<OptionGroup, Long> {}

谢谢你的帮助。谢谢。

j8ag8udp

j8ag8udp1#

根本原因是在父实体和子实体中广泛使用级联,这导致了级联链:通过保存一个选项组,一个邀请被保存,一个选项组被再次保存。清理后,它的工作。
我推荐阅读:Hibernate - how to use cascade in relations correctly

相关问题