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.
Related
I have following classes and on annotating #BatchSize annotation it is not working and I am getting n+1 select query.
Class Shipment{
#OneToMany(fetch = FetchType.LAZY, mappedBy = order.shipment, cascade = CascadeType.ALL,
orphanRemoval = true)
#BatchSize(size=20)
Set<Orders> orders = new Hashset(); <---- Batch size annotation not working
}
Order.class
class Order{
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "item_fk")
Item item;
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "shipment_fk")
Shipment shipment; }
Item.class
class Item{
String id;
String name;
}
What is mistake in implementation that i am getting n+1 queries?
Try to use List<Orders> instead of Set<Orders>.
Please note as it's mentioned in the documentation:
However, although #BatchSize is better than running into an N+1 query issue, most of the time, a DTO projection or a JOIN FETCH is a much better alternative since it allows you to fetch all the required data with a single query.
Your N + 1 query issue is due to the fact that you do eager fetching of Item in Order. Change to LAZY there and you should be good to go.
public class Role {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id",referencedColumnName = "id"))
private Set<User> users;
}
public class User {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
I have a many to many relationship between the two classes. When calling role.getUsers(), I want to get only the user ids, the rest of the fields should be ignored, since there will be a lot of data and I don't want to load everything, How can I achieve this?
A straightforward way to do it would be to use a Criteria query, but to use it inside an Entity, you'd have to inject an EntityManager there, which is considered a bad practice. A better solution would be to create this query in a Service.
But if you still want to do it, then your getUsers method would look something like this:
public List<User> getUsers() {
Criteria cr = entityManager.createCriteria(User.class)
.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.setResultTransformer(Transformers.aliasToBean(User.class));
List<User> list = cr.list();
return list;
}
If you want to restrict your list, just use a Restrictions, like so: criteria.add(Restrictions.eq("id", yourRestrictedId))
Since you have mapped the entities User and Role using #ManyToMany relationship, you need to create a DAO/Service class to implement the business logic to filter only userIds and return the same.
This cannot be handled in your Model\Entity classes as it will defy the whole concept of Object-Relational mapping.
I can create the business logic using DAO for your example if you want but you will get 10's of blogs achieving the same.
For your reference,you can check my sample project here.
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 was trying to use #Formula in one of my entity classes.
What I need to do is select a boolean from another entity.
I tried to put the property definition but it keeps throwing a NullPointerException when publishing, I did it as follows
#JoinColumn(name = "SOIR08_FECHA_CARGA", referencedColumnName = "SOIR15_CODI_FECHA", nullable = true)
#ManyToOne(fetch = FetchType.EAGER)
private FechaCarga loadDate;
#JoinColumn(name = "SOIR08_RECEPTOR", referencedColumnName = "SOIR05_CON_DISTRITO_TELEFONICO", nullable = true)
#ManyToOne(optional = true, fetch = FetchType.EAGER)
private DistrictPhone receiver;
#Formula("(select io.done from Table io where io.district = receiver and io.loadDate = loadDate)")
private Boolean isDone;
Then I tried putting the #Formula annotation in the getter
#Formula("(select io.done from Table io where io.district = receiver and io.loadDate = loadDate))")
public Boolean getIsDone() {
return isDone;
}
but when I access the page where the property must be shown I get ORA-00904: "APROB0_"."ISDONE": invalid identifier
Any idea,suggestion or workaround will be highly appreciated.
You have to write pure SQL in the #Formula (but not HQL).
I couldn't find a way to use this annotation without getting errors.
What I decided to do was to add a column in the table, and fill it when a insert was made, not the best way but I needed to do it fast and there was nothing on forums that worked for me.
Thanks.
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.