JPA native query with annotation executes different query - java

I am trying to implement an example like this: A Person class has a list of places that it likes. But when I want to query it, I want result as each person with only the most favorite place(just the first one not all of them). So I have done this:
#Entity
class Person{
...
#ManyToMany(cascade = {CascadeType.REFRESH,CascadeType.PERSIST}, fetch = FetchType.EAGER)
#JoinTable(name = "person_favorite_place",
joinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "place_id", referencedColumnName = "id")
)
#OrderColumn(name="favorite_place_order")
List<Place> favoritePlaces;
}
And in repository, I did:
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query(value = "select person0_.id as id1_0_0_, person0_.age as age2_0_0_, person0_.name as name3_0_0_, favoriteme1_.person_id as person_id1_1_1_, place2_.id as place_id2_1_1_, favoriteme1_.favorite_place_order as favorite3_1_, place2_.id as id1_3_2_, place2_.invented as invented2_3_2_, place2_.name as name3_3_2_ from person person0_ left outer join person_favorite_place favoriteme1_ on person0_.id=favoriteme1_.person_id left outer join place place2_ on favoriteme1_.place_id=place2_.id where person0_.id=:personId and favoriteme1_.favorite_place_order = 0", nativeQuery = true)
Person getPersonWithFavoritePlace(#Param("personId") Long personId);
}
But it seems , there are 2 sql queries is getting run. the first one is:
select person0_.id as id1_0_0_, person0_.age as age2_0_0_, person0_.name as name3_0_0_, favoriteme1_.person_id as person_id1_1_1_, place2_.id as place_id2_1_1_, favoriteme1_.favorite_place_order as favorite3_1_, place2_.id as id1_3_2_, place2_.invented as invented2_3_2_, place2_.name as name3_3_2_ from person person0_ left outer join person_favorite_place favoriteme1_ on person0_.id=favoriteme1_.person_id left outer join place place2_ on favoriteme1_.place_id=place2_.id where person0_.id=:personId and favoriteme1_.favorite_place_order = 0
the second one:
select favoriteme0_.person_id as person_id1_1_0_, favoriteme0_.place_id as place_id2_1_0_, favoriteme0_.favorite_place_order as favorite3_0_, place1_.id as id1_3_1_, place1_.invented as invented2_3_1_, place1_.name as name3_3_1_ from person_favorite_place favoriteme0_ inner join place place1_ on favoriteme0_.place_id=place1_.id where favoriteme0_.person_id=?
I can understand the first one, which is completely the query that I want to execute but the second one , I do not know where it comes. So I think, because of that I am having all the favorite places of a person but not the most desired one.
Any ideas?
BR
PS: I have written the query over native output of "findOne" method. Just added "and favoriteme1_.favorite_place_order = 0" in the end. Also I have tried to use the exact query without modification and it worked like a charm!!

The second query is used to load the favoritePlaces that you defined with FetchType.EAGER

Your repository is querying Persons but not places and because of FetchType.EAGER loads all Places also with the second query; To query for the Places use PlacesRepository.

Related

JPA - Ignore join operation in namedQuery in EclipseLink

