ManyToOne relationship is always null when finding entity in the same transaction - java

I have a ConversationEntity with a ManyToOne relationship with ModeratorEntity
#Entity
#Table(name="CONVERSATIONS")
public class ConversationEntity {
#Id
private Integer id;
private Integer moderatorId;
#ManyToOne
#JoinColumn(name="moderatorId", insertable=false, updatable=false)
private ModeratorEntity moderator;
}
#Entity
#Table(name="MODERATORS")
public class ModeratorEntity {
#Id
private Integer id;
private Integer name;
}
And a service class with a transactional method that first of all saves the ModeratorEntity and after that the ConversationEntity with the moderatorId previously created
#Transactional
public void doStuff(Moderator moderator, Integer conversationId) {
Integer moderatorId = moderatorService.save(moderator);
Integer conversationId = conversationService.save(conversationId, moderatorId);
//do other stuff
Conversation conversation = conversationService.findById(conversationId);
}
When I'm trying to find the ConversationEntity by the id in the same transaction, a few lines below, I'm getting the ConversationEntity with the field moderatorId set but with the ModeratorEntity object = null.
If I do this outside the transaction I'm getting the ModeratorEntity object properly set.
I tried using saveAndFlush in ModeratorRepository and ConversationRepository and set FetchType.EAGER in the ManyToOne relationship but none of them worked

There are many questions about similar problems. For example #Transactional in bidirectional relation with Spring Data returns null and Hibernate: comparing current & previous record.
As long as you are within a single transaction you'll always get the same instance and no data is actually loaded from the database. And since (it seems at least) you never set the reference to the ModeratorEntity it stays null.
Once you are in a new transaction the database gets accessed and JPA populates a new instance, now including a ModeratorEntity reference.
The possible fixes therefore are:
Instead of an id let moderatorService.save return an entity and set that in the Conversation. You might as well drop the moderatorId. This is the idiomatic way to do things with JPA.
Perform the query in a separate transaction. Springs TransactionTemplate might come in handy. While this does work it causes JPA internals to bleed into your application which I recommend to avoid.

Related

Java Jpa #GeneratedValue smart generation for both null and non-null values

I have 10-15 entities in my local postgres database.
All entities contains one identity of type Integer. See the code fragment down below.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, nullable = false)
#NonNull
private Integer id;
My CRUD (T is my entity) class contains create method for inserting new entities into appropriate table
private final JpaRepository<T, Integer> jpaRepository;
private final EntityManager entityManager;
private final Class<T> clazz;
#Override
public T create(T entity) {
return getJpaRepository().saveAndFlush(entity);
}
I am trying to achieve next goals:
If I call create method where entity contains some value (!= null) for id field then save it exactly with that id (not generated).
If I call create method where entity contains value == null - then create it with auto-generated id as database provide.
Why i need this?
I'm trying to fill in-memory small database with entities that I grab from JSON entity and save it with same id's.
My problem is that annotation #GeneratedValue(strategy = GenerationType.AUTO/SEQUENCE/TABLE/IDENTITY) not gives me such things.
If I remove GeneratedValue annotation then null values will not be handled properly.
I will highly appreciate your suggestions.
Add CommentCollapse 
Spring Data is no magic and if you look at the implementation of the saveAndFlush method, or rather at the implementation of save which it calls,
you'll see that it checks if (entityInformation.isNew(entity)).
How does it check isNew? Simply by checking whether the ID is NULL.
But you can simply use the entity manager directly! merge will do what you want. It's there to bring detached entities back into the managed state and copy its changes to the peristed entity. But if no persisted entity exists, it will simply create it.
Not that you do not want to always use merge instead of persist, this comes at a cost. See How do persist and merge work in JPA

Spring Data JPA get entity foreign key without causing the dependent entity lazy load

