Playframework sends 2 queries for fetched query - caching

I currently have problems with the JPA at the play framework 1.2.4.
I need to have a UserOptions model in a separate database and want to join it lazyly cause its only needed in one query.
In this query I want to load the options eagerly and by searching I found out that can only be done by using a join query.
If I use eager instead oder lazy, everything would be fine by using User.findById() and the options and the user is found in one query.
But play sends two queries when I use a 'left join fetch' query. So heres the query:
User.find("
SELECT
user
FROM
User user
LEFT JOIN FETCH
user.options options
WHERE
user.id = ?
", Long.parseLong(id)).first();
And here the models:
#Entity
public class User extends Model
{
#OneToOne(mappedBy = "user", fetch = FetchType.LAZY)
public UserOptions options;
// ...
}
#Entity
public class UserOptions extends Model
{
#OneToOne(fetch = FetchType.LAZY)
public User user;
}
The question is why play sends two query for the fetch query?
Thanks in advance

Okay, got it myself. The query is still the same.
The problem was, that custom queries are not cached by default. So I used this piece of code to provide a database cache (only for the current request).
// Get database cache instance
EhCacheImpl cache = EhCacheImpl.getInstance();
// Get cached user
User user = (User)cache.get("UserWithOptions_"+id);
// Check whether a user is cached
if(user == null)
{
// Get the user
user = User.find("SELECT user FROM User user LEFT JOIN FETCH user.options options WHERE user.id = ?", Long.parseLong(id)).first();
}
// Refresh cache
cache.add("UserWithOptions_"+id, user, 0);
May the force be with you!

Related

Hibernate OnetoMany with Fetch Lazy giving LazyInitializationException

I am a newbie to Java Persistence API and Hibernate and using Spring JPA repositories for querying in DB. Now I have two entities in Parent <-> Child relationship with Parent entity with #OneToMany and Child entity with #ManyToOne mapping.
Parent Entity:-
#Entity
#Table(name = "PERSONS")
public class Persons {
...
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<Cards> cards = new ArrayList<Cards>();
...
}
Child Entity:-
#Entity
#Table(name = "CARDS")
public class Cards {
...
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", nullable = false, insertable = false, updatable = false)
public Person person;
...
}
And I am using my PersonsRepository like below :-
#Repository
public interface PersonsRepository extends JpaRepository<Persons, String> {
....
}
Now the fetchType being used in the relationship is LAZY at both the ends. Now whenever I tried to loop over a List and tried to process the cards for each using person.getCards(), it gives me below error:-
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.abc.Persons.cards, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
Now I have found everyone saying that using LAZY is the best approach in Hibernate and it says lot more about the correct design of code as well. I agree the way I have used person.getCards() will not have any open session and that is the reason it is giving me LazyInitializationException but the intent behind this is to save a lot more DB calls.
Assuming I have 1000 persons list, that means I have to make 1000 separate calls to getCards() for each person. That's why if I use the FETCHTYPE.EAGER in Person #OneToMany, what is the performance impact since everything will be fetched eagerly.
Need suggestions about the best practices followed for such kind of problems. TIA.
Edit:-
I have a method in service class where I am using #transactional for that like below:-
#Transactional(readOnly = true)
public void fetchData(Integer param1, Timestamp param2, Timestamp param3, List<String> param4, NavigableMap<Long, List<Cards>> param5) {
List<Persons> validPersons = personRepo.getCardsPerPerson(param2, param3);
if(validPersons != null && !validPersons.isEmpty()) {
// store the cards on the basis of epoch timestamp
prepareTimestampVsCardsMap(validPersons, param4, param5);
}
}
private void prepareTimestampVsCardsMap(List<Persons> validPersons, List<String> uList, NavigableMap<Long, List<Cards>> timestampVsCardsList) {
for(Person person : validPersons) {
Long epoch = order.getOrderTime().getTime();
Set<Cards> cardsPerPerson = person.getCards();
}
}
Also, the query being used in repository for getting the cards associated to a person is using join fetch as below:-
#Query(value = "select p from Person p join fetch Cards c on p.id = c.id WHERE p.orderTime BETWEEN ?1 AND ?2 ORDER BY orderTime ASC")
public List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
I am still getting the same above mentioned LazyInitializationException. Can anyone please help.
First of all, it's always better to use FetchType.LAZY instead of FetchType.EAGER. Why? Because you might not need all the data every time. If you want to return a list of Persons and display them somehow, somewhere, do you need to fetch all of their cards as well? If not, then FetchType.LAZY would be the better option, and you would then control how much data you need.
LazyInitializationException usually indicates that you didn't fetch all the data you need while your Session was opened. There are many ways to fetch associated data (none of which is keeping the Session opened while processing request):
1. using join fetch in your JPQL/HQL
#Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
2. if you're using Spring Data, you could use #EntityGraph instead of join fetch
#EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();
That way, every time you call getPersons, it will fetch cards as well. Of course, you couldn't use this one if you have to write #Query.
If you're using Spring Data's naming conventions for some simple queries, then #EntityGraph would be an option for fetching associations.
3. using Criteria API
Again, if you're using Spring Data, this is just a fallback solution in case you end up with MultipleBagFetchException. I will not go into details for this one, but in case you encounter this exception, you'll find solution in Vlad Mihalcea's blog post The best way to fix the Hibernate MultipleBagFetchException.
You are under the misconception that EAGER loading means Hibernate will fetch all data with one statement, this is false. With EAGER as a strategy, the framework will just do every query required to fetch all data for every entity.
Example: If one entity has 2 EAGER relationships, fetching one will result in 3 statements, one to load the entity, one for each of its relationships. If you have 3 entities, you will have 7 statements, the initial statement loading the 3 objects, plus 2 per object.
When your treatment requires everything, there is no real performance impact at the moment. But most applications are not made of one treatment. This means every treatment in your application will load everything which is EAGER, even if not needed. This will effectively slow everything down. You also risk loading all your database in memory if everything is in EAGER.
This is why LAZY is the recommended approach.
As for your LazyInitializationException, it seems in your stack trace that you are using the stream API. It's a wild guess due to missing details, but JPA/Hibernate doesn't handle sharing a session between threads, so if you are using parrallelStream it could cause the problem.

Incorrect derived query for byId in Spring Data Neo4j

I have two entities: User and Connection, along with two appropriate repositories. Both entities has #GraphId id field. Connection entity has User user field.
In ConnectionRepository interface I added following method:
List<Connection> findByUserId(long userId)
But it doesn't work. It generates incorrect cypher query. I think it incorrect, because it contains clause like this:
WHERE user.id = 15
which is not working, because id is not a property. It must be:
WHERE id(user) = 15
Is this a bug? In any case, how can I get it to work?
The derived query translates to the property id of the user defined on the Connection. It is quite possible that node entities contain a user managed id property as well and it would be incorrect to assume that id is always the node id.
In this case, you might want to use a #Query instead.
#Query("MATCH (user:label) WHERE ID(user)={0} return user")
List<Connection> findByUserId(long userId)

What is the work-around for deleting orphan entities using JPA 2.0 and #OneToMany?

I'm using JPA 2.0, Hibernate 4.1.0.Final, Spring 3.1.1.RELEASE, and Java 1.6. I have this entity with a one-to-many relationship to another entity …
import javax.persistence.CascadeType;
...
#Entity
#Table(name = "classroom")
public class Classroom implements Serializable
{
...
#OneToMany(mappedBy = "classroom", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<ClassroomUser> roster;
However, when I update my entity with a different set of ClassroomUser objects
classroom.setRoster(newRoster);
and save the entity, all the previous ClassroomUser objects remain. What is the proper/shortest way to update my entity while removing all the orphan records from the database?
Thanks, - Dave
Use orphanRemoval:
#OneToMany(mappedBy="classroom", cascade={CascadeType.ALL}, orphanRemoval=true)
Whenever an entry is removed from the persistent set, it will get deleted. And this means you need to work with the persistent set. I.e. you are not allowed to replace the set, instead you should do:
classroom.getRoster().clear();
classroom.getRoster().addAll(newRoster);
EXAMPLE how to synchronize persistent set with a user required set:
/**
* Assemble ClassroomUser relations.
* #param classroom Classroom entity. Must be attached persistent or transient. Never null.
* #param userIds Collection of user identifiers. Can be empty. Never null.
*/
private void assembleClassroomUsers(Classroom classroom, Collection<Integer> userIds) {
// Make sure relation set exists (might be null for transient instance)
if (classroom.getUsers() == null) {
classroom.setUsers(new HashSet<ClassroomUser>());
}
// Create working copy of the collection
Collection<Integer> ids = new HashSet<Integer>(userIds);
// Check existing relations and retain or remove them as required
Iterator<ClassroomUser> it = classroom.getUsers().iterator();
while (it.hasNext()) {
Integer userId = it.next().getUser().getId();
if (!ids.remove(userId)) {
it.remove(); // This will be picked by the deleteOrphans=true
}
}
// Create new relations from the remaining set of identifiers
for (Integer userId : ids) {
ClassroomUser classroomUser = new ClassroomUser();
classroomUser.setClassroom(classroom);
// User must not have ClassroomUser relations initialized, otherwise Hibernate
// will get conflicting instructions what to persist and what to drop => error.
// It might be safer to use dummy transient instance...
User dummyUser = new User();
dummyUser.setId(userId);
classroomUser.setUser(dummyUser);
classroom.getUsers().add(classroomUser);
}
}
This approach might seem a little bit complex. You might be able to create something simpler (but probably not too much) with custom equals/hashCode and some Set<E> manipulation methods (e.g. from Guava).

Joining asp_Users db tables in LINQ using asp.net MVC

I am working on an example asp.net project using MVC, but my database is a live one which I can't make changes to (technically it's the test version of this database but my point is changes to the database aren't possible).
I use the UserID from the asp_Users table to store who makes changes to various aspects of the system, and I want to start showing the user name in various front-end tables, but how do I link the tables to get this user name?
So to clarify, I'm going to want to do this for several tables throughout the system so I was hoping I could do it using LINQ.
I can get the info I want from using the join query, but how do I pass this to my View to use?
var plans = from users in db.aspnet_Users
join import in db.Plan_Imports
on users.UserId.ToString()
equals import.User_ID
select new
{
Date = import.Date,
UserName = users.UserName
};
Sample tables
asp_Users
UserID
UserName
...
table1
ID
field1
field2
...
User_ID <--- ref to asp_Users
table2
ID
field1
field2
...
User_ID <--- ref to asp_Users
I would create a ViewModel for each view.
ViewModel is just a POCO class
public class PlanViewModel
{
public string UserName { set;get;}
public DateTime ImportDate { set;get;}
}
Then Get the Data to this ViewModel/Collection of ViewModel using LINQ Projections from your query.
public ActionResutl Show()
{
var plans = (from users in db.aspnet_Users
join import in db.Plan_Imports
on users.UserId.ToString()
equals import.User_ID
select new PlanViewModel
{
ImportDate = import.Date,
UserName = users.UserName
}).ToList();
return View(plans);
}
Now Lets make our view strongly typed to a collection of our PlanViewModel
#model List<PlanViewModel>
#foreach(var plan in Model)
{
<p>#plan.UserName</p>
<p>#plan.ImportDate.ToString()</p>
}
The solution provided by Shyju worked perfectly, and it wasn't too complex to do, however I decided that I didn't think using LINQ was appropriate in this case as the code was getting out of hand for what should be a simple call.
What I did instead was use a stored procedure to get the information, and saved it to a complex object which I passed to my view.
The code is now much neater and easier to manage, as the code above became just
var plans = db.SP_SelectImports();
Read more about stored procedure mapping here: http://dotnet.dzone.com/news/mapping-stored-procedure

Can I filter the Users returned by GetAllUsers based on a role they are in

I am trying to create an administration interface where users and roles (among other things) can be administered. I have a list of users who can be edited, deleted or viewed. I have this code in the action:
var model = Membership.GetAllUsers()
.Cast<MembershipUser>()
.Select(x => new UserModel
{
UserName = x.UserName,
Email = x.Email,
UserRoles = Roles.GetRolesForUser(x.UserName)
});
return View(model);
This is all fine except that I don't want admins to be able to edit each other. So I need to filter out all users in the "super admin" role. I can certainly figure this out by stepping through each role for each user to see if they are a member. I am wondering if there is a nice sucinct way to do this by filtering the result set in the Select statement, or using Except or Where
I would normally think about the sql I want generated then try write the linq, filtering by roles should be fairly easy with a simple where statement.
However it appears you're trying to abstract each part of the query into smaller bits, this may make it easier to write but can have a devastating effect on performance. For example, I wouldn't be suprised if the GetRolesForUser method you are calling causing an extra database query per user that is returned by GetAllUsers, using the Include method is a much nicer way to get all roles at the same time.
var model = context.Users
.Include(user => user.UserRoles)
.Where(user => !user.UserRoles.Any(role => role == superAdmin)
.Select(user => new UserModel() { UserName = user.UserName, Email = user.Email, UserRoles = user.UserRoles});

Resources