I have below scenario
Entity TableA :
#Entity
#Table(name = "TABLE_A")
#NamedQueries({
#NamedQuery(name = "TableA.namedQ1", query = "SELECT t1 FROM TableA t1 JOIN FETCH t2.TableB t2"
+ " WHERE <conditions here>"),
#NamedQuery(<Need query here which will ignore mapping below and return rows only for TableA>)
} )
public class TableA implements Serializable{
#Id
#Column(name = "id")
private int id
...
...
...
#OneToMany(mappedBy = "tableA", cascade = CascadeType.ALL ,fetch=FetchType.LAZY)
private List<TableB> tableB;
}
Entity TableB :
#Entity
#Table(name = "TABLE_B")
public class TableB implements Serializable{
#Id
#Column(name = "id1")
private int id1
...
...
...
#ManyToOne
#JoinColumn(name = "id",insertable = false, updatable = false)
private TableA tableA;
}
I am facing below two issues :
Query mentioned above i.e
SELECT t1 FROM TableA t1 JOIN FETCH t2.TableB t2
takes long time to execute. around 30 seconds. But the same query for same dataset takles hardly 3-4 seconds in SQL developer. ANythnig I should do in code to make it run faster?
I have requirement where i dont need data from other table(retrived via mapping). I would be needing data only from TableA. I tried below named query but it run separate query against TableB for each row in TableA which takes 4+ minutes to execute.
"SELECT t1 FROM TableA t1 where <condition goes here>"
What modifications I have to do in query to ignore mapping. I would need to retain annotations(#OneToMany) as I will need it in namedQ1.
Thanks in anticipation
When you use FETCH, you are asking to retrieve the data for the collection in
advance. If you don't need the elements from TableB then your query should be:
SELECT t1 FROM TableA t1 left join t1.tableB t2
Note that the join is only necessary if you need to add some condition to t2.
For example:
SELECT t1 FROM TableA t1 left join t1.tableB t2 WHERE t2.field = 123
if this is not your case, than this should be enough:
SELECT t1 FROM TableA t1;
In all these cases, it will create a proxy for the collection TableB
and won't need to access the table on the database, unless you need to use the collection later on.
For anyone having similar questions
Question 1 in OP : https://stackoverflow.com/questions/61573091/long-time-of-fetching-data-from-oracledb-using-eclipselink
Question 2 in OP: Solved using #Davide solution

problems with OneToMany including a filter clause in spring jpa

I currently get unexpected results in my MYSQL8/H2 test-case when using on a #OneToMany relationship in spring jpa. I want to filter in a list of TKBColumn-tables inside my TKBData table using JPQL. I expect to get one TKBData-table with the filtered TKBColumn but I always get the TKBData-table with ALL TKBColumn (unfiltered). When I using a SQL command it works!
I got no Idea whats the problem here, why it always give me the TKBData-table with always ALL TKBColumn-tables inside.
Native Query (This works):
SELECT d.id,c.name FROM TKBDATA d LEFT JOIN TKBDATA_TKBCOLUMN dc ON d.ID = dc.TKBDATA_ID LEFT JOIN TKBCOLUMN c ON c.ID = dc.COLUMNS_ID WHERE c.name = 'column1';
Output
ID NAME
7b6ec910-3e53-40a3-9221-ee60e75c8d67 column1
JPQL Query (Not works):
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
Output:
id: e892bc28-c35f-4fc8-9b09-387f97a758d8, name:column1
id: 069cc76b-3487-4ad8-a4ae-6568694e2287, name:column2
Table 'TKBData'
public class TKBData {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
private Set<TKBColumn> columns = Sets.newHashSet();
...
}
Table 'TKBColumn'
public class TKBColumn {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
}
Spring Data Repository
#Service
public interface KBDataRepository extends CrudRepository<TKBData, String>, KBDataCustomRepository {
#Query("select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
}
Spring JPA Generated H2 Tables (relevant)
CREATE CACHED TABLE "PUBLIC"."TKBCOLUMN"(
"ID" VARCHAR(255) NOT NULL,
"NAME" VARCHAR(255),
...
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA_TKBCOLUMN"(
"TKBDATA_ID" VARCHAR(255) NOT NULL,
"COLUMNS_ID" VARCHAR(255) NOT NULL
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA"(
"ID" VARCHAR(255) NOT NULL,
...
)
Relevant Content of tables which are generated at the start of the test class
Table: TKBDATA
ID
726004cf-5cab-4b1d-bb3f-466ba22622e9
Table: TKBDATA_TKBCOLUMN
TKBDATA_ID COLUMNS_ID
726004cf-5cab-4b1d-bb3f-466ba22622e9 7b4e4ea8-4ff9-4668-8882-67ff93b595ca
726004cf-5cab-4b1d-bb3f-466ba22622e9 d670e813-0466-48a8-be54-ee992cf28462
Table: TKBCOLUMN
ID DATAORDER NAME OWNERID
d670e813-0466-48a8-be54-ee992cf28462 0 column1 16e01046-9a84-4651-98d8-4e3e358212eb
7b4e4ea8-4ff9-4668-8882-67ff93b595ca 1 column2 16e01046-9a84-4651-98d8-4e3e358212eb
For more informations you can find the github repository here: https://github.com/fo0/ScrumTool
Test class: https://github.com/fo0/ScrumTool/blob/master/ScrumTool/src/test/java/com/fo0/vaadin/scrumtool/test/data/TKBDataColumnFilterTest.java
Edit:
The solution for this was to use a native query, because of the design of JPA and how it works with objects, thats why my use-case has exactly this problem.
Meaning of select d from TKBData d JOIN d.columns c WHERE c.name = column1 is
Find a TKBData object where it has an associated column object for which name is column1
Once its decided which TKBData has at least one column object for which name is column1, then it will return all its associated column objects which you don't have control over in JPA. ( see My answer to another question ). Alternative is to write native sql and return custom non entity objects
For example, you have TKBDATA_1 with column1 and column2 associated, you also have TKBDATA_2 with column3 associated.
When you run your query, it will ignore TKBDATA_2 and decides to return TKBDATA_1 as it has atleast one column object with name= column2. But after that you don't have control over which associated column objects to return for TKBDATA_1 and JPA will return all associated column objects
If you are not sure of the reason, read about hibernate session.How it provides unique presentation of any associated entry in memory. It is the foundation for its dirty checking and repeatable read
Update your #OneToMany as follows
#OneToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
#JoinTable(name = "TKBDATA_TKBCOLUMN",
joinColumns = #JoinColumn(name = "TKBDATA_ID"),
inverseJoinColumns = #JoinColumn(name = "COLUMNS_ID"))
private Set<TKBColumn> columns = Sets.newHashSet();
When it comes to JPA query language, I would like to think in terms of query a collection of in-memory objects.
So now try to describe the meaning of the following two queries in terms of objects.
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
vs
select d from TKBData d JOIN d.columns c WHERE c.name = :name
Don't forget unlike in sql where you are select any columns here you have said you want to select TKBData objects and restricting which TKBData objects to return.
So to achieve the same result as of your native sql, use the second JPA query
Note:
Even though you used a left join in your sql query, it is effectively an inner join sql query because you also applied a where condition to the most right table on that join.
Use the DISTINCT JPQL keyword
#Query("select distinct d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
Or use JPA method naming query
public TKBData findByColumnsName(String name);

JPQL: query entities with Many-to-Many relationship modeled using a composite key

I read this article of how to build Many-To-Many using composite key.
The entities are
class Student {
// ...
#OneToMany(mappedBy = "student")
Set<CourseRating> ratings;
// ...
}
class Course {
// ...
#OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
// ...
}
#Entity
class CourseRating {
#EmbeddedId
CourseRatingKey id;
#ManyToOne
#MapsId("student_id")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("course_id")
#JoinColumn(name = "course_id")
Course course;
int rating;
// standard constructors, getters, and setters
}
#Embeddable
class CourseRatingKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
// standard constructors, getters, and setters
// hashcode and equals implementation
}
Now, let's assume, I want to get students that has specific courseId.
How will my JPQ look like then:
select s from Student s join fetch s.ratings r where r.id.courseId=:courserId
OR
select s from Student s join fetch s.ratings r where r.course.id=:courserId
or it would be totally different?
You should check the SQL carefully on this sort of JPA queries because there is probably more going on there than you realize.
You did a great job of setting up the ManyToMany relation with the attribute rating and your second query will work but could easily cause you problems and possible performance considerations.
For getting only the students the correct query could be
em.createQuery("select s from Student s join fetch s.ratings r where r.course.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
That gives the logs
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id where ratings1_.course_id=1
Hibernate: select course0_.id as id1_0_0_ from Course course0_ where course0_.id=?
Student(id=1):[CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)]
JPA will generate an extra query to get the course information since it was not prefetched. You can solve that by prefetching it yourself.
em.createQuery("select s from Student s join fetch s.ratings r join fetch r.course c where c.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
and that gives the logs
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, course2_.id as id1_0_2_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id inner join Course course2_ on ratings1_.course_id=course2_.id where course2_.id=1
Student(id=1):[CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)]
There is an issue in that you have students with only a partial set of course ratings. If you attempt to update a rating for a student I think you will cause the other course ratings to be lost. Perhaps not a problem for your use case but you can also get the course with the list of students:
em.createQuery("select distinct c from Course c join fetch c.ratings r join fetch r.student where c.id = 1", Course.class).getSingleResult().getRatings().forEach(r->System.out.println(r.getStudent() + ":" + r));
And that will give the same results with a slightly different query and you can update a student's rating without affecting other courses.
Hibernate: select distinct course0_.id as id1_0_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, student2_.id as id1_2_2_, ratings1_.rating as rating3_1_1_, ratings1_.course_id as course_i1_1_0__, ratings1_.student_id as student_2_1_0__ from Course course0_ inner join CourseRating ratings1_ on course0_.id=ratings1_.course_id inner join Student student2_ on ratings1_.student_id=student2_.id where course0_.id=1
Student(id=2):CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)
Student(id=1):CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)
This can also effect the JSON formatting if you are creating a REST service. In the first instance you have an multiple arrays of CourseRatings with one entry and in the second instance you have just have one array of CourseRatings with rating and student entries. Basically a partial list of Students each with a partial list of Courses is not an accurate representation of your database where as a Course with a complete list of its Students is. I'm not sure at this point which one is more efficient on the SQL server but there should be a lot less Courses than Students so if you want all the students for a Course it is probably better this way.
With JPA you should check the SQL and test your use cases carefully. Since there are lots of students and several courses per student the performance or memory overhead could be worth considering. Note also that the results can get even stranger if you were not using fetch in your queries to begin with.
This is the correct version:
SELECT s
FROM Student s
JOIN FETCH s.ratings r WHERE r.course.id = :courserId
You can put Student object inside of your embeddable id as well, in case you don't need its id. That way it will be easier to read your code, and you won't have to make #Column(name = "student_id") Long studentId to be non-insertable and non-updatable.

