How to avoid LazyInitializationException with nested collections in JPA-Hibernate? - java

Mandatory background info:
As part of my studies to learn Spring, I built my usual app - a little tool that saves questions and later creates randomized quizzes using them.
Each subject can have any number of topics, which in turn may have any number of questions, which once again in turn may have any number of answers.
Now, the problem proper:
I keep getting LazyInitializationExceptions.
What I tried last:
I changed almost each and every collection type used to Sets.
Also felt tempted to set the enable_lazy_load_no_trans property to true, but I've consistently read this is an antipattern to avoid.
The entities proper: (only fields shown to avoid wall of code-induced fatigue)
Subject:
#Entity
#Table(name = Resources.TABLE_SUBJECTS)
public class Subject implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_SUBJECT)
private int subjectId;
#Column(name="subject_name", nullable = false)
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_SUBJECT,
fetch = FetchType.EAGER
)
private Set<Topic> topics;
}
Topic:
#Entity
#Table(name = Resources.TABLE_TOPICS)
public class Topic implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "topic_id")
private int topicId;
#Column(name = "name")
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_TOPIC,
orphanRemoval = true,
cascade = CascadeType.MERGE,
fetch = FetchType.EAGER
)
private Set<Question> questions;
#ManyToOne(
fetch = FetchType.LAZY
)
private Subject subject;
}
Question:
#Entity
#Table(name = Resources.TABLE_QUESTIONS)
public class Question implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_QUESTION)
private int questionId;
#Column(name = "statement")
private String statement;
#OneToMany(
mappedBy = Resources.ENTITY_QUESTION,
orphanRemoval = true,
cascade = CascadeType.MERGE
)
private Set<Answer> answers;
#ManyToOne(
fetch = FetchType.LAZY
)
private Topic topic;
}
Answer:
#Entity
#Table(name = Resources.TABLE_ANSWERS)
public class Answer implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_ANSWER)
private int answerId;
#Column(name = "answer_text", nullable = false)
private String text;
#Column(name = "is_correct", nullable = false)
private Boolean isCorrect;
#ManyToOne(
fetch = FetchType.LAZY
)
private Question question;
}
I'm using interfaces extending JpaRepository to perform CRUD operations. I tried this to fetch stuff, without luck:
public interface SubjectRepository extends JpaRepository<Subject, Integer>
{
#Query
Optional<Subject> findByName(String name);
#Query(value = "SELECT DISTINCT s FROM Subject s " +
"LEFT JOIN FETCH s.topics AS t " +
"JOIN FETCH t.questions AS q " +
"JOIN FETCH q.answers as a")
List<Subject> getSubjects();
}
Now, the big chunk of text Spring Boot deigns to throw at me - the stack trace:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [org.callisto.quizmaker.domain.Subject#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.callisto.quizmaker.domain.Subject$HibernateProxy$B8rwBfBD.getTopics(Unknown Source) ~[main/:na]
at org.callisto.quizmaker.service.QuizMakerService.activeSubjectHasTopics(QuizMakerService.java:122) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.checkIfActiveSubjectHasTopics(QuizMaker.java:307) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.createNewQuestion(QuizMaker.java:117) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.prepareMainMenu(QuizMaker.java:88) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.run(QuizMaker.java:65) ~[main/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]
This exception happens when I call this line of code:
boolean output = service.activeSubjectHasTopics();
Which, in turn, calls this method on a service class:
public boolean activeSubjectHasTopics()
{
if (activeSubject == null)
{
throw new NullPointerException(Resources.EXCEPTION_SUBJECT_NULL);
}
return !activeSubject.getTopics().isEmpty();
}
The activeSubjectHasTopics method gets called in this context:
private void createNewQuestion(View view, QuizMakerService service)
{
int subjectId = chooseOrAddSubject(view, service);
service.setActiveSubject(subjectId);
if (checkIfActiveSubjectHasTopics(view, service))
{
chooseOrAddTopic(view, service, subjectId);
}
do
{
createQuestion(view, service);
createAnswers(view, service);
}
while(view.askToCreateAnotherQuestion());
service.saveDataToFile();
prepareMainMenu(view, service);
}
private boolean checkIfActiveSubjectHasTopics(View view, QuizMakerService service)
{
boolean output = service.activeSubjectHasTopics();
if (!output)
{
view.printNoTopicsWarning(service.getActiveSubjectName());
String topicName = readTopicName(view);
createNewTopic(service, topicName);
}
return output;
}

Not helpful if you change your structure to set. If you need to get the entities you need to explicitly include FETCH clause in your hql queries. You'll work your way by checking out the Hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching

I was able to track down the cause of the issue thanks to a comment from Christian Beikov - to quote:
Where do you get this activeSubject object from? If you don't load it
as part of the transaction within activeSubjectHasTopics, then this
won't work as the object is already detached at this point, since it
was loaded through a different transaction.
The activeSubject object was defined as part of the service class containing the activeSubjectHasTopics method, and was initialized by a different transaction as he pointed out.
I was able to fix the problem by annotating that service class as #Transactional and storing the IDs of the objects I need instead of the objects themselves.

Related

JpaObjectRetrievalFailureException when saving entity with one-to-many and client-assigned ids

In a simple Spring Boot Application, I'm facing with a JpaObjectRetrievalFailureException when I'm trying to save an entity with one-to-many association and client-assigned ids.
Please take a look on these entities and on this simple repository:
#Entity
#Table(name = "cart")
public class Cart {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "cart_id")
private List<Item> items;
// constructors, getters, setters, equals and hashCode ommitted
}
#Entity
#Table(name = "item")
public class Item {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
// constructors, getters, setters, equals and hashCode ommitted
}
public interface CartRepository extends JpaRepository<Cart, UUID> {
}
I wrote this test:
#DataJpaTest
class CartRepositoryTest {
#Autowired
private CartRepository cartRepository;
#Test
void should_save_cart() {
// GIVEN
final var cart = new Cart(UUID.randomUUID(), "cart");
final var item = new Item(UUID.randomUUID(), "item");
cart.setItems(List.of(item));
// WHEN
final var saved = cartRepository.save(cart);
// THEN
final var fetched = cartRepository.findById(saved.id());
assertThat(fetched).isPresent();
}
}
When I run the test, call to cartRepository.save(cart) fails with:
Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
at app//org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:379)
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
at app//org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at app//org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at app//org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at app/jdk.proxy3/jdk.proxy3.$Proxy105.save(Unknown Source)
at app//com.example.testjpaonetomany.repository.CartRepositoryTest.should_save_cart(CartRepositoryTest.java:28)
If I modify my entities by adding #GeneratedValue for ids, and in my test, I replace UUID.randomUUID() by null to delegate to Hibernate the ID generation, the test passes.
How to deal with client-generated ids?
The cause is that you save the parent object only (which is absolutely correct and fine) but still need to explain JPA that the operation should be propagated i.e.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cart_id")
private List<Item> items;
As minor improvements I would suggest to put the UUID generation into constructors and establish the relation via the dedicated method i.e.
final var cart = new Cart("cart");
cart.addItem(new Item("item"));
and probably consider using em.persist() instead of repository.save() as it makes a select request first in case of using uuids as #Augusto mentioned

