我有一个实体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> {}
谢谢你的帮助。谢谢。
1条答案
按热度按时间j8ag8udp1#
根本原因是在父实体和子实体中广泛使用级联,这导致了级联链:通过保存一个选项组,一个邀请被保存,一个选项组被再次保存。清理后,它的工作。
我推荐阅读:Hibernate - how to use cascade in relations correctly