Use existing table joins for Hibernate Entity Criteria

Suppose I have an entity like the following, where each of the sets is a separate entity that has a foreign key relationship to the PersonEntity primary key.
PersonEntity - PK: person_id
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person", orphanRemoval = true)
Set<AddressEntity> addresses
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person", orphanRemoval = true)
Set<NameEntity> nameParts
Both the AddressEntity and NameEntity have a PersonEntity, which is the FK relationship expressed in entity form.
All tables also have a field called the tenant_id which they are partitioned on.
If I create HibernateCriteria like follows:
final Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PersonEntity.class, "p");
criteria.add(Restrictions.eq("p.personId", personId));
criteria.add(Restrictions.eq("p.tenantId", tenantId));
I get SQL like:
select ALL_ATTRIBUTES_SNIPPED
FROM person this_
LEFT OUTER JOIN address addresses2_
ON this_.person_id=addresses2_.person_id
LEFT OUTER JOIN name nameparts4_
ON this_.person_id=nameparts4_.person_id
WHERE this_.person_id=?
AND this_.tenant_id=?
Looking at the explain plan, I see that this checks all the partitions when doing the join. That's unnecessary as it just needs to look in one partition.
What I would like to would be to add additional restrictions on all tables, such that all of them are restricted by tenant_id. So the SQL might look like this:
select ALL_ATTRIBUTES_SNIPPED
FROM person this_
LEFT OUTER JOIN address addresses2_
ON this_.person_id=addresses2_.person_id
LEFT OUTER JOIN name nameparts4_
ON this_.person_id=nameparts4_.person_id
WHERE this_.person_id=?
AND this_.tenant_id=?
AND addresses2_.tenant_id =?
AND nameparts4_.tenant_id =?
However, I can't seem to figure out how to create criteria to perform this. When I try something like the following:
final Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PersonEntity.class, "p")
.createAlias("addresses", "address", JoinType.LEFT_OUTER_JOIN)
.createAlias("nameParts", "namePart", JoinType.LEFT_OUTER_JOIN)
criteria.add(Restrictions.eq("p.personId", personId));
criteria.add(Restrictions.eq("p.tenantId", tenantId));
criteria.add(Restrictions.eq("address.tenantId", tenantId));
criteria.add(Restrictions.eq("namePart.tenantId", tenantId));
I get SQL that looks like this:
select ALL_ATTRIBUTES_SNIPPED
FROM person this_
LEFT OUTER JOIN address addresses2_
ON this_.person_id=addresses2_.person_id
LEFT OUTER JOIN name nameparts4_
ON this_.person_id=nameparts4_.person_id
LEFT OUTER JOIN address addresses3_
ON this_.person_id=addresses3_.person_id
LEFT OUTER JOIN name nameparts1_
ON this_.person_id=nameparts1_.person_id
WHERE this_.person_id=?
and this_.tenant_id = ?
and addresses3_.tenant_id = ?
and nameparts1_.tenant_id = ?
As you can see, the tables are joined twice.
How can I create a restriction that uses the original tables? I don't see how I would be able to supply a restriction that would access the existing joins. I tried something like p.addresses.tenantId but it said addresses was not recognized.
Edit: I have largely resolved the query issue, by placing this line on the Set in PersonEntity and on the PersonEntity in the set entity (ie, AddressEntity).
#JoinColumns(value={
#JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID", insertable=false, updatable=false),
#JoinColumn(name="TENANT_ID", referencedColumnName="TENANT_ID", insertable=false, updatable=false)
})
I also removed the mappedBy attribute for those columns.
This forces a join on both the person_id and tenant_id and makes the explain plan cost significantly better (as well as real world performance). However, I'm not sure if this is a real solution because it introduces a new problem.
My problem now is that when I try to create a PersonEntity, I get the following error:
12:09:26.672 WARN [main] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 1400, SQLState: 23000
12:09:26.672 ERROR [main] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ORA-01400: cannot insert NULL into ("USER"."ADDRESS"."PERSON_ID")
This occurs even though the SQL shows the person insert occurred just before the attempt at the address insert. It seems like the person_id is not being passed along to be placed into the address insert. How could I force Hibernate to do that? Previously, it just happened automatically (from my point of view).
I am using the sequence generator to create my primary keys, if that matters.
What resolved this for me was adding these lines to the collections in the PersonEntity and then on the PersonEntity field in the child entity classes (AddressEntity, NameEntity):
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns(value={
#JoinColumn(name = "PERSON_ID", referencedColumnName = "PERSON_ID", nullable = false),
#JoinColumn(name = "TENANT_ID", referencedColumnName = "TENANT_ID", nullable = false)
})
public PersonEntity getPerson() {
return personEntity;
}
That worked for the query but I couldn't do inserts or updates, so the other thing I had to do was make sure that the existing tenantId field had insertable=false and updateable=false, like so:
#Column(name = "TENANT_ID", insertable = false, updatable = false)
public String getTenantId() {
return tenantId;
}
Then, executing the criteria in the original question would result in all child tables having a join on the PERSON_ID and the TENANT_ID, exactly as I wanted.
This changed my estimated cost from 2525 to 15 on the explain plan since it could go straight to the correct partition instead of looping through them.

JPQL joining tables with where clause

I hve a table with a forign key like so:
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="BASE_ID", updatable = false)
private Base base;
Which connects to a Base table using its "id" .. I'm trying to query this with a where clause but i'm running into errors, my query looks like:
return getEntityManager()
.createQuery("SELECT a FROM BaseEventLog a join fetch a.base p WHERE p.eventStatus = 'ERROR'",
BaseEventLog.class)
.getResultList();
In sql it would look like this:
SELECT *
FROM BASE_EVENT_LOG
JOIN BASE
ON BASE.ID=BASE_EVENT_LOG.BASE_ID
WHERE EVENT_STATUS = 'ERROR'
Any suggestions on how to create such a query with jpsql ?
SELECT a FROM BaseEventLog a
join a.base p
WHERE p.eventStatus = 'ERROR'
No need for the fetch; or atleast I have never used it with JPQL. If you want to use the fetch keyword take a look out how to use it here:
http://docs.oracle.com/cd/E17904_01/apirefs.1111/e13946/ejb3_langref.html#ejb3_langref_fetch_joins

Categories

Resources