Hibernate doesn't fetch eager

I have tow classes, the "Article" which contains a #ManyToOne reference to a "SurchargeGroup" which specifies the surcharge for that article.
#Entity
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public final class Article {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Getter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_WRITE)})
private int id;
#JoinColumn(nullable = false)
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup surchargeGroup;
}
The other class "SurchargeGroup" contains a parent object reference which can inherit the surcharge to the "SurchargeGroup" if it isn't set the case that no surcharge is provided by any parent is not possible.
#Table
#Entity
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public class SurchargeGroup implements Serializable, Cloneable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_WRITE)})
private int id;
#Column
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SURCHARGE_WRITE)})
private Double surcharge;
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_WRITE)})
private String name;
#JoinColumn
#ManyToOne(fetch = FetchType.EAGER)
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup parent;
public double getSurcharge() {
if (surcharge == null) {
return parent == null
? supplier == null
? Setting.SURCHARGE_DEFAULT.getDoubleValue()
: supplier.getDefaultSurcharge()
: parent.getSurcharge();
} else return surcharge;
}
#JoinColumn
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private Supplier supplier;
}
My problem is now that if I call the "getSurcharge()" method I get this exception which I cannot explain to myself because I marked the surcharge group to fetch eager
Exception in thread "AWT-EventQueue-0" org.hibernate.LazyInitializationException: could not initialize proxy [kernbeisser.DBEntities.SurchargeGroup#1046] - the owning Session was closed
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:172)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at kernbeisser.DBEntities.SurchargeGroup$HibernateProxy$cdTAuBkS.getSurcharge(Unknown Source)
I asked myself if this could get caused by the #Audited annotation? Any ideas? Thanks a lot!
Note: the #Key annotations have no effect to this scenario.
Here is what the debugger shows (Sorry for the German toString() functions):
Hibernate needs to stop eagerly fetching associations at some point, otherwise it would need to join an infinite number of times the SurchargeGroup entity (since it references itself).
The depth these fetches can be controlled application wide using the hibernate.max_fetch_depth property.
The source of the error was the AuditReader it doesn't fetch all eager properties even if they are annotated as Fetch.EAGER
It looks like the AuditReader only fetches one level of eager relations:
Article -> SurchargeGroup -> SurchargeGroup -> ...
(fetched) (fetched) (not fetched)

