I have an hibernate managed entity:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// (omitted useless details)
#OneToOne
#JoinColumn(name = "child_id", updatable = false)
private Child child;
// (omitted useless details)
}
--
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// (omitted rest...this is a POJO)
}
Since the responsibility of the update for Child is managed elsewhere, my goal is to simply not update child when the parent is saved. Unfortunately, this updatable = false on the #JoinColumn appears to be getting ignored, saving any changes made to both parent and child.
How can I achieve only saving the parent in a one to one relationship?
Any help is appreciated.
EDIT: Clarification, the Child class is an #Entity as well, but a simple POJO. The relationship between Parent and Child is unidirectional from the parent.
I looked into this for quite a while and looking at the documentation for #JoinColumn annotation and the updatable/insertable properties it says...
"(Optional) Whether the column is included in SQL UPDATE statements generated by the persistence provider."
What this means is setting updatable = false will only prevent the foreign key from being updated (column is included). When you save the parent entity it will also save all child entities attached to the object.
This means that any child properties that are attached to the parent entity when you save will also be persisted. This brings another issue to light if someone else may have updated this child entity, but because you are saving the parent entity with the child entity attached to it, you will un-knowingly overwrite any changes made to the child object.
Solution:
AFAIK the only way to resolve this is to be diligent about the object you save, unless you really need to be saving/updating the child entities don't. Keep your objects self contained, this is the downfalls of lazy/eager loaded properties and saving that same object graph. You could also do like another poster said and set the child entity to null before the save to prevent any updates, but can easily be missed.
If you are OK with saving the entire object tree (parent and children entities) you could add a property that utilizes the #Version attribute to introduce optimistic concurrency. This way if any entity was updated by someone else and you are trying to save an old version it will fail.
#Version
#Column(name=”version”)
private long version;
public long getVersion() { return version; }
Related
I have a tricky problem with hibernate using more queries than necessary for a simple findAll call. In my model there is two entities Parent and Child with oneToMany association;
Parent
class Parent{
#id
private long id;
//unique
private String code;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<OperatorAttribute> children;
}
Child
class Child{
#id
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_code", referencedColumnName = "code")
#LazyToOne(LazyToOneOption.NO_PROXY) // here i'm trying to tell hibernate to create no proxy and just ignore the field but no luck :/
public Parent parent;
}
The problem is that whenever I try to fetch the list of child using childRepository.findAll() from the database, hibernate make N+1 select query, why ?
I think this may be the explanation for that: IMHO when Hibernate populate the child object, he tries to create a proxy for the parent field,
and for that he needs the id of the parent row, which should normally be the foreign key in the child table, but in my case the #fk isn't binded to the primary key of the Parent table but to a unique column (plz don't ask me why) so in order to populate the id he needs to do an additional select query just to initialize the proxy of the parent field.
So my question is how to prevent Hibernate from creating a proxy for the parent field.
Thanks.
You are right. The proxy needs the #Id of the proxied entity (this way it could be made sure that it could be find). As soon you define the LazyToOneOption.NO_PROXY it tells the system to give back the real object. And this is what happens here. What you get mapped on the result is not a proxy, because with this annotation you explicitly disabled it so you have to get the real object.
Based on the mapping provided you cannot ignore the field because You'll loose the information what was is the Parent on the Child. So with this kind of setup you'll always need to read the parent.
If this field is not needed at all in a specific area, you can create some other mappings to the same table. But be careful! This could introduce a load of other cache related problems.
I frequently have to create a record that has child records. I created a utility class U that is not managed by the container with a static method to create the objects. It creates the required entities and relates them to each other and then returns the parent entity - BUT DOES NOT PERSIST.
When I go to persist the parent entity I expect all the attached child entities to be persisted as well but only the parent is persisted.
In another place, quite by accident, I noticed that if I created and attached the entities to the parent in class X that is not directly managed by the container but was created by a class C that was managed by the container - everything works as expected. One other thing to note is that in this case the parent already existed so I set the ID on the child when I created it.
Does this mean I cannot create entities using static methods? Or is there something else going on?
Here are the entities in question.
PARENT:
#Id
#GeneratedValue(generator = "INFO_ID_GEN", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "INFO_ID_GEN", sequenceName = "INFO_ID",allocationSize=1)
#Column(name="INFO_ID")
private int infoId;
#OneToOne(mappedBy="PARENT", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private CHILD child;
CHILD:
#Id
#Column(name="INFO_ID", insertable=false)
private int infoId;
#OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name="INFO_ID")
private PARENT parent;
If you need anything else let me know.
I have the following entities with a parent-child relationship:
public class Parent {
#Id #GeneratedValue String id;
#Version Long version;
#OneToMany(mappedBy = "parent", orphanRemoval = true)
#Cascade({CascadeType.ALL})
Set<Child> children;
// getters and setters
}
public class Child {
#Id #GeneratedValue String id;
#ManyToOne
#JoinColumn("parent_id")
Parent parent;
// getters and setters
}
I retrieve a Parent for edit on the web UI by copy properties to a ParentDto, which has a list of ChildDtos.
Once I'm done editing, I send the ParentDto object back and copy all properties into a new Parent object (parent) with a new HashSet to store the Children created from the list of ChildDtos.
Then I call getCurrentSession().update(parent);
The problem
I can add children, update children, but I can't delete children. What is the issue here and how do I resolve it?
Thanks in advance.
You have a bidirectional association, you need to remove from Child class the link to the parent class, try to make Parent reference to null, and also set the Set<Child> to a new HashSet<Child> or whatever your implementation is.
Then save the changes that will remove the children form the table.
This action can only be used in the context of an active transaction.
public void remove(Object entity);
Transitions managed instances to removed. The instances will be deleted from the datastore on the next flush or commit. Accessing a removed entity has undefined results.
For a given entity A, the remove method behaves as follows:
If A is a new entity, it is ignored. However, the remove operation cascades as defined below.
If A is an existing managed entity, it becomes removed.
If A is a removed entity, it is ignored.
If A is a detached entity, an IllegalArgumentException is thrown.
The remove operation recurses on all relation fields of A whose cascades include CascadeType.REMOVE. Read more about entity lifecycle
Please help! It has already taken me a day, and I am no closer to resolving the issue. My set up is as follows:
Eclipse Indigo
Eclipselink 2.3
Apache Tomcat 6
What I am doing is I am persisting an entity that has an #OneToOne mapping with a child entity. Once the record is in the database, I am running a select and successfully selecting the newly inserted record. However, the child entity is null.
My goal is to only insert the parent record, because the child record is already in the DB.
Question: what do I need to change to cause the child record to be populated?
I have an entity that I persist:
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(timeSheet);
em.getTransaction().commit();
}
The entity contains #OneToOne:
#Entity
#Cache(expiry=-1)
#Table(name="TIME_SHEET")
public class TimeSheet implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumns({
#JoinColumn(name="PROJECT_ID", referencedColumnName="PROJECT_ID", nullable = false, insertable=false, updatable=false),
#JoinColumn(name="COMPANY_ID", referencedColumnName="COMPANY_ID", nullable = false, insertable=false, updatable=false)
})
private Project project;
public Project getProject() {
return project;
}
The child entity is:
#Entity
#Cache(expiry=-1)
#Table(name="PROJECT")
public class Project implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ProjectPK id;
After "TimeSheet" is persisted and selected, "Project" is null. What should I do to ensure that "Project" is populated?
Thank you!
Well, in the code shown, you persist a TimeSheet without first setting a Project in it, so why would you expect it to have a Project when you load it? You should never expect your object model to look different after you load it than it did before you saved it. If it does look different, then you have a problem.
You should set the cascade policy correctly.
#OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-hibspec-cascade
Cascade policy dictates what to do with associated entities when you perform a persistence action on the parent entity.
As Ryan points out, you need to maintain the reference in TimeSheet to the Project. This is because the TimeSheet is cached. So you can either set the relationship by reading in the existing project when creating the TimeSheet, or by refreshing the TimeSheet after the transaction commits so that it gets built.
The code you have shown sets the TimeSheet->Project relationship fields are read only and doesn't show how the foriegn keys are populated. Make sure that these are actually set when inserting the TimeSheet through other mappings or it maybe that the TimeSheet legitimately doesn't have a Project - though the fields seem to have a not-null constraint.
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.