I have to deal with cyclic dependent relations I cannot influence and I am fairly new to JPA.
So a Entity has members of the same Entity and I resolved that by:
#Entity
#Table("A")
public class A {
#ManyToMany
#JoinTable(name = "A_HAS_SUBAS",
joinColumns = {#JoinColumn(name = "A_ID")},
inverseJoinColumns = {#JoinColumn(name = "SUBA_ID")})
private Set<A> as;
}
When writing to the DB I have the problem that Hibernate seems to not know which A has to be persisted first. I tried to solve this by removing all relations from A, write to the DB and restore relations afterwards through hibernate.
This seems to work, but seems to fail if an A has no SubAs and this doesn't fit with my understanding of the issue. So I certainly be wrong somewhere.
The Entity without relations is persisted by an inner transaction:
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
private void immediatelySaveNewEntity(A entity) {
try {
if (!dao.entityExistsFromId((int) entity.getId())) { dao.save(entity); }
} catch (Exception e) {
e.printStackTrace();
}
}
As a result I get a
ORA-02291: integrity constraint (...) violated - parent key not found
I can circumvent this issue by removing constraints from the DB, but this is not my preferred way of dealing with this.
I don't see any #Id attribute declared in class A. I believe you might have removed it for brevity.
Can you try updating #ManyToMany to #ManyToMany(cascade=CascadeType.ALL) as below and try.
#Entity
#Table("A")
public class A {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "A_HAS_SUBAS",
joinColumns = {#JoinColumn(name = "A_ID")},
inverseJoinColumns = {#JoinColumn(name = "SUBA_ID")})
public Set<A> as;
}
Able to save it with below hibernate test code and should work with JPA as well.
Session sess = //Get Session
Transaction tx1 = sess.beginTransaction();
A a = new A();
a.as = new HashSet<>();
a.as.add(new A());
a.as.add(new A());
sess.persist(a);
tx1.commit();
Incase I got your test scenario wrong, posting your basic test scenario would help.
Well, this was kind of mindbending for me, but my approach was okay with a slight mistake.
I had cyclic dependent Entities. Before writing to the DB took place I removed all relations from the entity, saved it to the DB and restored the relationships afterwards as an update. This was okay, because in this manner I had all entities in the DB and could restore the cyclic dependencies with ease. I wish I could have gotten rid of them in the first place, but no.
The mistake was in the how I did that. With having a single Transaction the removal of the relations had no effect, because when the Entity with all its relations got finally persisted to the DB I had restored the previous state already.
I attempted to use a new Transaction with
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
but in the same Bean and how I learned the hard way the same transaction.
The hint came from Strange behaviour with #Transactional(propagation=Propagation.REQUIRES_NEW)
So I injected a new instance of the same bean and executed the new Transaction on this instance, with access to the proxy and well --it worked.
Related
I have this association in the DB -
I want the data to be persisted in the tables like this -
The corresponding JPA entities have been modeled this way (omitted getters/setters for simplicity) -
STUDENT Entity -
#Entity
#Table(name = "student")
public class Student {
#Id
#SequenceGenerator(name = "student_pk_generator", sequenceName =
"student_pk_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"student_pk_generator")
#Column(name = "student_id", nullable = false)
private Long studentId;
#Column(name = "name", nullable = false)
private String studentName;
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
private Set<StudentSubscription> studentSubscription;
}
STUDENT_SUBSCRIPTION Entity -
#Entity
#Table(name = "student_subscription")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class StudentSubscription {
#Id
private Long studentId;
#ManyToOne(optional = false)
#JoinColumn(name = "student_id", referencedColumnName = "student_id")
#MapsId
private Student student;
#Column(name = "valid_from")
private Date validFrom;
#Column(name = "valid_to")
private Date validTo;
}
LIBRARY_SUBSCRIPTION Entity -
#Entity
#Table(name = "library_subscription",
uniqueConstraints = {#UniqueConstraint(columnNames = {"library_code"})})
#PrimaryKeyJoinColumn(name = "student_id")
public class LibrarySubscription extends StudentSubscription {
#Column(name = "library_code", nullable = false)
private String libraryCode;
#PrePersist
private void generateLibraryCode() {
this.libraryCode = // some logic to generate unique libraryCode
}
}
COURSE_SUBSCRIPTION Entity -
#Entity
#Table(name = "course_subscription",
uniqueConstraints = {#UniqueConstraint(columnNames = {"course_code"})})
#PrimaryKeyJoinColumn(name = "student_id")
public class CourseSubscription extends StudentSubscription {
#Column(name = "course_code", nullable = false)
private String courseCode;
#PrePersist
private void generateCourseCode() {
this.courseCode = // some logic to generate unique courseCode
}
}
Now, there is a Student entity already persisted with the id let's say - 100.
Now I want to persist this student's library subscription. For this I have created a simple test using Spring DATA JPA repositories -
#Test
public void testLibrarySubscriptionPersist() {
Student student = studentRepository.findById(100L).get();
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
librarySubscription.setStudent(student);
studentSubscriptionRepository.save(librarySubscription);
}
On running this test I am getting the exception -
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.springboot.data.jpa.entity.Student; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.springboot.data.jpa.entity.Student
To fix this I attach a #Transactional to the test. This fixed the above exception for detached entity, but the entity StudentSubscription and LibrarySubscription are not getting persisted to the DB. In fact the transaction is getting rolled back.
Getting this exception in the logs -
INFO 3515 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext#35390ee3 testClass = SpringDataJpaApplicationTests, testInstance = com.springboot.data.jpa.SpringDataJpaApplicationTests#48a12036, testMethod = testLibrarySubscriptionPersist#SpringDataJpaApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration#5e01a982 testClass = SpringDataJpaApplicationTests, locations = '{}', classes = '{class com.springboot.data.jpa.SpringDataJpaApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#18ece7f4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#264f218, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#2462cb01, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer#928763c, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#7c3fdb62, org.springframework.boot.test.context.SpringBootTestArgs#1, org.springframework.boot.test.context.SpringBootTestWebEnvironment#1ad282e0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
Now I have couple of questions -
Why am I getting detached entity exception. When we fetch an entity from the DB, Spring Data JPA must be using entityManager to fetch the entity. The fetched entity gets automatically attached to the persistence context right ?
On attaching #Transactional on the test, why the transaction is getting rolledback, and no entity is getting persisted. I was expecting the two entities - StudentSubscription and LibrarySubscription should've been persisted using the joined table inheritance approach.
I tried many things but no luck. Seeking help from, JPA and Spring DATA experts :-)
Thanks in advance.
Let me add a few details that outline a couple of design problems with your code that significantly complicate the picture. In general, when working with Spring Data, you cannot simply look at your tables, create cookie-cutter entities and repositories for those and expect things to simply work. You need to at least spend a bit of time to understand the Domain-Driven Design building blocks entity, aggregate and repository.
Repositories manage aggregates
In your case, Student treats StudentSubscriptions like an entity (full object reference, cascading persistence operations) but at the same time a repository to persist the …Subscriptions exists. This fundamentally breaks the responsibility of keeping consistency of the Student aggregate, as you can simply remove a …Subscription from the store via the repository without the aggregate having a chance to intervene. Assuming the …Subscriptions are aggregates themselves, and you'd like to keep the dependency in that direction, those must only be referred to via identifiers, not via full object representations.
The arrangement also adds cognitive load, as there are now two ways to add a subscription:
Create a …Subscription instance, assign the Student, persist the subscription via the repository.
Load a Student, create a …Subscription, add that to the student, persist the Student via it's repository.
While that's already a smell, the bidirectional relationship between the …Subscription and Student imposes the need to manually manage those in code. Also, the relationships establish a dependency cycle between the concepts, which makes the entire arrangement hard to change. You already see that you have accumulated a lot of (mapping) complexity for a rather simple example.
What would better alternatives look like?
Option 1 (less likely): Students and …Subscriptions are "one"
If you'd like to keep the concepts close together and there's no need to query the subscriptions on their own, you could just avoid those being aggregates and remove the repository for them. That would allow you to remove the back-reference from …Subscription to Student and leave you with only one way of adding subscriptions: load the Student, add a …Subscription instance, save the Student, done. This also gives the Student aggregate its core responsibility back: enforcing invariants on its state (the set of …Subscription having to follow some rules, e.g. at least one selected etc.)
Option 2 (more likely): Students and …Subscriptions are separate aggregates (potentially from separate logical modules)
In this case, I'd remove the …Subscriptions from the Student entirely. If you need to find a Students …Subscriptions, you can add a query to the …SubscriptionRepository (e.g. List<…Subscription> findByStudentId(…)). As a side effect of this you remove the cycle and Student does not (have to) know anything about …Subscriptions anymore, which simplifies the mapping. No wrestling with eager/lazy loading etc. In case any cross-aggregate rules apply, those would be applied in an application service fronting the SubscriptionRepository.
Heuristics summarized
Clear distinction between what's an aggregate and what not (the former get a corresponding repository, the later don't)
Only refer to aggregates via their identifiers.
Avoid bidirectional relationships. Usually, one side of the relationship can be replaced with a query method on a repository.
Try to model dependencies from higher-level concepts to lower level ones (Students with Subscriptionss probably make sense, a …Subscription without a Student most likely doesn't. Thus, the latter is the better relationship to model and solely work with.)
The transaction is getting rolled back because the test is doing DB updates in the test method.
#Transactional does auto rollback if the transaction includes any update DB. Also here is the compulsion to use transaction because EntityManager gets closed as soon as the Student entity gets retrieved, so to keep that open the test has to be within the transactional context.
Probably if I had used a testDB for my testcases then probably spring wouldn't haveve been rolling back this update.
Will setup an H2 testDb and perform the same operation there and will post the outcome.
Thanks for the quick help guys. :-)
Why am I getting detached entity exception. When we fetch an entity from the DB, Spring Data JPA must be using entityManager to fetch the entity. The fetched entity gets automatically attached to the persistent context right ?
Right, but only for as long as the entityManager stays open. Without the transactional, as soon as you return from studentRepository.findById(100L).get();, the entityManager gets closed and the object becomes detached.
When you call the save, a new entityManager gets created that doesn't contain a reference to the previous object. And so you have the error.
The #Trannsaction makes the entity manager stay open for the duration of the method.
At least, that's what I think it's happening.
On attaching #Transactional on the test, why the transaction is getting rolledback,
With bi-directional associations, you need to make sure that the association is updated on both sides. The code should look like:
#Test
#Transactional
public void testLibrarySubscriptionPersist() {
Student student = studentRepository.findById(100L).get();
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
// Update both sides:
librarySubscription.setStudent(student);
student.getStudentSubscription().add(librarySubscription);
// Because of the cascade, saving student should also save librarySubscription.
// Maybe it's not necessary because student is managed
// and the db will be updated anyway at the end
// of the transaction.
studentSubscriptionRepository.save(student);
}
In this case, you could also use EntityManager#getReference:
#Test
#Transactional
public void testLibrarySubscriptionPersist() {
EntityManager em = ...
StudentSubscription librarySubscription = new LibrarySubscription();
librarySubscription.setValidFrom(//some date);
librarySubscription.setValidTo(//some date);
// Doesn't actually load the student
Student student = em.getReference(Student.class, 100L);
librarySubscription.setStudent(student);
studentSubscriptionRepository.save(librarySubscription);
}
I think any of these solutions should fix the issue. Hard to say without the whole stacktrace.
I am having these Entities: DocumentType, UserGroup, User
DocumentType.java has #ManyToMany Set of UserGroup:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "review_type", joinColumns = #JoinColumn(name="doc_type"),
inverseJoinColumns = #JoinColumn(name="user_group_id") )
private Set<UserGroup> reviewUserGroups;
UserGroup.java has #ManyToMany Set of User:
#ManyToMany
#JoinTable(name = "group_users", joinColumns = #JoinColumn(name = "group_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private Set<User> users;
What I want to do implement this code:
#Transactional
private void createDocuments(int avgDocsPerUser) {
List<DocumentType> documentTypes = documentTypeRepository.findAll();
int documentTypesCount = documentTypes.size();
List<User> users = userRepository.findAll().stream().filter(user -> !user.isAdmin()).collect(Collectors.toList());
int usersCount = users.size();
int documentsToCreate = (int) Math.floor(Math.random() * (usersCount * avgDocsPerUser)) + 1;
List<Document> documentList = new ArrayList<>();
while (documentList.size() < documentsToCreate) {
DocumentType documentType = documentTypes.get((int) Math.floor(Math.random() * documentTypesCount));
User user = documentType
.getSubmissionUserGroups()
.stream().findAny()
.get().getUsers()
.stream().findAny().get();
// create new document here and add User info to it
}
documentRepository.saveAll(documentList);
}
The problem that I keep getting error:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: it.akademija.wizards.entities.DocumentType.submissionUserGroups, could not initialize proxy - no Session
I want to avoid EAGER fetching. How to implement this code so I can randomly get User that is a part of UserGroup which is a part of SubmissionUserGroups in DocumentType object.
Part of your problem is likely that you've used the #Transactional annotation on a private method. According to the docs, this doesn't work:
When using proxies, you should apply the #Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the #Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
In addition, I find the way you get a User from the document type a bit hard to understand. Part of the issue there is the number of time you stream through collections, find something, and then stream through another collection.
It might be easier (and more in line with Spring idioms) to inject the UserRepository into this class and do a separate query here. If this method is also public, I believe it would be included in the same transaction so you wouldn't suffer the performance overhead of having to open another session.
However, you should do some more research on this. You might find this other post helpful: How to load lazy fetched items from Hibernate/JPA in my controller.
I'm new in Spring, Hibernate, JPA and it's API. I've created a #RestController and the related method is like,
#GetMapping("/user")
public ResponseEntity getListItemById(){
UserTypeEntity entity = userTypeRepository.findFirstByUserTypeId(1);
return ResponseEntity.ok().body(entity);
}
UserTypeEntity has 2 lazy getters,
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false)
public UserEntity getUserByUserId() {
return userByUserId;
}
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_type_list_item_id", referencedColumnName = "list_item_id", nullable = false)
public ListItemEntity getListItemByUserTypeListItemId() {
return listItemByUserTypeListItemId;
}
All the properties of ListItemEntity and UserEntity are null until and unless I use JOIN FETCH query. I've checked and verified that one.
(It might be familiar to any experienced)
Looks like following.
Here is the sample response I use to get,
(Sorry it couldn't even be formatted because of large data response, though I have single row in each table. It's Infinite recursion (StackOverflowError))
Everything is loaded eventually. I couldn't identify what the heck going wrong here. Why these lazy null properties are loaded and I got this weird and vague response? I've wasted whole day on this, plz help to get out of this.
Move your annotations from getter to field declaration and you will see consistent results.
After your edit:
#ShreeKrishna that was my point. Now I can provide clear explanation that this is expected behavior. Debugger shows you what fields are actually are and those are and WILL BE null as your lazy intitialization trigger is on GETTER method, not on field. So, don't pay attention to what debugger is showing you as long as you will access your properties via getters instead of direct access - you will be fine.
TL;DR: Is it possible to perform nested transactions in Hibernate that use SavePoints to rollback to specific states?
So I am attempting to persist a parent entity with a OneToMany mapping to child entities. This is working fine.
During this persistence, I would like to catch and log ALL constraint violations that occur. Currently, the FIRST entity (child or parent) to have a constraint violation throws a ConstraintViolationException and rolls back the transaction. I would like for the transaction to still be rolled back, but somehow collect ALL of the constraint violations that would occur.
Here is a brief outline of my entities:
ParentEntity.java
#Entity
#Table(name = "PARENT", schema = "SOMESCHEMA")
public class ParentEntity {
private static final ID_COLUMN = "ID_COLUMN";
#Id
#Column(name = ID_COLUMN)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinColumn(name = ID_COLUMN, referencedColumnName = ID_COLUMN)
private List<childEntity> children;
}
ChildEntity.java
#Entity
#Table(name = "CHILD", schema = "SOMESCHEMA")
public class ChildEntity {
public ChildEntity(String input) {
this.validationString = input;
}
#Id
#Column(name = ParentEntity.ID_COLUMN)
private Long id;
#ManyToOne
#JoinColumn(name = ParentEntity.ID_COLUMN, insertable = false, updatable = false)
private ParentEntity parent;
// The field under validation (should be less than 25 char's long)
#Column(name = "VALIDATE_ME")
private String validationString;
}
Example run:
public void someMethod() {
ParentEntity parent = new ParentEntity();
parent.addChild(new Child("good input 1"));
parent.addChild(new Child("bad input 1 break here"));
parent.addChild(new Child("bad input 2 break here"));
parent.addChild(new Child("good input 2"));
dataAccessObject.persist(parent);
}
Results:
I see the transaction rolled back and the ConstraintViolationException only contains information for the first bad child.
Desired Results:
I see the transaction rolled back and the ConstraintViolationException show information for all the bad children regardless of how many children were bad. (Also, if the parent has a constraint violation, I would still like to check the child constraints)
Is this possible?
ConstraintViolationException is a HibernateException and all Hibernate exceptions are not recoverable. So if one exception is thrown, you cannot rely on the existing session state to continue processing reliably.
So this is not possible or recommended.
I found a way to accomplish my end result, although it was through means I did not expect.
In order to accomplish my goal, I would need to use nested transactions and SavePoints. At the end of the day, that implementation would still produce invalid data in my database, because during the time it takes to find an error in a child, some of the other children may have already been persisted and a consumer of my database would be unaware that the parent and all it's children were about to be deleted due to one or more bad entities (parents or children).
My solution:
I implemented a validator to validate all of the parents and children before going to persist them. The drawback to this method is that I have to annotate constraints on my entity fields, but double validation is never a bad thing.
The answer to my original question:
This is impossible to due with my version of Hibernate, unless I implemented custom SavePoint functionality to support nested transactions.
I have a many-to-many relation with an additional column in the link table. I've configured it in a way that the owning side fetches children eager (so I don't get LazyInitializationException) and in the opposite direction it is lazy. This works.
I now wanted to fine-tune the transactions (before there was just #Transactional on class level of DAO and Service classes. I set method getById to readOnly = true:
#Transactional(readOnly = true)
public Compound getById(Long id) {
return compoundDAO.getById(id);
}
After this change I get a LazyInitializationException in following snippet:
Compound compound = compoundService.getById(6L);
Structure structure = compound.getComposition().get(0).getStructure();
System.out.println("StructureId: "+ structure.getId()); // LazyInitializationException
If I remove (readOnly = true) this works! Can anyone explain this behavior? I use Spring + Hibernate. Kind of confusing as I don't see any reason why this should affect which data is loaded?
EDIT:
Snippets of relationship definitions. This is a many-to-many with a column in the link table.
Owning side (eg Compound contains Structures):
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.compound",
cascade = CascadeType.ALL, orphanRemoval = true)
#OrderBy("pk.structure.id ASC")
private List<CompoundComposition> composition = new ArrayList<>();
Belongs to side:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.structure",
cascade = CascadeType.ALL)
#OrderBy("pk.compound.id ASC")
private List<CompoundComposition> occurence;
Many-To-One in #Embeddable ID class
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Compound getCompound() {
return compound;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() {
return structure;
}
EDIT 2:
Stack Trace
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:272) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
at org.bitbucket.myName.myApp.entity.Structure_$$_javassist_0.getId(Structure_$$_javassist_0.java) ~[classes/:na]
at org.bitbucket.myName.myApp.App.main(App.java:31) ~[classes/:na]
EDIT 3:
Also see my comment:
Log is very different with readOnly and it is missing the part were the relations are loaded, eg. some selects are missing in the log.
EDIT 4:
So I tired with a basic DriverManagerDataSource and no Connection pool. The issue is exactly the same. For me looks like an issue in Hibernate.
This is just wow. I'm starting to understand why some people hate ORMs...Just feels like I'm constantly having to spend hours to solve a weird issue and the solution is a very specific set of annotations + some code to work around the limitations of said annotations.
First to why this happens (why meaning with which annotations, but not in terms of making logical sense, which is the actual problem here as using common-sense is useless. Only trial and error helps). In the owning side, in #OneToMany I have orphanRemoval = true (which I have found out is required for consistency. one would think database constraints should handle that...just one of the many things that can drive you crazy.). It seems that if the transaction is not read-only, then this setting leads to some data being fetched even so its lazy, namely here:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() {
return structure;
}
In a read-only transaction, this fetching does not happen. I would guess because if you can't change anything you will also not have to remove orphans and hence any data that the logic behind this setting requires is not needed in a read-only tx.
So the obvious solution would be in above relation to change to FetchType.EAGER. Wrong! If you do that you will not be able to update the owning side (Compound) using session.merge. This will lead to a StackOverFlowError.
The real solution was actually already mentioned. Just leave the config as is but explicitly load the desired relations in the Service layer:
#Transactional(readOnly = true)
#Override
public Compound getById(Long id) {
Compound compound = compoundDAO.getById(id);
for (CompoundComposition composition : compound.getComposition()){
Hibernate.initialize(composition.getStructure());
}
return compound;
}
I admit I'm tending to fall in the premature optimization trap. This doesn't look very efficient and also seems to break how SQL works in the first place. But then I'm in the lucky position that in most cases CompoundComposition will contain only 1 or 2 elements.
Perhaps you could put
value.getComposition().get(i).getStructure();
in the body of the getById() method, so that the lazy loading happens within the transaction. I realize in this case you'd have to loop over i which might be inconvenient.
Two things :-
Lazy fetch works on Collections Interface. Since ...
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() {
return structure;
}
... this is not a collection interface (like List<Structure> would have been), it will be fetched in Eager fetch mode.
Make service method as transactional. It seems that after fetching from the dao layer, your structure is detached with NEVER flush mode. This is the underlying ORM issue I guess.