Dropwizard-Hibernate Lazy fetch is not lazy

I am using Dropwizard-1.1.2 with hibernate-5.2.8. I implemented one-to-many relationship like this:
Parent Table:
#TypeDefs( {#TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
#Table(name="parent_table")
#Entity
public class ParentTable {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Column(name = "parent_id", updatable = false, nullable = false)
private UUID id;
#JoinColumn(name="parent_id")
#OneToMany(targetEntity = NotificationModel.class, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
private List<NotificationModel> notifications = new ArrayList<>();
public UUID getId() {
return id;
}
public List<NotificationModel> getNotifications() {
return notifications;
}
public void setNotifications(List<NotificationModel> notifications) {
this.notifications = notifications;
}
}
Notification Table
#Table(name="notifications")
#Entity
public class ReminderNotificationModel {
#Id
#GeneratedValue
private Integer id;
#Column(name = "notification_id")
#Type(type="pg-uuid")
private UUID notificationId;
private String message;
#Column(name = "notification_status")
private String notificationStatus;
#Column(name = "scheduled_at")
private DateTime scheduledAt;
// getters and constructors
}
Now in my DAO no matter if I try native-query, criteria query or get by id on parent table, it always gives me all the notifications as well. Should the notifications be fetched lazily?
DAO
public class ReminderDao extends AbstractDAO<ReminderModel> {
#Inject
public ReminderDao(SessionFactory sessionFactory) {
super(sessionFactory);
}
public ReminderModel getById(UUID id) {
ReminderModel m = get(id); // m has notifications as well
return m;
}
}
I was reading hibernate's documentation which says Lazy is a hint for basic types. However collection is not a basic type. So lazy should have been honored. What am I missing?
LAZY is merely a hint that the value be fetched when the attribute is
accessed. Hibernate ignores this setting for basic types unless you
are using bytecode enhancement
"Loading the notifications of a parent lazily" is not the same thing as "not loading notifications of a parent and pretend the parent has no notification at all".
Loading lazily means that the notifications are loaded only when the code reads them from the list, for example when printing them with the debugger, or when serializing them to JSON, or when getting the size of the list.
If you really want to test that lazy loading works correctly, then load a parent, end the transaction and close the entity manager, and check that getting the size of the list throws an exception. Or load a parent, then detach it, then check that getting the size of the list throws an exception. Or load the parent, and check that Hibernate.isInitialized(parent.getNotifications()) is false.

JPA (Hibernate) issue saving ManyToOne relationship

