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)
Related
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:)
I have the following Entity. In this I want to fetch all data except phoneNumber. What will be the best solution? It would be fine if I could do it with annotation.
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "employee_name")
private String name;
#Column(name = "gender")
private char gender;
#Column(name = "date_of_birth")
private String dob;
#Column(name = "skills")
private String[] skills;
#Column(name = "phone_number")
private String phoneNumber;
//getter setter
}
To tell what will be the best way to do this you have to say why you want to do this and what you want to achieve.
There are many options:
omit the getter
use a projection (DTO or interface)
use inhreitance
use inheritance with #MappedSuperclass
Can you please use #Transient on your field. If it is a subclass of anyclass then on class level please use #Embedded.
If not then you need to read this as this is always lazy fetch From Hibernate, Chapter 19. Improving performance:
Lazy attribute fetching: an attribute or single valued association is fetched when the instance variable is accessed. This approach requires buildtime bytecode instrumentation and is rarely necessary.
You can use #Column(insertable = true, updatable = false) and I am not sure if we can ignore while fetching using entity. you can achieve your requirements using JPA Projections
Also,#JsonIgnore may be useful. it is used to tell Jackson to ignore a certain property of a Java object but
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);
Inside a service class, I have a method that is called from a #Transactional method. I have verified that I have a transaction active at the point this code is called. I realize that I don't have a DA layer when I should, but I am working with a legacy application that makes doing things the 'right' way more of a hassle than it's worth at this point.
The mappings look like this:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
#JoinColumn(name = "FOO_ID")
#OneToOne(fetch = FetchType.LAZY, optional = false)
public Bar getBar() { return bar; }
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
#GeneratedValue(generator = "someSeq")
#GenericGenerator(name = "someSeq",
strategy = "foreign",
parameters = {
#Parameter(name = "property", value = "foo")
})
public String getId() { return id; }
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "FOO_ID")
public Foo getFoo() { return foo; }
// SETTERS INCLUDED
}
The method looks something like this:
public String createFoo(Foo foo) {
Session ses = getSessionFactory().getCurrentSession();
Bar bar = new Bar();
bar.setFoo(foo);
foo.setBar(bar);
ses.save(foo);
ses.save(bar);
System.out.println(foo.getId()); // yields the ID value set by generator
System.out.println(bar.getId()); // yields same ID value as above
ses.flush();
ses.refresh(foo);
}
Now, with org.hibernate.SQL logging set to DEBUG, I can see that the insert statements for both Foo and Bar are created, but the refresh after the flush is called throws a org.hibernate.UnresolvableObjectException: No row with the given identifier exists exception.
What could cause this? The database used is Oracle 11gR2.
UPDATE
I have narrowed my issue down to sessions. It seems that calling the currentSession.flush() is not writing the data to the database as expected for the refresh. If I comment out the rest of the method, it will commit at the end and everything will be in the database.
Doing the flush/refresh will not return the hydrated object, however, so I cannot use the database-populated values (set by column defaults) later on in my transaction. I also cannot split the transaction into multiple ones because I need to be able to rollback at any point in the method.
Any ideas as to why the flush is not giving me accessible data in the database?
ANOTHER UPDATE
I have moved a lot of code around just to try and isolate the issue, and I'm still having problems. I also got rid of the relationship between the two entities to try and handle everything manually, just to see if that would fix the problem. Considering all the comments from Steve, here's what I have now:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
public String getId() { return id; }
// SETTERS INCLUDED
}
#Service('fooService')
#Transactional(readOnly = true)
class FooService {
#Autowired
SessionFactory sessionFactory // populated using Spring config:
// org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
#Transactional(readOnly = false)
public void doSomeStuff(Foo fooToSave) {
sessionFactory.getCurrentSession().saveOrUpdate(fooToSave);
Bar bar = new Bar(fooToSave); // this populates the Bar.Id field
sessionFactory.getCurrentSession().saveOrUpdate(bar);
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().refresh(fooToSave); // exception thrown here
}
}
YET ANOTHER UPDATE
After quite a bit of playing around in Oracle-land to make sure that the SQL was running on the same session and the like, I've found the issue. Even though Hibernate is logging that the SQL bind variables are being set, they actually are not. Using Oracle 11gR2's V$SQL_BIND_CAPTURE, I was able to see using the SQL ID that was executed last (verified to be the insert statement) had 24 bind variables and not one of them ever had a value bound to it. Still not sure what's causing the values to be blank, but I am quite a bit closer to finding my answer. It has to be a problem with my mappings, which I cannot put here in entirety.
Being bind variables, I'm guessing that Oracle doesn't throw a fit about not being able to insert. JDBC typically just returns the number of rows inserted for an INSERT statement for verification, but I'm not sure exactly how the Hibernate abstraction handles this stuff. I am currently using Hibernate 3.6.10 -- upgraded from 3.6.5 to see if it might fix the issue. It didn't. :P
I'VE BEEN MISLEAD
Ignore that "YET ANOTHER UPDATE" section, above. The bind variables seem like they don't show up in the V$SQL_BIND_CAPTURE view until the transaction has been committed. Back to the drawing board.
ANOTHER REVISION - I SWEAR I'M GONNA GET BANNED
I decided to go back to basics. What have I changed since it was in a working state? Mostly mappings. A few service layer items were also changed, but it was mostly moving our Hibernate mappings from XML to annotations. So I took the same service method I've been playing with, commented out all the other stuff, and tried doing the very same thing as what I'm trying to do with Foo using another persistent object type. Guess what? That works. The only link that could be causing my heartache at this point is the mapping I have for Foo. I doubt my employer would like me to just throw full source up on SO, so I'll probably have to just figure this one out on my own. I will post the answer in some capacity when I finally figure it out.
SUCCESS! BUT I'M NOT SURE WHY...
Here's the code that was giving me trouble. Keep in mind that BAZ is a linking table that has a composite ID made up with an #Embeddable (just called "key" for this example), consisting of FOO_ID referencing a row in the FOO table and a STATE_ID referencing another table.
public class Foo {
// OTHER FIELDS INCLUDING IDs AND SUCH
private Baz bazOfDoom;
private Baz bazOfLight;
private Set<Baz> allTheBaz;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "DOOM_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfDoom() { return bazOfDoom; }
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "LIGHT_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfLight() { return bazOfLight; }
#OneToMany(mappedBy = "key.foo", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
public Set<Baz> getAllTheBaz() { return allTheBaz; }
}
I removed the cascades and it worked. I don't know why. Whoever can explain that will get the "correct answer" mark from me. :)
It seems that your object doesn't own an identifer for your object after saving it to database, leading thus to your exception when calling refresh().
Indeed, assume your database tables own primary key defined as auto-increment.So, when you save your first Foo object, primary key column is valued as: 1.
However, Hibernate has to be aware of this newly generated identifier after calling save() method !
The best way to do this is to expect Hibernate to reaffect the good identifier as soon as the object is saved into database.
Thus, you might miss this line within your entity classes in order to provide identifier automatically when object is saved in database:
#GeneratedValue(strategy = GenerationType.AUTO)
Of course, you don't have to autogenerate them and rather can manually precise it.