Needless Left Outer Join Created on FK's using JPA/Hibernate - java

I have an Entity such as:
#Entity
class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, columnDefinition = "BIGINT")
private Long id;
#NotNull(message = NOT_NULL_CODE)
#Size(min = 5, max = 150, message = SIZE_CODE)
#Column(nullable = false, length = 150)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organization_id", nullable = false)
#JsonBackReference("organization-brands")
private Organization organization;
// getters and setters
}
My repository has the following defined:
Page<Brand> findAllByOrganization(Organization organization, Pageable pageable);
This results in the following query:
select
brand0_.id as id1_1_,
brand0_.name as name2_1_,
brand0_.organization_id as organiza3_1_
from
brand brand0_
left outer join
organization organizati1_
on brand0_.organization_id=organizati1_.id
where
organizati1_.id=? limit ?
I'm wondering why there is a left outer join created? I thought maybe it was because the organization_id was part of the select so I created a DTO Projection that only included the name and id columns, but that still included the left outer join. I found this bug report filed but there's been zero movement on it.
The reason this is concerning for me is because the left outer join adds, on average, 200ms to the query. I haven't done any benchmarking to determine if this grows exponentially with more rows of data or not. But with my current dataset, it's 200ms. Yikes.

This is most likely related to:
http://stackoverflow.com/a/17987718/1356423
From the presence of your nullable = false on the column definition it looks like the relationship is non-optional so try annotating as #ManyToOne(fetch = FetchType.LAZY, optional = false)
Without theoptional = false Hibernate has to check the association table to see whether to set either a proxy or null for the associated Organisation: hence the join.

assuming Organization class PK is named as id
then
Page<Brand> findAllByOrganizationId(String organizationId, Pageable pageable);
should do what you want

Related

fetching elementcollecion from hibernate based on memeber of condition

I have two entities Customer & Tester. Where a customer can request for tests and a tester can assign it to itself which will be set to the currentTestId. Also all the test id would be added to assignedTests list for old tests references. Testers have hierarchy as test lab manager, lab employee and whenevr a tester accept the request lab mangers are also assigned the test and so the ManyToOne(customer) and OneToMany(Tester) relationships are added.
DataSet:
Customer Entity
#Column(name = "test_id",nullable = false)
private long testId;
#ManyToOne(optional = false)
#JoinColumn(name = "tester_id")
private Tester tester;
Tester Entity
#Column(name = "test_id",nullable = false)
private long currentTestId;
#OneToMany(mappedBy = "tester")
private Set<Customer> customers = new HashSet<>();
#ElementCollection
#CollectionTable(name="assigned_tests",
joinColumns = #JoinColumn(name="tester_id", referencedColumnName = "id"))
#Column(name="test_id", nullable = false)
private Set<Long> assignedTests = new HashSet<>();
Problem Statement:
I want to fetch all customers who are part of the tester assigned tests. So since it is a one to many relation ideally I should have something like this
Select c.id from Customer c, Tester t
where c.testId memeber of t.assignedTests
and t.id = :testerId
But this gives me the error:
The basic field cannot be used as a state field path
The Query compilation work if I pass the testId but unfortunately in the code I cannot pass it(just tried to test if it will work)
Select c.id from Customer c, Tester t
where :testId memeber of t.assignedTests
and t.id = :testerId
I am new to JPA and learning how to use ElementCollection. I am using Postgres database and JPA 2.1. Also I cannot use native query or the criteria Api. It has to be using the JPQL but still suggestions would be really helpful.
How about:
SELECT c.id FROM Customer c
JOIN c.tester t
JOIN t.assignedTests at
WHERE t.id = :testerId
?

Query in repository not translated correctly

