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.
Related
I have a Parent User Class that has multiple ManyToMany Relationships.
#Table(name = "user")
public class User {
..
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "user_address",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "address_id")}
)
#JsonIgnore
private final List<Address> addresses = new ArrayList<Address>();
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "reports",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "reports_id")}
)
#JsonIgnore
private final List<Reports> reports = new ArrayList<Reports>();
}
When I access the FIRST ManyToMany property, everything works fine. However, immediately after
accessing the first, when I try to access the SECOND ManyToMany Property I get the "could not initialize proxy - no Session" exception:
#Component
public class Combiner {
public void combineData() {
...
List<Address> addresses = user.getAddress(); // This works
List<Reports> reports = user.getReports(); // Get the error here
..
}
}
The Address and Reports classes have the inverse relationship as many ManyToMany back to the User Entity Above.
public class Address {
#ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
public class Reports {
#ManyToMany(mappedBy = "reports", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
I tried searching SO for the same error where there are MULTIPLE relationships like mine and the first passes but second fails, but could'nt find a post (or google couldn't understand the search terms, if anyone knows a pre-existing one - please let me know).
Could someone assess what else Im missing?
I've tried these so far to no avail:
Added #Transactional to the parent Service class that calls Combiner above
Made the second failing relationship EAGER. (as i understand it you cant make BOTH EAGER since i get a multiple bags error probably because of Cartesian join)
AM Using SpringBoot (2.2.4) with Hibernate Core {5.4.10.Final}
Approach one:
Make #ManyToMany uni-directional. The exception clearly says it can not initialize the collection of role you have in User class.
As you asked in the comment section Why can't this use case be Bi Directional - You can make this bi-directional as well.
Approach two: make collection of role EAGER or use Hibernate.initialize() to initialize the collection.
Bonus: you can make both collection EAGER by using Set not List.
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.
My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);
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.
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.