I try to cascade-persist the following two JPA entities (getters/setters and other fields ommited):
#Entity
public class TestEntity {
#Id #GeneratedValue
private Integer id;
private String name; // field only necessary so there is at least one mapping in the table
#ElementCollection
#MapKeyJoinColumn(name="PROPERTY_KEY", referencedColumnName="ID")
#Column(name="PROPERTY_VALUE")
#CollectionTable(name="PROPERTIES")
private Map<TestKey, String> properties = new HashMap<>();
}
#Entity
public class TestKey {
#Id #GeneratedValue
private Integer id;
private String name;
}
This is the code I use to persist the entity:
EntityManager em = ...
em.getTransaction().begin();
TestKey key = new TestKey();
key.setName("some key");
TestEntity entity = new TestEntity();
entity.getProperties().put(key, "some value");
em.persist(entity);
em.getTransaction().commit();
em.close();
But when I do this, I get the following exception:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship
that was not marked cascade PERSIST: TestKey
I am able to fix this by persisting the key before persisting the entity like so:
[...]
em.persist(key); // <<-- line added to previous example
em.persist(entity);
em.getTransaction().commit();
em.close();
This code works as expected and persists both the TestEntity and the TestKey.
Is it somehow possible to cascade-persist the entity without explicitly persisting the key - am I perhaps doing something wrong here?
I actually thought that all #ElementCollection mappings are automatically cascade-persisted. I came to this conclusion from the follwing statements:
https://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/ElementCollections
Their is no cascade option on an ElementCollection, the target objects
are always persisted, merged, removed with their parent.
https://stackoverflow.com/a/19517505/3270595:
You don't need #ManyToMany annotation here. Operations on
ElementCollections are always cascaded.
I'm using EclipsLink version 2.5.2.v20140319-9ad6abd 2.7.5.v20191016-ea124dd158.
Related
I am simply trying to perform an update of an entity. However hibernate attempts 2 SQL statements, one to perform the correct update and an unwanted second to update the ID alone to null, which causes my application to fail.
I am using Spring Data alongside Hibernate and when performing an update of an Entity, I see the expected update SQL is performed, however when running the application with SQL Server, a subsequent update is attempted which does the following:
update my_table set id=null where id=?
This fails obviously.
Cannot update identity column 'ID'.
Running the same code with H2 I do not see this second update triggered.
Any idea what might be the cause of this behaviour?
I am extending JpaRepository and using the default save().
Here is a snippet of my entity:
#Table(name = "MY_TABLE")
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String anotherValue;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id")
private List<ChildEntity> children = new ArrayList<>();
// getters, builder, private default constructor ...
Snippet building my entity:
MyEntity.newBuilder()
.withId(id)
.withAnotherValue(valueUpdate)
.build();
Repository:
public interface MyRepository extends JpaRepository<MyEntity, Long>
Saving:
myRepository.save(myUpdatedEntity);
As i think of probable cause for this is if you associate two entities with their IDs as foreign keys then hibernate may try to update ID of parent as foreign key of other entity. Its not correct way to associate.
In a one-to-many relation add a foreign key in the many side entity, that have to reference the primary key of the one side entity class.
#Entity
public class MyEntity {
..
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "MYENTITY_ID")
private List<ChildEntity> children = new ArrayList<>();
}
I have some code that deletes a row from a table using the following code:
EntityManager em = getEntityManager();
try
{
final EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.remove(data);
transaction.commit();
} catch (final PersistenceException e) {
{
throw new CPDPersistenceException(e);
}
The error message is: Cannot delete or update a parent row: a foreign key constraint fails.....etc.
The problem is that a foreign key for other tables "reference_id" exists in the table that I am trying to delete. However, wherever this primary key exists in the persistent Java object where it is defined, it has a reference that should cause cascading deletion. For example:
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="reference_id")
private Reference reference;
My understanding, from reading other entries on this subject, is that the "cascade" phrase attached to reference would fix the problem of deleting one entry that is related to other entries in other tables. Does anybody have any ideas?
What I understand from your code, you shouldn't set cascade property of #ManyToOne annotation for the owning entity's field of the referenced entity. On the contrary, you must set the cascade property of #OneToMany annotation in the parent entity for the child entity field. For example:
class ParentEntity {
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private ChildEntity child;
...
}
class ChildEntity {
#ManyToOne
private ParentEntity parent;
...
}
You are using unidirectional hibernate relationship. And because you are using cascade property for the reference field of both Uuid and Attachment entities, just manipulations on these two entities affect Reference entity, not vice versa.
I recommend using bidirectional relationship and set the cascade property of the #OneToMany annotation for your both uuid and attachment fields in your Reference entity to get the desired result, as follows:
public class Reference implements Serializable {
#Id
#Column(name = "reference_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int referenceID;
#OneToMany(mappedBy = "reference", cascade=CascadeType.ALL)
private Attachment attachment;
#OneToMany(mappedBy = "reference", cascade=CascadeType.ALL)
private Uuid uuid;
MORE STUFF
}
public class Attachment implements Serializable {
#ManyToOne
#JoinColumn(name = "reference_id")
private Reference reference;
MORE STUFF
}
public class Uuid extends Serializable {
#ManyToOne
#JoinColumn(name = "reference_id")
private Reference reference;
MORE STUFF
}
I have an common entity, which has a bidirectional association. The code of the entity is the next one:
#Entity
#Table(name = "TableName")
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int ID;
...
#OneToMany(mappedBy = "OtherEntityID", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OtherEntity> OtherEntity;
...
I try to delete the Entity with this code:
Session s = HibernateUtil.getSessionFactory().openSession();
Entity e = s.get(Entity.class, EntityID);
s.beginTransaction();
s.delete(e);
s.getTransaction().commit();
s.close();
However, Hibernate returns this Exception:
Exception in thread "main" org.hibernate.AssertionFailure: collection owner not associated with session
If I set orphanRemoval=false, then I'm able to remove the entity without any problem. I've tried initializing the Collection before deleting the entity with Hibernate.initialize(), but nothing changed.
I suppose this has something to do with the fact that the Collection is loaded LAZY and not EAGGER, but I've not found any solution yet. Could anybody help me?
PD: I'm using Hibernate 5.2.4 Final.
I have a class Customer that has a OneToOne bidirectional relationship with a Subscription:
#Entity
#Table(name = "customers")
public class Customer{
#OneToOne(mappedBy="customer",cascade = CascadeType.ALL)
private Subscription currentSubscription;
}
#Entity
#Table(name = "subscriptions")
public class Subscription {
#Id
#Column(columnDefinition = "INT8",name="id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="customer"))
private Long id;
#OneToOne
#PrimaryKeyJoinColumn
private Customer customer;
}
Now, when I create a customer with a subscription and call persist on the customer, it nicely saves the subscription as well into the database. However when I have already persisted a customer, and want to add a subscription, it fails with the following error:
Caused by: org.hibernate.id.IdentifierGenerationException: attempted
to assign id from null one-to-one property
[com.qmino.miredot.portal.domain.Subscription.customer]
I've written a test in order to explain what I want to achieve:
#Test
public void shouldCascadeUpdateSubscription(){
Customer owner = customerRepository.save(CustomerMother.getCustomer(false));
Subscription subscription = SubscriptionBuilder.create()
.setBillingDayOfMonth(LocalDate.now().getDayOfMonth())
.setSubscriptionPlan(subscriptionPlan)
.build();
subscription.setCustomer(owner);
owner.setCurrentSubscription(subscription);
customerRepository.save(owner);
Customer result = customerRepository.findOne(owner.getId());
assertThat(result.getCurrentSubscription(),is(notNullValue()));
assertThat(result.getCurrentSubscription().getId(),is(result.getId()));
}
Where did I go wrong?
Cascade here is not the problem, Cascade indicates the action to be done by entity when deleted or updated. What is correct if you want to save complete entity. But for that, you need to have the correct data, your message suggest it tries to update the Customer entity but it founds an empty AccountDetails, so in order to correctly fetch the other entities, you need to add FecthType.EAGER, to get all attributes of mapped entities.
#OneToOne(mappedBy="customer",cascade = CascadeType.ALL, fetch = FetchType.EAGER))
I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)