I am quite a beginner with Spring Data, and I have to code my first difficult query :)
I am making a reservation engine for an event. My data model is composed by:
a RoomType entity defining a possible configuration for a room (e.g. double, triple, quadruple)
a Room entity representing the actual Room
a RoomArrangement entity defining all the possible RoomTypes for a Room (e.g. the room 7 can be configured as Triple or Double room)
a RoomAssignment entity representing the actual configuration chosen for a room after having been reserved
FKs are configured this way
RoomType <--* RoomArrangement *--> Room <-- RoomAssignment
(see code below for Entity definition)
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
I have configured Spring as below.
RoomType.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_type_id")
private List<RoomArrangement> roomArrangements;
RoomArrangement.java
#ManyToOne(targetEntity = RoomType.class)
#JoinColumn(name = "room_type_id", nullable = false)
private RoomType roomType;
#ManyToOne(targetEntity = Room.class)
#JoinColumn(name = "room_id", nullable = false)
private Room room;
#Column(name = "priority")
private Integer priority;
Room.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_id")
private List<RoomArrangement> roomArrangements;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "room", cascade = CascadeType.ALL)
private RoomAssignment assignment;
RoomArrangementRepository.java
RoomArrangement findFirstByRoomTypeAndRoom_AssignmentIsNullOrderByPriorityAsc(RoomType rt);
The query above is translated to
select
roomarrang0_.id as id1_3_,
roomarrang0_.priority as priority2_3_,
roomarrang0_.room_id as room_id3_3_,
roomarrang0_.room_type_id as room_typ4_3_
from
room_arrangements roomarrang0_
left outer join
rooms room1_ on roomarrang0_.room_id=room1_.id
where
roomarrang0_.room_type_id=9
and
(room1_.id is null)
order by
roomarrang0_.priority asc
limit 1;
The issues are two:
I do not know where the where clause
(room1_.id is null)
comes from
I do not know where the "AndRoom_AssignmentIsNull" clause has gone
Should I "invert" the OneToOne relationship and put the FK on the Room class?
Thanks for your help!
Lorenzo
I've tried to apply some of the suggestions, and "turned" the query on the RoomRepository.
The query came like this:
Room findFirstByRoomArrangements_RoomTypeAndAssignmentIsNullOrderByRoomArrangements_PriorityAsc(RoomType rt);
We come to the same problem:
select
room0_.id as id1_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join
room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and
(room0_.id is null)
order by
roomarrang1_.priority asc
limit ?;
I think the problem lies in the fact that the one-to-one relationship between Room and RoomAssignment is represented on the database with a FK on the room_assignments table to the rooms table.
I will try to put the FK on the rooms table instead of on the room_assignments table and see if something changes.
If you need to find a room without an without an Assignment, shouldn't you be looking that in the Room repository?
You said that you have to find a Room but you are returning a RoomArrangement in a RoomAssignmentRepository. This is quite confusing.
Let's assume you are in right place, that means RoomRepository and as you said:
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
try to use the following method name
Room findByRoomArrangementRoomTypeAndRoomAssignmentIsNullOrderByPriorityAsc(RoomType rt)
Made it!
In the end, the problem lied in the fact that the one-to-one relationship between Room and RoomAssignment was mapped by an FK from RoomAssignment to Room. Apparently, Spring Data didn't manage this configuration properly: the Assignment is null was translated to room_assignment.room_id = null and, since room_id was an FK to room.id, to room.id = null.
Inverting the FK mapping, the query is translated to
select
room0_.id as id1_6_,
room0_.assignment_id as assignme3_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and (room0_.assignment_id is null)
order by
roomarrang1_.priority asc
limit ?
which correctly returns what I needed.

QueryDSL, Hibernate, JPA — using .fetchJoin() and getting data in first SELECT, so why N+1 queries after?