I'm experiencing a problem: I have an Entity Definition, that has a OneToMany relationship with DefinitionInfo Entities and when I try to load an existing Definition, add a DefinitionInfo to it's infoList collection, saving says it can not find the entity of the DefinitionInfo I just added to the Definition. To this I say 'DUH!' I am trying to add a new DefintionInfo to the Definition so of course you won't find it!
Here's the code:
#Entity(name = "CohortDefinition")
#Table(name="cohort_definition")
#NamedEntityGraph(
name = "CohortDefinition.withDetail",
attributeNodes = { #NamedAttributeNode(value = "details", subgraph = "detailsGraph") },
subgraphs = {#NamedSubgraph(name = "detailsGraph", type = CohortDefinitionDetails.class, attributeNodes = { #NamedAttributeNode(value="expression")})}
)
public class CohortDefinition implements Serializable{
private static final long serialVersionUID = 1L;
...
#OneToMany(mappedBy="cohortDefinition", fetch= FetchType.LAZY)
private Set<CohortGenerationInfo> generationInfoList;
...
+ Getters and Setters
And the target/dependent object:
Entity(name = "CohortGenerationInfo")
#Table(name="cohort_generation_info")
public class CohortGenerationInfo implements Serializable {
private static final long serialVersionUID = 1L;
public CohortGenerationInfo()
{
}
public CohortGenerationInfo(Integer cohortDefinitionId, Integer sourceId)
{
this.id = new CohortGenerationInfoPK(cohortDefinitionId, sourceId);
}
#EmbeddedId
private CohortGenerationInfoPK id;
#ManyToOne
#MapsId("cohortDefinitionId")
#JoinColumn(name="id")
private CohortDefinition cohortDefinition;
Here is the EmbeddedID class (The Id for the Info consists of the definition's ID + a ID of another entity that represents differetnt types of info.
#Embeddable
public class CohortGenerationInfoPK implements Serializable {
private static final long serialVersionUID = 1L;
public CohortGenerationInfoPK() {
}
public CohortGenerationInfoPK(Integer cohortDefinitionId, Integer sourceId) {
this.cohortDefinitionId = cohortDefinitionId;
this.sourceId = sourceId;
}
#Column(name = "id")
private Integer cohortDefinitionId;
#Column(name = "source_id")
private Integer sourceId;
+ getter/setter
Here is what I am doing. This should be so simple, but I just don't understand what is going on:
Source source = this.getSourceRepository().findBySourceKey(sourceKey);
CohortDefinition currentDefinition = this.cohortDefinitionRepository.findOne(id);
CohortGenerationInfo info = findBySourceId(currentDefinition.getGenerationInfoList(), source.getSourceId());
if (info == null)
{
info = new CohortGenerationInfo(currentDefinition.getId(), source.getSourceId());
currentDefinition.getGenerationInfoList().add(info);
}
info.setStatus(GenerationStatus.PENDING)
.setStartTime(Calendar.getInstance().getTime());
this.cohortDefinitionRepository.save(currentDefinition);
Here's the Hibernate select just before it blows up:
Hibernate: select cohortgene0_.id as id1_4_0_, cohortgene0_.source_id as source_i2_4_0_, cohortgene0_.execution_duration as executio3_4_0_, cohortgene0_.is_valid as is_valid4_4_0_, cohortgene0_.start_time as start_ti5_4_0_, cohortgene0_.status as status6_4_0_, cohortdefi1_.id as id1_2_1_, cohortdefi1_.created_by as created_2_2_1_, cohortdefi1_.created_date as created_3_2_1_, cohortdefi1_.description as descript4_2_1_, cohortdefi1_.expression_type as expressi5_2_1_, cohortdefi1_.modified_by as modified6_2_1_, cohortdefi1_.modified_date as modified7_2_1_, cohortdefi1_.name as name8_2_1_ from cohort_generation_info cohortgene0_ inner join cohort_definition cohortdefi1_ on cohortgene0_.id=cohortdefi1_.id where cohortgene0_.id=? and cohortgene0_.source_id=?
Here is the stack trace:
javax.persistence.EntityNotFoundException: Unable to find org.ohdsi.webapi.cohortdefinition.CohortGenerationInfo with id org.ohdsi.webapi.cohortdefinition.CohortGenerationInfoPK#3
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:183)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:989)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:716)
at org.hibernate.type.EntityType.resolve(EntityType.java:502)
at org.hibernate.type.EntityType.replace(EntityType.java:366)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:549)
at org.hibernate.type.CollectionType.replace(CollectionType.java:690)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:177)
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:407)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:219)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:85)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:876)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:858)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:863)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1196)
I stoppped here at the merge part of the stack trace. It appears to be fetchign the existing entity from the DB and mereg it. The Definition DOES EXIST, it's just a new DefinitionInfo being added.
Does anyone have any thoughts? I have some limitations on how to structure the database, but I'm hoping this isn't a database structure problem, rather just somethign annoying with JPA.
Couple of apparent issues with the code.
Since you are expecting to do transitive persistence, i.e., saving CohortDefinition should also save CohortGenerationInfo, you will need to set the cascade property on the OneToMany annotation. For example,
#OneToMany(mappedBy="cohortDefinition", fetch= FetchType.LAZY, cascade= CascadeType.ALL)
You have set a bidirectional relationship between CohortDefinition and CohortGenerationInfo. As a best practice, it is better to set the values on both sides of the relationship explicitly rather than relying on the ORM to do it. So the code should do the following (Better to put it in a helper method).
currentDefinition.getGenerationInfoList().add(info);
info.setCohortDefinition(currentDefinition);

