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
Related
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);
I want to model an optional one to one mapping with JPA.
tableA has a column (flag) and when that is true I need to select additional data from an other tableB to get a string.
tableB has a fk to the pk of tableA.
So far I tiered this mapping but hibernates makes additional selects for tableB per default. I only want those when I access the mapping to tableB.
Class TableA
#OneToOne(mappedBy= tableA_id, fetch = FetchType.LAZY, optional = true)
private TableB tableB;
Class TableB
#OneToOne(mappedBy= tableA_id, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "TABLEA_ID"
private TableA tableA
The result is that I get selects on tableB just from a
em.createQuery("from TableA") before even accessing getTableB().
Using Entity entity = hibernateTemplate.get(Entity.class, id); when I hit a entity.getChild() which is a OneToOne relation, every other OneToOne relations are loaded as well. I use hibernate 5.4.1-Final.
I use bytecode enhancement as below :
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>false</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
A.java
#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "ID_A")
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private B b;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private C c;
...
getters/setters
...
B.java
#Entity
#Table(name = "B")
public class B {
#Id
#Column(name = "ID_A")
private String id;
}
C.java
#Entity
#Table(name = "C")
public class C {
#Id
#Column(name = "ID_A")
private String id;
}
So when I do
A a = hibernateTemplate.get(A.class, "100");
// triggers an Hibernate query only on A entity. The B and C aren't fetched => OK
// Hibernate: select a0_.ID_A as ID_A_27_0_ from A a0_ where a0_.ID_A=?
a.getB(); // ERROR : triggers two queries : one on C and one on B
// Hibernate: select c0_.ID_A as ID_A _26_0_ from C c0_ where c0_.ID_A =?
// Hibernate: select b0_.ID_A as ID_A _13_0_ from B b0_ where b0_.ID_A =?
Even if I fetch the B in an HQLQuery, I still have a query to C :
Query<A> queryA = hibernateTemplate.createHQLQuery("from A a join fetch a.b where a.id=:id", A.class);
queryA.setParameter("id", "100");
A a = queryA.uniqueResult(); // triggers an inner join
// Hibernate: select a0_.as ID_A1_27_0_, b1_.ID_A as ID_A1_13_1_ from A a0_ inner join B b1_ on a0_.ID_A=b1_.ID_A where a0_.ID_A=?
a.getB(); // KO -> triggers a query to select C !
// Hibernate: select c0_.ID_A as ID_A1_26_0_ from C c0_ where c0_.ID_A=?
I've tried to do a double mapping (OneToOne with mappedBy specified) without success.
The PK of B and C are the same as A.
I expect the a.getB(); not to trigger the fetch of C. Is this a hibernate bug ? I can't find anything regarding this behaviour in their documentation.
Is my mapping correct ?
It seems that it works as designed :) b and c belong to the same default "LazyGroup". If any of b or c needs to be loaded, the whole group will.
Quote from the bytecode enhancer documentation :
Lazy attributes can be designated to be loaded together and this is
called a "lazy group". By default, all singular attributes are part of
a single group, meaning that when one lazy singular attribute is
accessed all lazy singular attributes are loaded. Lazy plural
attributes, by default, are each a lazy group by themselves. This
behavior is explicitly controllable through the
#org.hibernate.annotations.LazyGroup annotation.
Just add the #LazyGroup("b") on the b field, and #LazyGroup("c") on c and it should work as expected : b will be loaded only on getB(), anc c on getC().
More on this here and here.
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.
I have following entities and pojo:
#Entity
public class TableA {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
string name;
}
#Entity
public class TableB {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
double price;
#ManyToOne
#JoinColumn(name = "tableAId")
TableA tableA;
//setters & getters
}
statistics
public class Statistics {
long tableAId;
double price;
long count;
public Statistics(long tableAId, double price, long count) {
this.tableAId = tableAId;
this.price = price;
this.count = count;
}
//setters & getters
}
I want to do a jpql query to get resultlist of statistics objects which is populated with reference id to tableA object and sum of price columns and count of rows in TableB table.
I've tried with following code without success:
Query query = em.createQuery("SELECT NEW se.exampel.Statistics"
+ "(b.tableAId, sum(price) ,count(b)) from TableB b ");
List<Statistics> statistics = query.getResultList();
Exception
java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: tableAId
of: se.exampel.TableB [SELECT NEW se.exampel.Statistics(b.tableAId,count(b), sum(price))
from se.exampel.TableB b ]
What am i doing wrong?
now it's fixed:
"select new se.exampel.Statistic(s.id, sum(p.price),count(p)) from tabelB p JOIN p.tabelA s GROUP BY s"
You are mixing SQL concepts into JPQL. The query needs to me made on Entity TableB, and so can only use mapped attributes within the TableB java class. So you will need to use something like:
"SELECT NEW se.exampel.Statistics(b.tableA.id, sum(b.price) ,count(b)) from TableB b "
Note that Hibernate is likely to do an inner join from tableB to tableA to get A's ID. If you want to be able to access the foreign key field in TableB directly in a JPA neutral way, you may need to add a read-only field in the TableB class for it. Something like
#Column(name="tableA_ID", insertable=false, updatable=false);
long tableAId;
which then allows you to access b.tableAId in queries:
"SELECT NEW se.exampel.Statistics(b.tableAId, sum(b.price) ,count(b)) from TableB b "
and should avoid the table join.
query should contain b.tableA which is property name instead of column name tableAId
Update: with reference to comment from #Chris query should be
SELECT NEW se.exampel.Statistics(b.tableA.id,sum(b.price),count(b)) from TableB b