I'm trying to query for a list of entities (MyOrders) that have mappings to a few simple sub-entities: each MyOrder is associated with exactly one Store, zero or more Transactions, and at most one Tender. The generated SELECT appears correct - it retrieves all the columns from all four joined tables - but afterwards, two more SELECTs are executed for each MyOrder, one for Transactions and one for Tender.
I'm using QueryDSL 4.1.3, Spring Data 1.12, JPA 2.1, and Hibernate 5.2.
In QueryDSL, my query is:
... = new JPAQuery<MyOrder>(entityManager)
.from(qMyOrder)
.where(predicates)
.join(qMyOrder.store).fetchJoin()
.leftJoin(qMyOrder.transactions).fetchJoin()
.leftJoin(qMyOrder.tender).fetchJoin()
.orderBy(qMyOrder.orderId.asc())
.transform(GroupBy
.groupBy(qMyOrder.orderId)
.list(qMyOrder));
which is executed as:
SELECT myorder0_.ord_id AS col_0_0_,
myorder0_.ord_id AS col_1_0_,
store1_.sto_id AS sto_id1_56_1_, -- store's PK
transactions3_.trn_no AS trn_no1_61_2_, -- transaction's PK
tender4_.tender_id AS pos_trn_1_48_3_, -- tender's PK
myorder0_.ord_id AS ord_id1_39_0_,
myorder0_.app_name AS app_name3_39_0_, -- {app_name, ord_num} is unique
myorder0_.ord_num AS ord_num8_39_0_,
myorder0_.sto_id AS sto_id17_39_0_,
store1_.division_num AS div_nu2_56_1_,
store1_.store_num AS store_nu29_56_1_,
transactions3_.trn_cd AS trn_cd18_61_2_,
tx2myOrder2_.app_name AS app_name3_7_0__, -- join table
tx2myOrder2_.ord_no AS ord_no6_7_0__,
tx2myOrder2_.trn_no AS trn_no1_7_0__,
tender4_.app_name AS app_name2_48_3_,
tender4_.ord_num AS ord_num5_48_3_,
tender4_.tender_cd AS tender_cd_7_48_3_,
FROM data.MY_ORDER myorder0_
INNER JOIN data.STORE store1_ ON myorder0_.sto_id=store1_.sto_id
LEFT OUTER JOIN data.TX_to_MY_ORDER tx2myOrder2_
ON myorder0_.app_name=tx2myOrder2_.app_name
AND myorder0_.ord_num=tx2myOrder2_.ord_no
LEFT OUTER JOIN data.TRANSACTION transactions3_ ON tx2myOrder2_.trn_no=transactions3_.trn_no
LEFT OUTER JOIN data.TENDER tender4_
ON myorder0_.app_name=tender4_.app_name
AND myorder0_.ord_num=tender4_.ord_num
ORDER BY myorder0_.ord_id ASC
which is pretty much what I'd expect. (I cut out most of the data columns for brevity, but everything I need is SELECTed.)
When querying an in-memory H2 database (set up with Spring's #DataJpaTest annotation), after this query executes, a second query is made against the Tender table, but not Transaction. When querying a MS SQL database, the initial query is identical, but additional queries happen against both Tender and Transaction. Neither makes additional calls to load Store.
All the sources I've found suggest that the .fetchJoin() should be sufficient (such as Opinionated JPA with Query DSL; scroll up a few lines from the anchor) and indeed if I remove them, the initial query only selects columns from MY_ORDER. So it appears that .fetchJoin() does force generation of a query that fetches all the side tables in one go, but for some reason that extra information isn't being used. What's really weird is that I do see the Transaction data being attached in my H2 quasi-unit test without a second query (if and only if I use .fetchJoin() ) but not when using MS SQL.
I've tried annotating the entity mappings with #Fetch(FetchMode.JOIN), but the secondary queries still fire. I suspect there might be a solution involving extending CrudRepository<>, but I've had no success getting even the initial query correct there.
My primary entity mapping, using Lombok's #Data annotations, other fields trimmed out for brevity. (Store, Transaction, and Tender all have an #Id a handful of simple numeric and string field-column mappings, no #Formulas or #OneToOnes or anything else.)
#Data
#NoArgsConstructor
#Entity
#Immutable
#Table(name = "MY_ORDER", schema = "Data")
public class MyOrder implements Serializable {
#Id
#Column(name = "ORD_ID")
private Integer orderId;
#NonNull
#Column(name = "APP_NAME")
private String appName;
#NonNull
#Column(name = "ORD_NUM")
private String orderNumber;
#ManyToOne
#JoinColumn(name = "STO_ID")
private Store store;
#OneToOne
#JoinColumns({
#JoinColumn(name = "APP_NAME", referencedColumnName = "APP_NAME", insertable = false, updatable = false),
#JoinColumn(name = "ORD_NUM", referencedColumnName = "ORD_NUM", insertable = false, updatable = false)})
#org.hibernate.annotations.ForeignKey(name = "none")
private Tender tender;
#OneToMany
#JoinTable(
name = "TX_to_MY_ORDER", schema = "Data",
joinColumns = { // note X_to_MY_ORDER.ORD_NO vs. ORD_NUM
#JoinColumn(name = "APP_NAM", referencedColumnName = "APP_NAM", insertable = false, updatable = false),
#JoinColumn(name = "ORD_NO", referencedColumnName = "ORD_NUM", insertable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "TRN_NO", insertable = false, updatable = false)})
#org.hibernate.annotations.ForeignKey(name = "none")
private Set<Transaction> transactions;
/**
* Because APP_NAM and ORD_NUM are not foreign keys to TX_TO_MY_ORDER (and they shouldn't be),
* Hibernate 5.x saves this toString() as the 'owner' key of the transactions collection such that
* it then appears in the transactions collection's own .toString(). Lombok's default generated
* toString() includes this.getTransactions().toString(), which causes an infinite recursive loop.
* #return a string that is unique per order
*/
#Override
public String toString() {
// use appName + orderNumber since, as they are the columns used in the join, they must (?) have
// already been set when attaching the transactions - primary key sometimes isn't set yet.
return this.appName + "\00" + this.orderNumber;
}
}
My question is: why am I getting redundant SELECTs, and how can I not do that?
I'm a little too late on the answer, but today the same problem happened to me. This response might not help you, but at least it would save someone the headache we went through.
The problem is on the relations between the entities, not in the query. I tried with QueryDSL, JPQL, and even native SQL but the problem was always the same.
The solution was to trick JPA into believing that the relations were there via annotating the child classes with #Id on those joined fields.
Basically you'll need to set Tender's id like this and use it from MyOrder like if it was a normal relationship.
public class Tender {
#EmbeddedId
private TenderId id;
}
#Embeddable
public class TenderId {
#Column(name = "APP_NAME")
private String appName;
#Column(name = "ORD_NUM")
private String orderNumber;
}
The same would go for the Transaction entity.

JPA - Generated Id from Sequence is always 0

I'm trying to use JPA to generate IDs from sequences in my database (Oracle 9i)
From what I found here and there, here is the group of annotations I've set on my ID variable :
#Id
#SequenceGenerator(name="PROCEDURENORMALE_SEQ_GEN", sequenceName = "PROCEDURENORMALE_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "PROCEDURENORMALE_SEQ_GEN")
#Column(name = "IDPROCEDURENORMALE", unique = true, nullable = false, precision = 10, scale = 0)
private long idProcedureNormale;
However, whenever I create a new object, this id is always set to 0, and because of that I can't persist data. I've tried to change the strategy from GenerationType.SEQUENCE to GenerationType.AUTO, nothing changed. For this specific table, Sequence number is supposed to be around 8300.
Where did I go wrong ?
I actually solved my issue, that happened not to be directly related with what I exposed.
This object I was trying to persist is part of a relatively complex object, and in the parent object I didn't add a CascadeType to the JPA mapping annotation :
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dossier")
private Set<Procedurenormale> proceduresNormales = new HashSet<>(0);
Changing this annotation to the following solved the issue :
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dossier", cascade = CascadeType.ALL)
private Set<Procedurenormale> proceduresNormales = new HashSet<>(0);

Hibernate Query.list returns actual Object instance instead of expected type

When performing a hibernate query in HQL using a join and subsequently calling query.list to return the list of matched objects, I am ending up with a list of actual Object instances (i.e. query.list().get(0).getClass() == Object.getClass()) instead of instances of the expected object.
Running the query without the join returns objects of the expected type correctly and they can be cast and used appropriately.
So far my searches have not turned up anything about what could be causing this. Is there something else I need to do when using a join in hql to ensure the object is mapped correctly?
Edit: Added code excerpts below. I had to change all the names and attempt to extract only the relevant portions (the real code is not really about cars).
Working query:
from Car car where car.name like :name
Non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Car entity:
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = {"someId"}),
#UniqueConstraint(columnNames = {"someOtherId"})})
public class Car extends SomeParentEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 64)
private String someId;
#Column(length = 64)
private String name;
// ... Many columns and mappings removed ...
#OneToMany(mappedBy = "car", fetch = FetchType.LAZY)
private List<Occupant> occupants;
// ...
}
Occupant entity:
#Entity(name = "car.Occupant")
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = { "name" }) })
public class User extends SomeParentEntity
{
#ManyToOne
#JoinColumn(name = "carId", nullable = false)
private Car car;
#Column(length = 64, nullable = false)
private String name;
// ... Many mappings / columns removed ...
}
Your JOIN in the HQL is making Hibernate retrieve two types of objects. You can see this if you activate the SQL logging.
Assuming you have a X-to-X relation in your entity, the problem should go away if you change the query to use
... JOIN FETCH entity.relation ...
Why are you using explicit left join btw.
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
I think we can simple use:
from Car car where car.name like :name or car.occupant.name like :oname
Then the query.list should give you List of object which should be casted back to List of car
+1's all around to the other answerers for your help, it is much appreciated.
However, the solution turned out to be very simple. The query just needed to have the select specified at the beginning for the join case in order to narrow the field (I assume).
So the non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Becomes:
select car from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)

Categories