【问题标题】:Hibernate/JPA: only one entry can have specific field valueHibernate/JPA:只有一个条目可以有特定的字段值
【发布时间】:2016-03-14 00:25:49
【问题描述】:

我需要一些看起来不那么具体的东西,但无论如何我无法想出好的和复杂的解决方案。

假设我有非常简单的 hibernate/jpa 实体:

@Entity(name="entity")
public class Type {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name;

    @Column(unique = false, nullable = false)
    private boolean defaultType;
}

我需要以某种方式注释 defaultType 字段,以便只有(并且完全)一个持久化实体将此值设为 true。当新实体将此 defaultType 保持为 true 时,必须更改旧实体(具有 defaultType=true)及其 defaultType 值改为假。此外,如果任何实体发生更改(其 defaultType 更改为 true),则应适用相同的规则。

据我所知,这可以在业务逻辑内部(例如在 DAO 层)、使用 DB 触发器或使用休眠拦截器或事件来实现(如果有其他方法,请告诉我)。我尝试使用 DAO 解决方案,但这是一种糟糕的解决方案,因为它可以被绕过,而且对于这种简单的操作来说真的很笨拙。数据库触发器不能用 hibernate/jpa 注释添加(如果我没记错的话),我不知道如何使用 hibernate 拦截器/事件来实现这个功能。

那么,这个问题的最佳解决方案是什么?

【问题讨论】:

  • 已回答,但我想补充一点:想要在数据库中有约束是有充分理由的;那么无论数据如何最终进入数据库,数据都会受到保护。如果从 JPA 的角度来解决这个问题,那么守卫只有在使用 JPA 时才到位。只是一个想法,我不知道你的环境、当前的要求或未来的要求。

标签: java hibernate jpa jakarta-ee jdbc


【解决方案1】:

你需要在JPA中使用Callback方法,例如PreUpdatePostUpdate,例如:

@Entity
@EntityListeners(com.acme.AlertMonitor.class) // set callback method in another class
public class Account {
   Long accountId;
   Integer balance;
   boolean preferred;

   @Id
   public Long getAccountId() { ... }
   ...
   public Integer getBalance() { ... }
    ...
   @Transient 
   public boolean isPreferred() { ... }
   ...

   public void deposit(Integer amount) { ... }
   public Integer withdraw(Integer amount) throws NSFException {... }

   @PreUpdate // callback method in some class 
   protected void validateCreate() {
     if (getBalance() < MIN_REQUIRED_BALANCE)
        throw new AccountException("Insufficient balance to open an
account");
   }

  @PostUpdate  // callback method in some class 
  protected void adjustPreferredStatus() {
      preferred =
      (getBalance() >= AccountManager.getPreferredStatusLevel());
   }
}

// callback method in another class 
public class AlertMonitor {
   @PreUpdate  // callback method in another class
   public void updateAccountAlert(Account acct) {
      Alerts.sendMarketingInfo(acct.getAccountId(), acct.getBalance());
   }
}

更新:关于你的问题,如果我不明白你想要什么,这段代码可能会对你有所帮助:

@Entity(name="entity")
@EntityListeners(com.yourpackage.TypeListner.class)
public class Type {

    ...
@Column(unique = false, nullable = false)
private boolean defaultType;
}

public class TypeListner {

pivate static Type objectWithTrue = null;

public void init() { // call this method when application is started 
    List<Type> results = entityManager
                  .createQuery("from Type", Type.class)
                  .getResultList();
    for(Type type: results) {
         if(type.getDefaultType()) {
             objectWithTrue = type;      
         }  
    }
}

private void changeDefaultType(Type changed) {
    if(changed.getDefaultType()) {
       if(changed != objectWithTrue && objectWithTrue != null) {        
         objectWithTrue.setDefaultType(false);
       }
       objectWithTrue = changed;
   }
}

@PostPresist  
public void newType(Type changed) {
   changeDefaultType(changed);
}

@PostUpdate
public void updateType(Type changed) {
   changeDefaultType(changed);
}

@PreRemove
public void removeType(Type changed) {
if(changed.getDefaultType() && objectWithTrue == changed) {
      objectWithTrue = null;
    }
}

您可以使用listner @PreUpdate 和@PrePresist 并且每次覆盖所有类型对象而不存储任何变量(它的性能不如第一个示例,但更可靠):

@PreUpdate 
void updateType(Type changed) {
    if(changed.getDefaultType()
        List<Type> results = entityManager
                  .createQuery("from Type", Type.class)
                  .getResultList();
       for(Type type: results) {
           if(changed != type && type.getDefaultType()) {
             type.setDefaultType(false);    
           }  
       }
    }
 }

【讨论】:

  • 你能根据这个问题调整你的例子吗?从我的角度来看,使用@PreUpdate 很难实现这一点。 @PostUpdate 在这里也无济于事,因为此时错误的更新已经发生。你也应该提到@PrePersist
  • 不完全是。您的解决方案存在一些问题:缺少初始化(重新启动您的 web 应用程序后,objectWithTrue 将是 null)。 changeDefaultType() 将不起作用,因为 objectWithTrue 仅在它是 != null 时设置,它永远不会设置。即使您解决了这个问题,objectWithTrue.setDefaultType(false); 也不会产生任何影响,因为您的静态实例会在会话结束后立即分离(如果您在 PostUpdate 中调用它就会发生这种情况)。
  • 添加 init() 方法获取所有 Type 对象并找到 deftype = true 并在应用程序启动时调用此方法有什么问题?
  • 将此 init() 示例添加到最后一个示例
  • 我提到的另外两件事呢?这些仍然适用。我想说的是,用EntityListeners 很难实现这一点。
猜你喜欢
  • 1970-01-01
  • 2011-12-17
  • 2017-04-22
  • 1970-01-01
  • 2015-11-07
  • 2016-08-25
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
相关资源
最近更新 更多