I have an #Entity A that references another entity B using OneToOne relation ship. I fetch entity A using spring data JpaRepository
A a = aRepository.findById(1);
int b_id = a.getB().getId();
As you can see I need to query ID of the B table, however in order to do that, I need to call getter of the B table, which will cause lazy-loading the B table itself. I do not want to do that because the only thing I need is the get ID, nothing else, and that ID is present in the first A table.
Is there any trick that will help me to get ID of the dependent table without triggering new query?
UPDATE
#Entity
class A {
#Id
private Long id;
#OneToOne
private B b;
}
#Entity
class {
#Id
private Long id;
}
Without looking at the entity mapping, I suspect, your entity classes might be using hibernate annotations on the field. With this if you call even the getId() method as in a.getB().getId() on the entity it will result in initializing the proxy (i.e., B object) and hits the database to fetch it.
So if the intent is only to get the id of the entity you can place the hibernate annotations on the getter methods instead. This doesn't result initializing the proxy (B object) to return the id. Although accessing any property other than id will result in hitting the database.
Have a look at related bug at HHH-3718
So, try using property/getter AccessType instead of field access. As an example instead of placing the annotations on field
#Id
#GeneratedValue(...)
private long id;
place them on the getters
#Id
#GeneratedValue(...)
public long getId() { ... }
Make sure you make similar changes to all the fields of B entity. Although you can explore #Access(AccessType.PROPERTY/FIELD) later.
There is already a related bug HHH-3718 regarding this behavior.
And a related topic on hibernate forum regarding field vs property access type that might be of interest for you Field Vs Property access
Posting your entities classes would help, if this doesn't resolve the issue.

One DAO per entity - how to handle references?

