我有一个具有一对多关联的hibernate实体:
@Entity
public class Parent {
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
@Cascade(CascadeType.ALL)
private Set<Child> children = new HashSet<Child>();
@Version
private Date version;
}
@Entity
public class Child {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PARENT_ID")
private Parent parent;
@Basic
private String key;
}
*为清晰起见,删除了一些注释
子实体映射到具有复合主键(KEY和PARENT_ID)的表上。问题是当两个用户将同一个Child(使用相同的密钥)添加到同一个Parent时,级联保存(session.saveOrUpdate(父))因Child的主键违规而不是乐观锁定失败而失败。
如果用户除了集合之外还更改了Parent实体中的其他一些属性,则乐观锁可以正常工作。
我可以向Parent类添加一些虚构属性,并在每次集合更改时更改它,它会完成这个技巧,但它看起来像是黑客。
或者我可以将复合主键替换为代理主键(通过添加@Id)。
问题是:在这种情况下实施乐观锁定的推荐方法是什么?
可能与Hibernate @Version causing database foreign key constraint failure有关。
答案 0 :(得分:1)
只有单向集合更改才会传播到父实体版本as explained in this article。因为您正在使用双向关联,所以;WITH var_varattr (FK_Variation_ID) As (
SELECT FK_Variation_ID
FROM T_Variation_VariationAttribute
WHERE ID IN (
SELECT TOP 100 PERCENT FK_Variation_VariationAttribute_ID
FROM T_Artikel_T_Variation
WHERE FK_Artikel_ID = 46
ORDER BY Position ASC
)
)
SELECT Id,
Name
FROM T_Variation
WHERE ID IN (
SELECT FK_Variation_ID
FROM var_varattr
)
方将控制此关联,因此在父方集合中添加/删除实体不会影响父实体版本。
但是,您仍然可以将更改从子实体传播到父实体。这要求您在修改子实体时传播OPTIMISTIC_FORCE_INCREMENT锁。
This article详细解释了您应该如何实现这样一个用例。
简而言之,您需要让所有实体实现@ManyToOne
接口:
RootAware
然后,您需要两个事件监听器:
public interface RootAware<T> {
T root();
}
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
private Long id;
private String title;
@Version
private int version;
//Getters and setters omitted for brevity
}
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment
implements RootAware<Post> {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
//Getters and setters omitted for brevity
@Override
public Post root() {
return post;
}
}
@Entity(name = "PostCommentDetails")
@Table(name = "post_comment_details")
public class PostCommentDetails
implements RootAware<Post> {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId
private PostComment comment;
private int votes;
//Getters and setters omitted for brevity
@Override
public Post root() {
return comment.getPost();
}
}
和
public static class RootAwareInsertEventListener
implements PersistEventListener {
private static final Logger LOGGER =
LoggerFactory.getLogger(RootAwareInsertEventListener.class);
public static final RootAwareInsertEventListener INSTANCE =
new RootAwareInsertEventListener();
@Override
public void onPersist(PersistEvent event) throws HibernateException {
final Object entity = event.getObject();
if(entity instanceof RootAware) {
RootAware rootAware = (RootAware) entity;
Object root = rootAware.root();
event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT);
LOGGER.info("Incrementing {} entity version because a {} child entity has been inserted", root, entity);
}
}
@Override
public void onPersist(PersistEvent event, Map createdAlready)
throws HibernateException {
onPersist(event);
}
}
您可以注册如下:
public static class RootAwareInsertEventListener
implements PersistEventListener {
private static final Logger LOGGER =
LoggerFactory.getLogger(RootAwareInsertEventListener.class);
public static final RootAwareInsertEventListener INSTANCE =
new RootAwareInsertEventListener();
@Override
public void onPersist(PersistEvent event) throws HibernateException {
final Object entity = event.getObject();
if(entity instanceof RootAware) {
RootAware rootAware = (RootAware) entity;
Object root = rootAware.root();
event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT);
LOGGER.info("Incrementing {} entity version because a {} child entity has been inserted", root, entity);
}
}
@Override
public void onPersist(PersistEvent event, Map createdAlready)
throws HibernateException {
onPersist(event);
}
}
然后通过Hibernate配置属性提供public class RootAwareEventListenerIntegrator
implements org.hibernate.integrator.spi.Integrator {
public static final RootAwareEventListenerIntegrator INSTANCE =
new RootAwareEventListenerIntegrator();
@Override
public void integrate(
Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry =
serviceRegistry.getService( EventListenerRegistry.class );
eventListenerRegistry.appendListeners(EventType.PERSIST, RootAwareInsertEventListener.INSTANCE);
eventListenerRegistry.appendListeners(EventType.FLUSH_ENTITY, RootAwareUpdateAndDeleteEventListener.INSTANCE);
}
@Override
public void disintegrate(
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
//Do nothing
}
}
:
RootAwareFlushEntityEventListenerIntegrator
现在,当您修改configuration.put(
"hibernate.integrator_provider",
(IntegratorProvider) () -> Collections.singletonList(
RootAwareEventListenerIntegrator.INSTANCE
)
);
实体时:
PostCommentDetails
还修改了父PostCommentDetails postCommentDetails = entityManager.createQuery(
"select pcd " +
"from PostCommentDetails pcd " +
"join fetch pcd.comment pc " +
"join fetch pc.post p " +
"where pcd.id = :id", PostCommentDetails.class)
.setParameter("id", 2L)
.getSingleResult();
postCommentDetails.setVotes(15);
实体版本:
Post
答案 1 :(得分:0)
首先,我认为您需要声明主键并定义PK的生成方式。 示例:
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
然后,将新子项添加到父级的最佳方法应该是这样(在父级方面):
public Child addChild() {
Child child = new Child()
if (childList== null) {
childList= new ArrayList<childList>();
}
child.setparent(this);
childList.add(child);
return child;
}
当孩子已经存在时,只需做同样的事情,但不要创建新的孩子。
我认为它应该解决你的一些问题。