3

I have a form to fill a POJO called Father. Inside it, I have a FotoFather field.

When I save a new Father, I save automatically the object FotoFather (with Hibernate ORM pattern).

FotoFather.fotoNaturalUrl must be filled with the value of Father.id and here is the problem!

When i'm saving Father on the db, of course I still haven't Father.id value to fill FotoFather.fotoNaturalUrl. How can I solve this problem?

Thank you

@Entity
@Table(name = "father")
public class Father implements Serializable{    
    ...
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    ...
    @OneToOne(targetEntity = FotoFather.class, fetch = FetchType.EAGER)
    @JoinColumn(name = "fotoFather", referencedColumnName = "id")
    @Cascade(CascadeType.ALL)
    private FotoFather fotoFather;
}

FotoFather.class

@Entity
@Table(name = "foto_father")
public class FotoFather.class{

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    ...
    @Column(name = "foto_natural_url")
    private String fotoNaturalUrl;
    ...
}
MDP
  • 4,177
  • 21
  • 63
  • 119

2 Answers2

1

If you simply need the complete URL for some application-specific purpose, I would likely err on the side of not trying to store the URL with the ID at all and instead rely on a transient method.

 public class FotoFather {
   @Transient
   public String getNaturalUrl() {
     if(fotoNaturalUrl != null && fotoNaturalUrl.trim().length > 0) {
       return String.format("%s?id=%d", fotoNaturalUrl, id);
     }
     return "";
   }
 }

In fact, decomposing your URLs even more into their minimalist variable components and only storing those in separate columns can go along way in technical debt, particularly if the URL changes. This way the base URL could be application-configurable and the variable aspects that control the final URL endpoint are all you store.

But if you must know the ID ahead of time (or as in a recent case of mine, keep identifiers sequential without loosing a single value), you need to approach this where FotoFather identifiers are generated prior to persisting the entity, thus they are not @GeneratedValues.

In order to avoid issues with collisions at insertion, we have a sequence service class that exposes support for fetching the next sequence value by name. The sequence table row is locked at read and updated at commit time. This prevents multiple sessions from concurrency issues with the same sequence, prevents gaps in the range and allows for knowing identifiers ahead of time.

 @Transactional
 public void save(Father father) {
   Assert.isNotNull(father, "Father cannot be null.");
   Assert.isNotNull(father.getFotoFather(), "FotoFather cannot be null.");
   if(father.getFotoFather().getId() == null) {
     // joins existing transaction or errors if one doesn't exist
     // when sequenceService is invoked.
     Long id = sequenceService.getNextSequence("FOTOFATHER");
     // updates the fotofather's id
     father.getFotoFather().setId(id);        
   }
   // save.
   fatherRepository.save(father);
 }
Naros
  • 19,928
  • 3
  • 41
  • 71
1

I think you can do be registering an @PostPersist callback on your Father class. As the JPA spec notes:

The PostPersist and PostRemove callback methods are invoked for an entity after the entity has been made persistent or removed. These callbacks will also be invoked on all entities to which these operations are cascaded. The PostPersist and PostRemove methods will be invoked after the database insert and delete operations respectively. These database operations may occur directly after the persist, merge, or remove operations have been invoked or they may occur directly after a flush operation has occurred (which may be at the end of the transaction). Generated primary key values are available in the PostPersist method.

So, the callback should be called immediately after the Father instance is written to the database and before the FotoFather instance is written.

public class Father(){

    @PostPersist
    public void updateFotoFather(){
        fotofather.setNaturalUrl("/xyz/" + id);
    }
}
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • Thank you, this seems to be the perfect solution. I tried it, but it seems that methods annotated with PostPersist or PrePersist etc. are not called at all in my application (and I don't get any error about those code lines). Should I set something in XML Spring configuration file? I looked a little into the internet, but it didn't find a solution. – MDP Dec 12 '15 at 19:02
  • 1
    http://stackoverflow.com/questions/8373024/hibernate-jpa-entity-listener-not-being-called-properly – Alan Hay Dec 14 '15 at 10:06