I have an entity whose embedded ID includes a transient field that is set from a non-transient one within a parameterised constructor. The class of that ID has a field bridge which converts it to/from a string. When a FullTextQuery is issued, the results include projected FullTextQuery.THIS entity documents.
Everything works fine as long as a single entity is returned. For two or more entities, their ID transient fields are set to null. I noticed that changing the database retrieval method from QUERY to FIND_BY_ID makes it work i.e. the ID transient field of each returned entity is correctly set (to a non-null value). Wondering if the behaviour shouldn’t be the same regardless of the retrieval method and the number of returned entities.
I suspect this is not a Hibernate Search issue, but an issue with your code or ORM. Try executing a Hibernate ORM query on this entity, with multiple results; I suspect you will have the same problem.
To be honest, what surprises me most is that this works at all, even partially. I would expect the field to always be null. But it seems in some cases the ID object you created is set on the loaded entity, which leads to your single working case. I suppose in most other cases, a new ID object is built.
The proper way to initialize this transient field, I think, would be to declare a @PostLoad method on the entity, which would call some initialize() method on the ID. But that probably won’t work on proxies.
I guess updating this transient field anytime one of the fields it’s based on is modified would be the safest course of action. Something like that:
@Access(AccessType.PROPERTY)
public class MyId {
private Integer myField1;
private Integer myField2;
@Transient
private transient Integer myTransientField;
protected MyId() {
// For Hibernate
}
public MyId(int myField1, int myField2) {
this.myField1 = myField1;
this.myField2 = myField2;
updateTransientField();
}
protected int setMyField1(int value) {
this.myField1 = value;
updateTransientField();
}
protected int setMyField2(int value) {
this.myField2 = value;
updateTransientField();
}
private void updateTransientField() {
if ( myField1 != null && myField2 != null ) {
this.myTransientField = myField1 + myField2;
}
}
}
(1) returns non-transient fields only i.e. the transient field is lost
(2) based on (1), returns entities with non-transient fields only i.e. without the transient one
If a single entity is being loaded (else if block) then the flow goes to (3) i.e. the entity is returned with all fields (non-transient and transient) set. This seems to explain why, although not working for multiple entities, DatabaseRetrievalMethod.QUERY works when a single entity is being returned.
@sant0s You can give it a try, but I doubt it will be fixed soon, if at all, since the basic problem is that the your ID class is flawed when used as Hibernate ORM expects (i.e. default constructor, then setters or direct writes to fields).
Did you try the piece of code I gave you in my last message? It could solve your problem.
If it doesn’t, you could open a ticket to ask for something like the @PostLoad annotation, but for embedded IDs instead of entities.
@yrodiere As you mentioned, @PostLoad is unfortunately not working (with neither DatabaseRetrievalMethod.FIND_BY_ID nor DatabaseRetrievalMethod.QUERY). I also tried your suggestion about setting the transient field from a non-transient field setter, but to no avail. Adding a no-args constructor and getters/setters to the ID class doesn’t make it work either. At some point, the setters get actually called, including the transient field one, but later in the flow FullTextQuery returns entities with null (embedded ID) transient fields due to, I guess, the logic I described earlier.
I’ve just created HHH-13052. Thanks for your help.
Check that you added the @Access annotation to your ID class, and that you added the @java.persistence.Transient annotation on the getter for your transient field, in particular.
@Access(AccessType.PROPERTY) to the @Embeddable class
@Transient to the transient field getter
DatabaseRetrievalMethod.QUERY (preferred over FIND_BY_ID) now works regardless of the number of returned entities.
Still, it’s curious how Hibernate ORM internals don’t require @Access(AccessType.PROPERTY) and @Transient in the single entity case - working example, based on yours, at https://github.com/sant0s/hibernate-test-case-templates/tree/HHH-13052 -, but do in the more than 1 entity one. If this is behaviour is expected or not an issue then HHH-13052 can be closed.
I tried @Access(AccessType.FIELD) and @Transient in the field, but it didn’t work, so I assume @Access(AccessType.PROPERTY) and @Transient in the getter is the only way to go.