Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate/JPA: only one entry can have specific field value

I need something that seems not so specific but anyway I was unable to come up with nice and sophisticated solution.

Say I have very simple hibernate/jpa entity:

@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;
}

What i need is to somehow annotate defaultType field so only (and exactly) one persisted entity have this value as true. When new entity get persisted with this defaultType as true, the old one (with defaultType=true) entity has to be altered and its defaultType value changed to false. Also if any entity get changed (its defaultType got changed to true), same rule should apply.

As far I know this can be achieved inside business logic (e.g. in DAO layer), with DB trigger or with hibernates interceptor or event (If there is another way, please let me know). I tried with DAO solution but it's kind of bad solution because it can be bypassed and it is really clumsy for such simple operation. DB triggers can not be added with hibernate/jpa annotations (if I am not mistaken) and i am not sure how to make this functionality with hibernate interceptors/events.

So, what is best solution for this problem?

like image 266
Andrija Avatar asked Dec 08 '15 21:12

Andrija


Video Answer


1 Answers

You need use Callback method in JPA, for example PreUpdate or PostUpdate, for instance:

@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());
   }
}

Update: About your question, If I undestand what you want, this code may help you:

@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;
    }
}

OR

You can use listner @PreUpdate and @PrePresist and every times overwrite all Type objects without store any variable (it isn't so good for perfomance then first example, but more reliable):

@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);    
           }  
       }
    }
 }
like image 189
Slava Vedenin Avatar answered Oct 23 '22 12:10

Slava Vedenin