I am writing an application that has typical two entities: User and UserGroup. The latter may contain one or more instances of the former. I have following (more/less) mapping for that:
User:
public class User {
#Id
#GeneratedValue
private long id;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="GROUP_ID")
private UserGroup group;
public UserGroup getGroup() {
return group;
}
public void setGroup(UserGroup group) {
this.group = group;
}
}
User group:
public class UserGroup {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
private Set<User> users;
public void setUsers(Set<User> users) {
this.users = users;
}
}
Now I have a separate DAO class for each of these entities (UserDao and UserGroupDao). All my DAOs have EntityManager injected using #PersistenceContext annotation, like this:
#Transactional
public class SomeDao<T> {
private Class<T> persistentClass;
#PersistenceContext
private EntityManager em;
public T findById(long id) {
return em.find(persistentClass, id);
}
public void save(T entity) {
em.persist(entity);
}
}
With this layout I want to create a new user and assign it to existing user group. I do it like this:
UserGroup ug = userGroupDao.findById(1);
User u = new User();
u.setName("john");
u.setGroup(ug);
userDao.save(u);
Unfortunately I get following exception:
object references an unsaved transient instance - save the transient
instance before flushing: x.y.z.model.User.group ->
x.y.z.model.UserGroup
I investigated it and I think it happens becasue each DAO instance has different entityManager assigned (I checked that - the references in each DAO to entity manager are different) and for user entityManager does not manager the passed UserGroup instance.
I've tried to merge the user group assigned to user into UserDAO's entity manager. There are two problems with that:
It still doesn't work - the entity manager wants to overwrite the existing UserGroup and it gets exception (obviously)
even if it worked I would end up writing merge code for each related entity
Described case works when both find and persist are made using the same entity manager. This points to a question(s):
Is my design broken? I think it is pretty similar to recommended in this answer. Should there be single EntityManager for all DAOs (the web claims otherwise)?
Or should the group assignment be done inside the DAO? in this case I would end up writing a lot of code in the DAOs
Should I get rid of DAOs? If yes, how to handle data access nicely?
any other solution?
I am using Spring as container and Hibernate as JPA implementation.
Different instances of EntityManager are normal in Spring. It creates proxies that dynamically use the entity manager that is currently in a transaction if one exists. Otherwise, a new one will be created.
The problem is that your transactions are too short. Retrieving your user group executes in a transaction (because the findById method is implicitly #Transactional ). But then the transaction commits and the group is detached. When you save the new user, it will create a new transaction which fails because the user references a detached entity.
The way to solve this (and to do such things in general) is to create a method that does the whole operation in a single transaction. Just create that method in a service class (any Spring-managed component will work) and annotate it with #Transactional as well.
I don't know Spring, but the JPA issue is that you are persisting a User that has a reference to a UserGroup, but JPA thinks the UserGroup is transient.
transient is one of the life-cycle states a JPA entity can be in. It means it's just created with the new operator, but has not been persisted yet (does not have a persistent identity yet).
Since you obtain your UserGroup instance via a DAO, it seems like something is wrong there. Your instance should not be transient, but detached. Can you print the Id of the UserGroup instance just after your received it from the DAO? And perhaps also show the findById implementation?
You don't have cascade persist on the group relation, so this normally should just work if the entity was indeed detached. Without a new entity, JPA simply has no way to set the FK correctly, since it would need the Id of the UserGroup instance here but that (seemingly) doesn't exist.
A merge should also not "overwrite" your detached entity. What is the exception that you're getting here?
I only partially agree with the answers being given by the others here about having to put everything in one transaction. Yes, this indeed may be more convenient as the UserGroup instance will still be 'attached', but it should not be -necessary-. JPA is perfectly capable of persisting new entities with references to either other new entities or existing (detached) entities that were obtained in another transaction. See e.g. JPA cascade persist and references to detached entities throws PersistentObjectException. Why?
I am not sure how but I've managed to solve this. The user group I was trying to assign the user to had NULL version field in database (the field annotated with #Version). I figured out it was an issue when I was testing GWT RequestFactory that was using this table. When I set the field to 1 everything started to work (no changes in transaction handling were needed).
If the NULL version field really caused the problem then this would be one of the most misleading exception messages I have ever got.

Hibernate zeroToOne

I am trying to establish a relationship between 2 entities which would be zero-to-one. That is, the Parent can be saved without the associated Child entity and also along with the assoicated Child.
Following are the 2 Entity classes...
Employee (Parent)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="EMP_NAME")
private String name;
#PrimaryKeyJoinColumn
#OneToOne(cascade = {CascadeType.ALL})
private EmployeeInfo info;
#Column(name="EMP_ENUM")
private Integer enumId;
EmployeeInfo (Child)
public class EmployeeInfo {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="EMPLOYEE_EMAIL")
private String email;
With such kind of a relation and id column of the only Parent (Employee) table set to AUTO INCREMENT in MySql DB, the problem is that while saving a Parent->Child object graph, I get the following exception
org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into EMP_INFO
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
I tried setting the Child Table's Id property to AUTO INCREMENT in the DB , and the persistence of such a Parent->Child object graph is successful.
However, the problem described here surfaces, because I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object, and hence do NOT want to have AUTO INCREMENT on the Child's id column.
One solution could be not use the PrimaryKeyJoinColumn, but use a particular JoinColumn, but that adds an unnecessary column to my existing Table.
Has anyone come across such a problem? If yes, any pointers would be much helpful.
Finally, I got it working thanks to Pascal and some googling from my side. Apparently, I cannot use the Native key generator for such relationships where the parent can exist without the child (optional = true).
The thing that worked finally was the following, leaving me the downside of having to deal with Hibernate specific annotation (#GenericGenerator) and also having to make-do with bi-directional relationships instead of the unidirectional that I wanted.
Employee (Parent) class remains unchanged as above. It has AUTO INCREMENT on the Id column.
As for the child class (EmployeeInfo) it changed to the following, and again WITHOUT having the AUTO INCREMENT set on the Id column.
#Table(name="EMP_INFO")
#Entity
public class EmployeeInfo {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="verifInfo")})
private Long id;
#OneToOne(optional=false)
#JoinColumn (name="id")
private Employee emp;
#Column(name="EMPLOYEE_EMAIL")
private String email;
This helped me achieve what I wanted but on the downside, GenericGenerator is not a JPA annotation, it is a hibernate annotation, and sadly I have to make do with that as of now because JPA does not currently support this(or any similar) annotation.
Anyway, it helps to get through such cases :-)
I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object.
The optional attribute of a OneToOne is true by default, which is what you want.
However, you are somehow misusing the #PrimaryKeyJoinColumn here (well, it actually depends on what you really want to achieve but your current combination of annotations is not correct).
IF you want to map a OneToOne with a shared primary-key, use the #PrimaryKeyJoinColumn. But in that case, don't use a GeneratedValue on EmployeeInfo and set the id manually or, if you don't want to set it manually, use the Hibernate specific foreign generator that I already mentioned in your previous question. Check also the related question mentioned below.
And IF you do not want to use a shared primary key (like in your current code since you're trying to get the id generated by the database), then do not use the PrimaryKeyJoinColumn.
You have to make a choice.
References
JPA 1.0 specification:
9.1.32 PrimaryKeyJoinColumn Annotation
Related question
JPA Hibernate One-to-One relationship.

JPA Cascading Delete: Setting child FK to NULL on a NOT NULL column

I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.

Categories