Need an example of a primary-key #OneToOne mapping in Hibernate

Can somebody please give me an example of a unidirectional #OneToOne primary-key mapping in Hibernate ? I've tried numerous combinations, and so far the best thing I've gotten is this :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#Id
#JoinColumn(name = "paper_cheque_id")
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
private PaperCheque paperCheque;
}
Whenever Hibernate tries to automatically generate the schema for the above mapping, it tries to create the primary key as a blob, instead of as a long, which is the id type of PaperCheque. Can somebody please help me ? If I can't get an exact solution, something close would do, but I'd appreciate any response.
I saved this discussion when I implemented a couple of #OneToOne mappings, I hope it can be of use to you too, but we don't let Hibernate create the database for us.
Note the GenericGenerator annotation.
Anyway, I have this code working:
#Entity
#Table(name = "message")
public class Message implements java.io.Serializable
{
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "message_id")
public MessageContent getMessageContent()
{
return messageContent;
}
}
#Entity
#Table(name = "message_content")
#GenericGenerator(name = "MessageContent", strategy = "foreign",
parameters =
{
#org.hibernate.annotations.Parameter
(
name = "property", value = "message"
)
}
)
public class MessageContent implements java.io.Serializable
{
#Id
#Column(name = "message_id", unique = true, nullable = false)
// See http://forum.hibernate.org/viewtopic.php?p=2381079
#GeneratedValue(generator = "MessageContent")
public Integer getMessageId()
{
return this.messageId;
}
}
Your intention is to have a 1-1 relationship between PaperChequeStopMetaData and PaperCheque? If that's so, you can't define the PaperCheque instance as the #Id of PaperChequeStopMetaData, you have to define a separate #Id column in PaperChequeStopMetaData.
Thank you both for your answers. I kept experimenting, and here's what I got working :
#Entity
#Table(name = "paper_cheque_stop_metadata")
#org.hibernate.annotations.Entity(mutable = false)
public class PaperChequeStopMetadata implements Serializable, SecurityEventAware {
private static final long serialVersionUID = 1L;
#SuppressWarnings("unused")
#Id
#Column(name = "paper_cheque_id")
#AccessType("property")
private long id;
#OneToOne(cascade = {}, fetch = FetchType.EAGER, optional = false, targetEntity = PaperCheque.class)
#PrimaryKeyJoinColumn(name = "paper_cheque_id")
#JoinColumn(name = "paper_cheque_id", insertable = true)
#NotNull
private PaperCheque paperCheque;
#XmlAttribute(namespace = XMLNS, name = "paper-cheque-id", required = true)
public final long getId() {
return this.paperCheque.getId();
}
public final void setId(long id) {
//this.id = id;
//NOOP, this is essentially a pseudo-property
}
}
This is, by all means, a disgusting hack, but it gets me everything I wanted. The paperCheque property accessors are as normal (not shown). I've run into this kind of unidirectional OneToOne mapping problem before and settled for much worse solutions, but this time I decided I was going to figure out out, so I kept hacking away at it. Once again, thank you both for your answers, it's much appreciated.
Just updating this question for future views.
When this question was made i think there wasn't a proper solution for this problem. But since JPA 2.0 you can use #MapsId to solve this problem.
Reference with proper explanation: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
You should stay away from hibernate's OneToOne mapping, it is very dangerous. see http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128
you are better off using ManyToOne mappings.

Categories

Resources