How can we get the count of OneToMany field of JPA entity as querying count for each parent entity while fetching as a list is costly and there is no way in JPA Repository.
I want to get the number of likes and comments for each PostEntity. The field is Lazy fetch type and if I call likes.size() or comments.size() then it will load all of the comments and likes from database and there can be thousands of comments and likes.
I know I can create a seperate repo for likes and comments to get the counts but while calling method from PostRepository how to get the counts for each and every entity? What is the best and efficient way?
Parent Entity
#Entity
#Table(name = "posts")
#Getter
#Setter
public class PostEntity extends MappedSuperClassEntity<UserEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Nullable
private String title;
#Nullable
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserEntity user;
#Nullable
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "community_id")
private CommunityEntity community;
#OneToMany(fetch = FetchType.LAZY)
private List<CommentEntity> comments;
#OneToMany(fetch = FetchType.LAZY)
private List<LikeEntity> likes;
#Transient
private int numberOfLikes;
#Transient
private int numberOfComments;
}
I would like to get the likes and comments count for each PostEntity while querying for the list of posts.
My Repo
public interface PostsRepository extends PagingAndSortingRepository<PostEntity, Integer> {
#Query(value = "SELECT P FROM PostEntity P WHERE P.user.id = :userId ORDER BY P.createdDate DESC")
Page<PostEntity> getUserPosts(int userId, Pageable pageable);
#Query(value = "select P from PostEntity P where p.community.id = :communityId order by P.createdDate desc")
Page<PostEntity> getCommunityPosts(int communityId, Pageable pageable);
}
I searched for a lot and someone suggested to use #Formula annotation for custom queries on the entity field but #Formula is hibernate specific and don't know if it works with #Transient field. Is there any JPA specific way to do that as it's a common problem.
You need "LazyCollection" annotation with EXTRA option.
#OneToMany(fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<CommentEntity> comments;
This annotation would allow to access "size()" without loading.
You can check this article.
https://www.baeldung.com/hibernate-lazycollection
Sometimes, we're only concerned with the properties of the collection, and we don't need the objects inside it right away. For example, going back to the Branch and the Employees example, we could just need the number of employees in the branch while not caring about the actual employees' entities. In this case, we consider using the EXTRA option. Let's update our example to handle this case. Similar to the case before, the Branch entity has an id, name, and an #OneToMany relation with the Employee entity. However, we set the option for #LazyCollection to be EXTRA:
I try to add comment but i have no writing comment access because of reputation so i send an answer.
Within a Spring Boot application, I have a class A that has a reference to another class B. The reference in class A is annotated with #ManyToOne.
When I get the records from class A, each reference to class B is expanded to show the values in B.
If I add an #JsonIdentityInfo annotation, then listing class A only expands the class B reference the first time it is retrived. Subsequent class A records with the same class B ref simply have the id.
What annotation do I need not to expand the class B ref so that each class A record simply shows the class B id.
Updated with code
Climate.java
#Entity
#SequenceGenerator(name = "climate_gen", sequenceName = "climate_gen", initialValue = 100)
public class Climate {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "climate_gen")
private long id;
private float temperature;
private float humidity;
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "location_id", nullable = false)
private Location location;
Location.java
#Entity
#SequenceGenerator(name = "location_gen", sequenceName = "location_gen", initialValue = 100)
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "location_gen")
private long id;
private String name;
private String gps;
private String timezone;
So setting fetchType to EAGER gives an expanded location record for each climate.
Setting fetchtype to LAZY gives an error
$ curl -k -w "\n" -H "Authorization: Bearer $TOKEN"
https://mint191:8453/api/v1/climates
{"timestamp":"2019-12-23T16:33:36.047+0000","status":500,"error":"Internal
Server Error","message":"Type definition error: [simple type, class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested
exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain:
java.util.ArrayList[0]->com.norricorp.bikes.model.Climate[\"location\"]->com.norricorp.bikes.model.Location$HibernateProxy$atNV2KHn[\"hibernateLazyInitializer\"])","path":"/api/v1/climates"}
Regards,
Here are some ideas:
use a custom Jackson serializer - example
other ways to handle bidirectional relationships in Jackson
use a custom query with JPQL Constructor Expression - more on SO and in this article: The best way to map a projection query to a DTO (Data Transfer Object) with JPA and Hibernate
instead of A referencing B directly, a use BId entity mapped to the same table as B: How to map multiple JPA entities to one database table with Hibernate. Here is a very simple project where I'm using this approach
use Spring Data JPA Projections or DTO Projections
UPDATE (25.12.2019)
When FetchType.LAZY is used Hibernate returns a proxy.
Here are some ways to work around the error you're getting:
Jackson – JsonMappingException (No serializer found for class)
No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor
Avoid Jackson serialization on non fetched lazy objects
If I got you correctly then I guess you are searching for the LazyLoading annotation inside the OneToMany Annotation
#OneToMany(fetch = FetchType.LAZY)
eg.: https://www.baeldung.com/hibernate-lazy-eager-loading
Maybe you can share some example code?
I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.
Here's a snippet of the code:
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
How can I correctly save a Transaction, associated with an already persisted Account object?
The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.
I have had the same problem and CascadeType.MERGE has worked for me.
I hope you are sorted.
This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.
As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.
An example setter for the Many side is in this link.
After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.
Remove cascading from the child entity Transaction, it should be just:
#Entity class Transaction {
#ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER can be removed as well as it's the default for #ManyToOne)
That's all!
Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.
But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).
Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.
You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...
Guess what happens if you call remove(transaction) still having "cascade ALL" in that #ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?
Don't pass id(pk) to persist method or try save() method instead of persist().
Removing child association cascading
So, you need to remove the #CascadeType.ALL from the #ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.
#ManyToOne(fetch= FetchType.LAZY)
Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.
Setting both sides of the association
Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.
You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.
Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.
My Spring Data JPA-based answer: I simply added a #Transactional annotation to my outer method.
Why it works
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
Reference:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
An old question, but came across the same issue recently . Sharing my experience here.
Entity
#Data
#Entity
#Table(name = "COURSE")
public class Course {
#Id
#GeneratedValue
private Long id;
}
Saving the entity (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Fix
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Conclusion : If the entity class has #GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)
If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).
In example of Account -> Transactions:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.
In your entity definition, you're not specifying the #JoinColumn for the Account joined to a Transaction. You'll want something like this:
#Entity
public class Transaction {
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
#JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
EDIT: Well, I guess that would be useful if you were using the #Table annotation on your class. Heh. :)
Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.
Alternatively, but effectively the same, this annotation declaration results in the same exception:
#Entity
public class Transaction {
#Id
#org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
#GeneratedValue(generator="system-uuid")
private Long id;
....
So, don't set the id value in your application code when it's already being managed.
Here is my fix.
Below is my Entity. Mark that the id is annotated with #GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate.
Mind you if the entity id field is not marked with #GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()
#Entity
#Data
#NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String key;
#Column
private String value;
}
And here is my main class.
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
When I tried saving that, it was throwing that
PersistentObjectException: detached entity passed to persist.
All I needed to fix was remove that id setting line for the simpleObject in the main method.
Maybe It is OpenJPA's bug, When rollback it reset the #Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the #Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* #param entity
* #param detached
* #return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
You need to set Transaction for every Account.
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
Or it colud be enough (if appropriate) to set ids to null on many side.
// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
foreach(Account account : accounts){
account.setId(null);
}
transactionObj.setAccounts(accounts);
// just persist transactionObj using EntityManager merge() method.
cascadeType.MERGE,fetch= FetchType.LAZY
Resolved by saving dependent object before the next.
This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation #ManytoOne
#OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
worked for me.
In my case I was committing transaction when persist method was used.
On changing persist to save method , it got resolved.
If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key)
Then this will work.
Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.
Add a #Version annotation to all mapped entities
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "orders")
private CustomerOrders orders;
}
#Entity
public class CustomerOrders {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
private BigDecimal value;
}
This error comes from the JPA Lifecycle.
To solve, no need to use specific decorator. Just join the entity using merge like that :
entityManager.merge(transaction);
And don't forget to correctly set up your getter and setter so your both side are sync.
So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.
But in my case I was trying to set a Value to a Field which was annotated with #Id.
So if you are using #Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.
The problem here is lack of control.
When we use the CrudRepository/JPARepository save method we loose the transactional control.
To overcome this issue we have Transaction Management
I prefer the #Transactional mechanism
imports
import javax.transaction.Transactional;
Entire Source Code:
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Data
#ToString(exclude = {"employee"})
#EqualsAndHashCode(exclude = {"employee"})
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
#SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
#Setter(AccessLevel.NONE)
#Column(name = "project_id",updatable = false,nullable = false)
private Integer pId;
#Column(name="project_name",nullable = false,updatable = true)
private String projectName;
#Column(name="team_size",nullable = true,updatable = true)
private Integer teamSize;
#Column(name="start_date")
private Date startDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projectemp_join_table",
joinColumns = {#JoinColumn(name = "project_id")},
inverseJoinColumns = {#JoinColumn(name="emp_id")}
)
private List<Employee> employees;
}
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#EqualsAndHashCode(exclude = {"projects"})
#ToString(exclude = {"projects"})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
#SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
#Setter(AccessLevel.NONE)
#Column(name = "emp_id",nullable = false,updatable = false)
private Integer eId;
#Column(name="fist_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#ManyToMany(mappedBy = "employees")
private List<Project> projects;
}
package com.oracle.repo;
import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
package com.oracle.repo;
import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRepo extends JpaRepository<Project,Integer> {
}
package com.oracle.services;
import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
#Component
public class DBServices {
#Autowired
private ProjectRepo repo;
#Transactional
public void performActivity(){
Project p1 = new Project();
p1.setProjectName("Bank 2");
p1.setTeamSize(20);
p1.setStartDate(new Date(2020, 12, 22));
Project p2 = new Project();
p2.setProjectName("Bank 1");
p2.setTeamSize(21);
p2.setStartDate(new Date(2020, 12, 22));
Project p3 = new Project();
p3.setProjectName("Customs");
p3.setTeamSize(11);
p3.setStartDate(new Date(2010, 11, 20));
Employee e1 = new Employee();
e1.setFirstName("Pratik");
e1.setLastName("Gaurav");
Employee e2 = new Employee();
e2.setFirstName("Ankita");
e2.setLastName("Noopur");
Employee e3 = new Employee();
e3.setFirstName("Rudra");
e3.setLastName("Narayan");
List<Employee> empList1 = new LinkedList<Employee>();
empList1.add(e2);
empList1.add(e3);
List<Employee> empList2 = new LinkedList<Employee>();
empList2.add(e1);
empList2.add(e2);
List<Project> pl1=new LinkedList<Project>();
pl1.add(p1);
pl1.add(p2);
List<Project> pl2=new LinkedList<Project>();
pl2.add(p2);pl2.add(p3);
p1.setEmployees(empList1);
p2.setEmployees(empList2);
e1.setProjects(pl1);
e2.setProjects(pl2);
repo.save(p1);
repo.save(p2);
repo.save(p3);
}
}
I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!