I am trying to improve the performance of a repository-method. I have a OneToMany-relationship in one of my entities, UserEntity, with a set of AddressEntities that are loaded lazily.
In AddressEntity:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = EntityConstants.COLUMN_USER_ID, referencedColumnName = EntityConstants.COLUMN_USER_ID)
private UserEntity user;
In UserEntity:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<AddressEntity> addresses;
The problem is that when I fetch AddressEntity, a query is made to the database to the user table as well. From what I understand this is to check for the existence of the UserEntity, and to create a proxy object to it.
This takes time, and I am not interested in whether or not the user entity exists in this case. Is there any way to prevent hibernate to do this extra query and simply leave userEntity to null?
Thanks in advance,
Markus
I solved this by only selecting the attributes that I needed:
SELECT ad.longitude, ad.latitude FROM AddressEntity ad
This does not cause hibernate to check for proxy objects on relations that are not selected.
Related
I have a task that require some "special" users to be able to switch between accounts without the need to login. As a starting point I have a join table that consists only of Users ID-s. In form of PRIMARY_USER_ID, and SECONDARY_USER_ID as a foreign keys from USERS table. The first thing that needs to be implemented is GET of all connections between Users. [{"primary_username", "primary_email","secondary_username","secondary_email"}].
I have created a many-to-many relationship on User entity, where both sides of relationship are on User.
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "CONTACTS_ONE_LOGIN",
joinColumns = { #JoinColumn(name = "PRIMARY_CONTACT")},
inverseJoinColumns = {#JoinColumn(name = "SECONDARY_CONTACT")}
)
private Set<Contact> secondaryContacts = new HashSet<>();
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(cascade = {
CascadeType.REMOVE
},
mappedBy = "secondaryContacts")
private Set<Contact> primaryContacts = new HashSet<>();
Now the problem is, when i want to get all connections between contacts, I would need to get first all the information from the join table, and then go through each PRIMARY_CONTACT_ID to get it's connected contacts. Which would result in very low performances.
I wanted to change this to have an CONNECTED_USERS entity, which would have instead of two USER ID-s have two Many-to-one relationships on USER.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PRIMARY_CONTACT_ID")
private Contact contact;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SECONDARY_CONTACT_ID")
private Contact contact;
My question is, is this going to add performance, since in my dev DB i do not have a lot of users to test it properly? Or is there any better way to do this?
It's even harder for us to guess how well your model will perform as we know nothing about your application, its data or its business rules. From your question it seems that only a few users are affected by this requirement. So unless your total user population is in the hundreds of thousands you probably don't need to worry.
Either way it's unlikely the performance benefits of the separate intersection table you propose will justify the overhead of maintaining it. What I do suggest is you build a compound function based index something like this (caveat: right now I have no access to a database so the following is untested and may contain syntax error):
create index connected_users_fbi on your_table (
case when secondary_contact_id is not null then primary_contact_id end,
secondary_contact_id);
This index will be useful for identifying primary contacts with secondary contacts. It may also support finding all the primary contacts which are connected to a secondary contact (if you need that feature) through Index Skip Scan.
Obviously don't take my word for it but try to benchmark it with realistic volumes of data. Your project should have a performance environment where you can do such tests. If it doesn't then it's pretty much doomed.
The problem which i am trying to solve is avoid duplicate items inside a list attribute in hibernate.
Consider the below domain.
public class Account
{
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FI_COMPANY_ACCOUNT", joinColumns = #JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID"))
private List<Company> companies;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AccountDesc> accountDescList;
}
public class Company {}
public class AccountDesc
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
private Account account;
}
I use a Criteria API to fetch Account. In the query i perform fetch using left join for companies and inner join for accountDescList attribute. This help me to get both attributes in first select, and which avoid further selects.
Root<Account> root = criteriaQuery.from(Account.class);
root.fetch("companies", JoinType.LEFT);
root.fetch("accountDescList");
I know the root entity (here Account) can be repeated in the results. I can solve the issue using multiple ways like,
http://in.relation.to/2016/08/04/introducing-distinct-pass-through-query-hint/
https://howtoprogramwithjava.com/how-to-fix-duplicate-data-from-hibernate-queries/
But issue i face is the attribute companies inside the Account has also duplicate entities. This happen if we have more than one entry for accountDescList.
To solve the issue of duplicates in the attribute companies, I feel only solution is to use Set. Could you please clarify on the below questions.
Is there a way other than using Set (for the attribute companies), to solve this issue.
Even if i use can i instruct hibernate to use OrderedSetType (which uses LinkedHashSet). So that i can retain the order of the items as it returned from database. Unfortunately I do not have a attribute to use in OrderBy. I need the whatever default order returned by database.
Thanks in advance.
But the issue I face is the attribute companies inside the Account has also duplicate entities.
That shouldn't happen unless you have duplicate Company entities assigned to the same account.
Using DISTINCT in the Criteria API query will remove root duplicates. However, in your case, it's not worth using JOIN FETCH on both #OneToMany relations since this will cause a Cartesian Product.
You should fetch at most one collection at a time, and maybe use #Subselect fetching for the second collection.
I think that it is much better use Set because a set doesn't allow elements duplicated, also you can overwrite equals method of Company and put it on what fields will be validated when two elements are equals.
The other way would be in setCompanies(List companies) method you can make something logic before this.companies = companies.stream().distinct().collect(Collectors.toList()); or
this.companies = new ArrayList<>(new HashSet(companies)) ;
I have defined roles in my database role_template.
#Entity
#Table(name = "role_template")
#Cacheable
public class Role {
#Id
private int id;
private String name;
#Transient
private final int identity = new Random().nextInt(1000000) + 1;
}
I have one role at this moment with id=1 and name="admin"
My entity User has a list of roles defined as follow
#Entity
#Table(name = "app_user")
public class User {
[...]
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_assign",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
}
Roles are joined to users with my association table
[Table `role_assign`]
int user_id
int role_id
My problem is predictable, #Cacheable does not work.
I tried with 2 users, they have the same Role template, but not the same instance. The transient variable identity isn't equals for the role of the two users.. My app configuration is good, I think I forgot something to make it working for #JoinTable
Is this the javax.persistence.Cacheable annotation? Because it should.
I think your understanding of how caching works with JPA is wrong and your observations is not sufficient to decide if caching takes place or not.
#Cacheable is about the 2nd level cache. If an entity is pulled from the cache it is instantiated from information stored in the cache, and not actually the same instance. The latter wouldn't work. Entities can always only be attached to a single session, but the 2nd level cache lives across sessions.
Two representations of an entity should be the same instance exactly if they belong to the same session.
In order to decide if the cache is used or not you have two good options:
Log the SQL statements issued against the database and see if the data for the entity is selected over and over again, or only once.
Log the cache interaction and see what is going on directly.
How you do that depends on the JPA provider you use. Here are instructions for Hibernate.
Using Hibernate 5, Spring 4
Please consider below codes and mapping between two entities:
User class
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private TruckOwner truckOwner;
//getter setters below
TruckOwner class
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
//getter setter below
When my code tries to update values inside user class like below code:
UserServiceImpl class
#Override
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void resetPassword(Long userId,String newPassword) {
User user = userDAO.findById(userId);
user.setPassword(newPassword);
System.out.println(user.getTruckOwner().getTruckOwnerId());
userDAO.merge(user);
}
When calling userDAO.merge(user); I get below error:
non-transient entity has a null id: com.mymodel.TruckOwner
I am facing this kind of problem in many places in my project, please help me with a proper solution to this problem and why is TruckOwner class has everything null set by hibernate?
We should know the implementation of the userdao merge method but I guess it's called the merge method of hibernate Session interface
In any case the not transient object is the TruckOwner object; hibernat will not fetch the object when you call System.out.println(user.getTruckOwner().getTruckOwnerId()); moreover in that point you are out from hibernate session and if you call any other getter of truckOwner except getTruckOwnerId() you should get the org.hibernate.LazyInitializationException (or similar.. I don't remember correctly)
I guess you have 2 option:
as suggested by staszko032 you should change fetch type to EAGER
When you load the user object by using the userDAO.findById(userId); you should fetch the truckOwner object inside the hibernate session by calling any other method inside the userDAO.findById(userId); implementation and inside the hibernate session
I hope it's useful
Angelo
Try this for the truck class:
#Entity
#Table(name = "truckOwner")
public class TruckOwner{
...
private User user;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "truckOwner", cascade = CascadeType.ALL)
public User getUser() {
return this.user;
}
}
And this for the User class:
#Entity
#Table(name = "user")
public class User{
private TruckOwner truckOwner;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
public TruckOwner getTruckOwner() {
return this.truckOwner ;
}
}
Eager mode is not a solution if you are making a production application. Problem is in your session is already closed when your are trying to getTruckOwner. Try to propagate session to all resetPassword method.
First you should not be using merge here! You should almost never use merge.
Merge should be used when you have an unmanaged entity (serialized or loaded by a previous persistence context) and wish to merge it into the current persistence context, to make it a managed entity. Your entity is already managed since a persistence context was loaded with your DAO inside a container managed transaction. This means you don't have to do even have to call save, any changed to a managed entity will be detected and persisted when the transaction commits.
On the surface JPA looks easy, because a lot of the complexity is not visible on the surface, god knows I banged my head against the wall when I started with TopLink 7 years ago, but after reading about Object life cycles and application versus container managed persistence context, I made a lot more mistakes, and it was much easier to decipher the error messages.
Another solution will be changing the fetch type to EAGER mode in User class. With LAZY mode, hibernate doesn't retrieve TruckOwner connected with user, as it is not explicitly needed in your case. Eventually TruckOwner is null for user, however it has nullable = false option set, and that's why merge fails.
I have a Group entity that has a list of User entities in a many to many relationship. It is mapped by a typical join table containing the two IDs. This list may be very large, a million or more users in a group.
I need to add a new user to the group, typically that will be something like
group.getUsers().add(user);
user.getGroups().add(group);
em.merge(group);
em.merge(user);
If I understand typical JPA operation, will this require pulling down the entire list of 1 million+ users into the collection in order to add the new user and then save? That doesn't sound very scalable to me.
Should I simply not be defining this relationship in JPA? Should I be manipulating the join table entries directly in a case like this?
Please forgive the loose syntax, I'm actually using Spring Data JPA so I don't directly use the entity manager directly very often, but the question seems to be general to JPA so I wanted to pose it that way.
Design your models like this and play with UserGroup for associations.
#Entity
public class User {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user",fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
#Entity
#Table(name="user_group",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "group_id"})})
public class UserGroup {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#ForeignKey(name = "usergroup_user_fkey")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "group_id", nullable = false)
#ForeignKey(name = "usergroup_group_fkey")
private Group group;
}
#Entity
public class Group {
#OneToMany(cascade = CascadeType.ALL, mappedBy="group", fetch = FetchType.LAZY )
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
Do like this.
User user = findUserId(id); //All groups wont be loaded they are marked lazy
Group group = findGroupId(id); //All users wont be loaded they are marked lazy
UserGroup userGroup = new UserGroup();
userGroup.setUser(user);
userGroup.setGroup(group);
em.save(userGroup);
Using the ManyToMany mapping effectively is caching the collection in the entity, so you might not want to do this for large collections, as displaying it or passing the entity around with it triggered will kill performance.
Instead you might remove the mapping on both sides, and create an entity for the relation table that you can use in queries when you do need to access the relationship. Using an intermediate entity will allow you to use paging and cursors, so that you can limit the data that might be brought back into usable chunks, and you can insert a new entity to represent new relationships with ease.
EclipseLink's attribute change tracking though does allow adding to collections without the need to trigger the relationship, as well as other performance enhancements. This is enabled with weaving and available on collection types that do not maintain order.
The collection classes returned by getUsers() and getGroups() don't have to have their contents resident in memory, and if you have lazy fetching turned on, as I assume you do for such a large relationship, the persistence provider should be smart enough to recognize that you're not trying to read the contents but just adding a value. (Similarly, calling size() on the collection will typically cause a SQL COUNT query rather than actually loading and counting the elements.)