I need to load the Post entities along with the PostVote entity that represents the vote cast by a specific user (The currently logged in user). These are the two entities:
Post
#Entity
public class Post implements Serializable {
public enum Type {TEXT, IMG}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "section_id")
protected Section section;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "author_id")
protected User author;
#Column(length = 255, nullable = false)
protected String title;
#Column(columnDefinition = "TEXT", nullable = false)
protected String content;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
protected Type type;
#CreationTimestamp
#Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
/*accessor methods*/
}
PostVote
#Entity
public class PostVote implements Serializable {
#Embeddable
public static class Id implements Serializable{
#Column(name = "user_id", nullable = false)
protected int userId;
#Column(name = "post_id", nullable = false)
protected int postId;
/* hashcode, equals, getters, 2 args constructor */
}
#EmbeddedId
protected Id id;
#ManyToOne(optional = false)
#MapsId("postId")
protected Post post;
#ManyToOne(optional = false)
#MapsId("userId")
protected User user;
#Column(nullable = false)
protected Short vote;
/* accessor methods */
}
All the associations are unidirectional #*ToOne. The reason I don't use #OneToMany is because the collections are too large and need proper paging before being accessed: not adding the #*ToManyassociation to my entities means preventing anyone from naively doing something like for (PostVote pv : post.getPostVotes()).
For the problem i'm facing right now I've come with various solutions: none of them looks fully convincing to me.
1° solution
I could represent the #OneToMany association as a Map that can only be accessed by key. This way there is no issue caused by iterating over the collection.
#Entity
public class Post implements Serializable {
[...]
#OneToMany(mappedBy = "post")
#MapKeyJoinColumn(name = "user_id", insertable = false, updatable = false, nullable = false)
protected Map<User, PostVote> votesMap;
public PostVote getVote(User user){
return votesMap.get(user);
}
[...]
}
This solution looks very cool and close enough to DDD principles (i guess?). However, calling post.getVote(user) on each post would still cause a N+1 selects problem. If there was a way to efficiently prefetch some specific PostVotes for subsequent accesses in the session then it would be great. (Maybe for example calling from Post p left join fetch PostVote pv on p = pv.post and pv.user = :user and then storing the result in the L1 cache. Or maybe something that involves EntityGraph)
2° solution
A simplistic solution could be the following:
public class PostVoteRepository extends AbstractRepository<PostVote, PostVote.Id> {
public PostVoteRepository() {
super(PostVote.class);
}
public Map<Post, PostVote> findByUser(User user, List<Post> posts){
return em.createQuery("from PostVote pv where pv.user in :user and pv.post in :posts", PostVote.class)
.setParameter("user",user)
.setParameter("posts", posts)
.getResultList().stream().collect(Collectors.toMap(
res -> res.getPost(),
res -> res
));
}
}
The service layer takes the responsability of calling both PostRepository#fetchPosts(...) and then PostVoteRepository#findByUser(...), then mixes the results in a DTO to send to the presentation layer above.
This is the solution I'm currently using. However, I don't feel like having a ~50 parameters long in clause might be a good idea. Also, having a separate Repository class for PostVote may be a bit overkill and break the purpose of ORMs.
3° solution
I haven't tested it so it might have an incorrect syntax, but the idea is to wrap the Post and PostVote entity in a VotedPost DTO.
public class VotedPost{
private Post post;
private PostVote postVote;
public VotedPost(Post post, PostVote postVote){
this.post = post;
this.postVote = postVote;
}
//getters
}
I obtain the object with a query like this:
select new my.pkg.VotedPost(p, pv) from Post p
left join fetch PostVote pv on p = pv.post and pv.user = :user
This gives me more type safeness than the the solutions based on Object[] or Tuple query results. Looks like a better alternative than the solution 2 but adopting the solution 1 in a efficient way would be the best.
What is, generally, the best approach in problems like this? I'm using Hibernate as JPA implementation.
I could imagine the standard bi-directional association using #OneToMany being a maintainable yet performant solution.
To mitigate n+1 selects, one could use e.g.:
#EntityGraph, to specify which associated data is to be loaded (e.g. one user with all of it's posts and all associated votes within one single select query)
Hibernates #BatchSize, e.g. to load votes for multiple posts at once when iterating over all posts of a user, instead having one query for each collection of votes of each post
When it comes to restricting users to perform accesses in less performant ways, I'd argue that it should be up the API to document possible performance impacts and offer performant alternatives for different use-cases.
(As a user of an API one might always find ways to implement things in the least performant fashion:)
Related
This question already has answers here:
Problem with LazyInitializationException
(2 answers)
Closed 4 months ago.
I have 3 tables in the DB and 3 JPA entities respectively in Java application.
#Data
#Entity
public class Fraud {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "fraud_type")
private String fraudType;
#Column(name = "fraud_value")
private String fraudValue;
#OneToMany(mappedBy = "fraud", fetch = FetchType.EAGER)
private List<FraudActionEntity> fraudActions;
}
#Data
#Entity
public class FraudActionEntity {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "fraud_id")
private Fraud fraud;
#ManyToOne
#JoinColumn(name = "action_id")
private Action action;
#Column(name = "enabled")
private Boolean enabled;
}
#Data
#Entity
public class Action {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "attribute_key")
private String attributeKey;
#Column(name = "attribute_value")
private String attributeValue;
}
#Repository
public interface FraudRepository extends JpaRepository<Fraud, Integer> {
public Fraud findByFraudTypeAndFraudValue(String fraudType, String fraudValue);
}
My use case
On a certain type of fraud, I want to traverse all the actions that triggers from that type of fraud and act on them.
Access code
Fraud fraud = fraudRepository.findByFraudTypeAndFraudValue("Type", "Value");
log.info(fraud.getFraudActions().get(0).getAction());
When I above code runs, everything works OK. I get the fraud and fraudActions associations as well, without getting any error.
I was under the impression that as both entities Fraud and FraudActionEntity are fetching each other eagerly, so it should give some error like cyclic fetch/infinite fetch loop, but it didn't!
Why did it work? And when exactly will give it error like cyclic fetch error OR infinite fetch loop error? And if it does give a cyclic fetch error, can we fix it using lazy fetch at #ManyToOne side as given below:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fraud_id")
private Fraud fraud;
Update: A simple and very effective work-around towards the LazyInitializationException is to annotate your method with #Transactional annotation. This will create and maintain the transaction while the method is being executed, thereby allowing your code to make the necessary calls to the DB's lazy init objects. Learn more about it here.
The return type of your JPA repository method should be a List of the Entity object, since the result could be more than one row (that is probably why you are getting the null of the fraud variable).
Regarding the Fetch strategy, you could use Eager on that particular association or maybe other strategies. One possible solution would be to make a second query in case you need the lazy-loaded FraudAction list of objects.
Also, as a side-note avoid using lombok data annotation, and always make sure that you have a NoArgsConstructor in your Entity/DTO classes (in your case #Data adds that by accident since it includes #RequiredArgsConstructor and you do not have any final variables.
I'm currently implementing a doc with a like button like this:
The like button is associated with certain user account. When you press a like, it will stay liked for that user (similar to youtube video).
My entities and DTOs are below:
Doc.java:
#Entity(name = "Doc")
#Table(name = "doc")
#Data
public class Doc {
//Unrelated code reacted for clarity
#ManyToMany(cascade = {
CascadeType.MERGE,
CascadeType.PERSIST
})
#JoinTable(
name = "doc_user_dislike",
joinColumns = #JoinColumn(name = "doc_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<UserWebsite> dislikedUsers;
#ManyToMany(cascade = {
CascadeType.MERGE,
CascadeType.PERSIST
})
#JoinTable(
name = "doc_user_like",
joinColumns = #JoinColumn(name = "doc_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<UserWebsite> likedUsers;
}
User.java:
#Entity
#Table(name = "user_website")
#Data
public class UserWebsite {
//Unrelated code reacted for clarity
#ManyToMany(mappedBy = "likedUsers")
private Set<Doc> likedDocs;
#ManyToMany(mappedBy = "dislikedUsers")
private Set<Doc> dislikedDocs;
}
DocDetailsDTO.java (This will be sent to client).
#Data
public class DocDetailsDTO {
private Long id;
private Boolean isDisliked;
private Boolean isLiked;
}
I'm having some solutions:
Add a field called isLiked to Doc.java with #Formular combine with
#Transient and perform queries to DB.
Have another API which accept from Client a list of DocID, and a
UserID, then return a list of DocID that UserID liked.
Check if UserID exist in likedUsers list (not very efficient,
sometimes not feasible since I have to initialize that big
lazy-loaded list).
The question is: What is the most efficient way to retrieve liked/disliked status for many post at once (>10 doc but max 100 doc per request) for about thousand users (1000 CCU) at once ? Are above solutions already optimal ?
Any help is appreciated. Thanks for your time reading through the question.
If I understand the problem correctly, this approach is not correct. You want to determine if a given user likes specified documents, so the formula would need a user id parameter, which you have no way to pass to the formula. Even if somehow #Formula could be used, it leads to N+1 problem (extra query per each document). Plus, you use managed entities which means extra dirty checking at the end.
This one is optimal in my opinion - one query, capable of using projection (no managed entities).
As you notice, this will kill your application and database. Plus, again you use managed entities which means extra dirty checking at the end. Definitely don't use this one.
I need some help with the JPA Framework.
I've read some answers "kind of" about this topic but I couldn't reach any conclusion.
First heres an examplo of the design i'm wooking with.
#BusinessObject
public class ClassA {
#Column(name = "ID", nullable = false)
private Long id;
#OneToMany(mappedBy = "classAAttr")
private Collection<ClassAB> classABCollection;
//STUFF AND OTHER COLUMNS.....
}
public class ClassAB {
#Column(name = "ID", nullable = false)
private Long id;
#JoinColumn(name = "TABLE_A_ID", referencedColumnName = "ID")
#ManyToOne
private ClassA classAAttr;
#JoinColumn(name = "TABLE_B_ID", referencedColumnName = "ID")
#ManyToOne
private ClassB classBAttr;
//STUFF AND OTHER COLUMNS.....
}
#BusinessObject
public class ClassB {
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "ORDERCLAUSE", nullable = false)
private String orderClause;
//STUFF AND OTHER COLUMNS.....
}
So I need to order the classABCollection in ClassA by the orderClause attribute in ClassB, but I can't find the right #OrderBy() clause AND/OR location for it.
I've read some things about the Comparator Interface but, unfortunately, due to business policy, I need to be sure that there is no other way...
How Should I Do It?
Thank you guys in advance.
The API docs for #OrderBy note:
The property or field name must correspond to that of a persistent
property or field of the associated class or embedded class within :
http://docs.oracle.com/javaee/6/api/javax/persistence/OrderBy.html
so sorting AB in A by a property of B is not possible.
The alternatives are to write a query or do an in memory sort by some means. Hibernate, for example, has an #Sort annotation which you can use to apply an in-memory sort on load, either by having the target Entity implement Comparable or by specifying a Comparator:
See section 2.4.6.1:
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
So - I found a roundabout. A query like one below wont work:
select * from A a where xyz
order by a.reference or a.reference.id
However, I found that by adding a function, we can make the query work:
(Do note, the reference need not be null.. and use appropriate values.)
select * from A a where xyz
order by coalesce(a.reference , 0)
I'm currently a little blocked with this and I can't see it clearly.
So I hope one of you have good idea's to help me.
The important code at the moment :
#Entity
#Table(name = "T_NOTA_RECIPIENT")
public class NotaRecipient extends PersistentEntity {
#Id
#Column(name = "NOTA_RECIPIENT_SID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "STATUS", insertable = true, updatable = true)
#Enumerated(EnumType.STRING)
private Status status = Status.NEW;
#ManyToOne
#JoinColumn(name = "NOTA_SID", referencedColumnName = "NOTA_SID", nullable = false)
private Nota nota;
#ManyToOne
#JoinColumn(name = "CREATOR_OFFICE_SID", referencedColumnName = "OFFICE_SID", nullable = false)
private Office creator;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "notaRecipient")
private Set<FollowUp> followUps;
...
}
Now, actually I don't want to load all the FollowUp who are in the DB but just the one of the current user.
But the problem is that I want to include the FollowUp so I can do database paging query.
We use hibernate, Spring Data and Query DSL with BooleanBuilder to "refine" our search.
I was thinking of using #Formula but this need to be a constant String so I can't include current userId in that.
Second solution could be setting the FollowUp as #Transient and fetch it myself in the DB and set it in mine service.
Problem here is that I can't use it as filter then or ordering by it.
#Formula doesn't have so much documentation, so is it possible to make a #Transient user and use that in the #Formula?
I asked some colleagues but they couldn't help me.
So then it's the time for asking here.
I can get the current user in the API, so that's no problem.
Anybody have alternative solutions?
You can define a mapping with expression
#JoinColumnOrFormula(formula=#JoinFormula(value="(SELECT f.id
FROM follow_up_table f
WHERE f.nota_id=id
and f.user_id={USER_ID})",
referencedColumnName="...")
And then add hibernate interceptor (see the example) and change the SQL on fly replacing {USER_ID} with real value in the
/**
* Called when sql string is being prepared.
* #param sql sql to be prepared
* #return original or modified sql
*/
public String onPrepareStatement(String sql);
I need some help with the JPA Framework.
I've read some answers "kind of" about this topic but I couldn't reach any conclusion.
First heres an examplo of the design i'm wooking with.
#BusinessObject
public class ClassA {
#Column(name = "ID", nullable = false)
private Long id;
#OneToMany(mappedBy = "classAAttr")
private Collection<ClassAB> classABCollection;
//STUFF AND OTHER COLUMNS.....
}
public class ClassAB {
#Column(name = "ID", nullable = false)
private Long id;
#JoinColumn(name = "TABLE_A_ID", referencedColumnName = "ID")
#ManyToOne
private ClassA classAAttr;
#JoinColumn(name = "TABLE_B_ID", referencedColumnName = "ID")
#ManyToOne
private ClassB classBAttr;
//STUFF AND OTHER COLUMNS.....
}
#BusinessObject
public class ClassB {
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "ORDERCLAUSE", nullable = false)
private String orderClause;
//STUFF AND OTHER COLUMNS.....
}
So I need to order the classABCollection in ClassA by the orderClause attribute in ClassB, but I can't find the right #OrderBy() clause AND/OR location for it.
I've read some things about the Comparator Interface but, unfortunately, due to business policy, I need to be sure that there is no other way...
How Should I Do It?
Thank you guys in advance.
The API docs for #OrderBy note:
The property or field name must correspond to that of a persistent
property or field of the associated class or embedded class within :
http://docs.oracle.com/javaee/6/api/javax/persistence/OrderBy.html
so sorting AB in A by a property of B is not possible.
The alternatives are to write a query or do an in memory sort by some means. Hibernate, for example, has an #Sort annotation which you can use to apply an in-memory sort on load, either by having the target Entity implement Comparable or by specifying a Comparator:
See section 2.4.6.1:
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
So - I found a roundabout. A query like one below wont work:
select * from A a where xyz
order by a.reference or a.reference.id
However, I found that by adding a function, we can make the query work:
(Do note, the reference need not be null.. and use appropriate values.)
select * from A a where xyz
order by coalesce(a.reference , 0)