EclipseLink Batch Fetch Hint Not Working For Two Fields - jpa

I'm using QueryHints in Spring Data JPA to use EclipseLink Batch Fetch with a type of IN. Ultimately, I need to use this around 30 fields but it doesn't seem to work right for 2 fields. Field A has a ManyToOne relationship and Field B has a ManyToMany. Based on the results of the initial query, I would expect the batch hint to generate an IN clause with 2 ids for Field A and 12 for Field B. This works fine when the hint is turned on for one field at a time. When it is enabled for both fields, the hint only applies to whichever field is the last hint in the list of QueryHints. I've tried EAGER and LAZY fetch on the fields as a shot in the dark, but it had not impact.
Is there a limitation with mixing batch fetch hints based on the relationship type? Is there something different going on? The EclipseLink documentation isn't very detailed for this feature.
EDIT: It seems it doesn't matter what fields I enable it only, it only works for one at at time. Here is sample code for two entities. The BaseEntity defines the PK id generation.
#Entity
#Table(name = "MainEntity")
public class MainEntity extends BaseEntity implements Cloneable {
...
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinTable(
name="EntityBMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="bId", referencedColumnName="id")})
#JsonIgnore
private Set<EntityB> bSet = new HashSet<>();
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(
name="EntityAMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="aId", referencedColumnName="id")})
#JsonIgnore
#OrderColumn(name="order_index", columnDefinition="SMALLINT")
private List<EntityA> aList = new ArrayList<>();
...
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityA")
public class EntityA extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityASet", fetch=FetchType.LAZY)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityB")
public class EntityB extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityBSet", cascade=CascadeType.ALL)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
The repository query:
#QueryHints(value = {
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_TYPE, value = "IN"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_SIZE, value = "250"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.aList")},
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.bSet")},
forCounting = false)
List<MainEntity> findAll(Specification spec);
Generated queries:
SELECT id, STATUS, user_id FROM MainEntity WHERE ((STATUS = ?) OR ((STATUS = ?) AND (user_id = ?)))--bind => [ONESTAT, TWOSTAT, myuser]
..
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac]
...
SELECT t1.id, t1.name, t0.mainId FROM EntityBMapping t0, EntityB t1 WHERE ((t1.id = t0.bId) AND (t0.mainId IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac, 1c07a3a9-7028-48ba-abe8-2296d58ebd57, 235bb4f2-d724-4237-b73b-725db2b9ca9f, 264f64b3-c355-4476-8530-11d2037b1f3c, 2d9a7044-73b3-491d-b5f1-d5b95cbb1fab, 31621c93-2b0b-4162-9e42-32705b7ba712, 39b33b19-c333-4523-a5a7-4ba0108fe9de, 40ba7706-4023-4b7e-9bd5-1641c5ed6498, 52eed760-9eaf-4f6a-a36f-076b3eae9297, 71797f0c-5528-4588-a82c-5e1d4d9c2a66, 89eda2ef-80ff-4f54-9e6a-cf69211dfa61, 930ba300-52fa-481c-a0ae-bd491e7dc631, 96dfadf9-2490-4584-b0d4-26757262266d, ae079d02-b0b5-4b85-8e6f-d3ff663afd6e, b2974160-33e8-4faf-ad06-902a8a0beb04, b86742d8-0368-4dde-8d17-231368796504, caeb79ce-2819-4295-948b-210514376f60, cafe838f-0993-4441-8b99-e012bbd4c5ee, da378482-27f9-40b7-990b-89778adc4a7e, e4d7d6b9-2b8f-40ab-95c1-33c6c98ec2ee, e557acf4-df01-4e66-9d5e-84742c99870d, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a76, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a77]
...
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [1c07a3a9-7028-48ba-abe8-2296d58ebd57]

As Chris mentioned, Named Queries are the best work around for this issue. The other option is to use a custom repository and call setHint on the EntityManager yourself for each hint specified (plenty of examples out there for creating custom repos in Spring Data JPA). You could attempt to override findOne(...) and protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) on SimpleJpaRepository to try and create a generic way to properly set the hints but you'll likely want to check that you don't duplicate hint setting on getQuery(...) as you'll still want to call super() for that and then apply your additional hints before returning the query. I'm not sure what the behavior would be if you applied a duplicate hint. Save yourself the trouble and use Named Queries is my advice.

Related

JPA Criteria API join on 3 tables and some null elements

I have one parent entity that has two child entities as attributes.
I want to select all elements from the parent entity that have EITHER a childOne with a given parameter as personal attribute OR childTwo with that same given parameter as personal attribute.
Here are my three classes simplified:
The Parent Object:
#Entity
public class ParentObject {
#Id
private int id;
private int fkChildOne;
private int fkChildTwo;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildOne childOne;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildTwo childTwo;
// getters and setters
}
The Child One Object:
#Entity
public class ChildOne {
#Id
private int childOneId;
private String nameChildOne;
#OneToMany
#JoinColumn(name = "fk_child_one_id")
private List<ParentObject> parents;
// getters and setters
}
The Child Two Object:
#Entity
public class ChildTwo {
#Id
private int childOneId;
private String nameChildTwo;
#OneToMany
#JoinColumn(name = "fk_child_two_id")
private List<ParentObject> parents;
// getters and setters
}
The Specs Class:
public static Specification<ParentObject> checkName(String name) {
return Specifications.where(
(root, query, builder) -> {
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne");
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo");
return builder.or(
builder.equal(joinchildOne .get("nameChildOne"), name),
builder.equal(joinchildTwo .get("nameChildTwo"), name)
);
}
);
}
When this spec is called in my service, I get no results. However, if I comment out one of the two joins and the corresponding Predicate in my builder.or method, then I get some results but they obviously don't match what I'm looking for, which is to select every ParentObject that have either ChildOne with that parameter or ChildTwo with that paramater.
Any clue what's wrong with the code ?
Finally got the solution : to fetch all the corresponding results, I had to add the type of the join which would be left join, since I wanted to fetch all ParentObjects regardless of owning childOne or ChildTwo objects.
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne", JoinType.LEFT);
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo", JoinType.LEFT);
Great, now you have to choose if you need to join or fetch.To optimize the query and the memory, you should establish the relations as Lazy (#ManyToMany (fetch = FetchType.LAZY)), so you will only bring the objects that you demand.
The main difference is that Join defines the crossing of tables in a variable and allows you to use it, to extract certain fields in the select clause, for example, on the other hand, fetch makes it feed all the objects of that property. On your example,
a select from parent with join of children (once the relation is set to lazy) would only bring initialized objects of type parent, however if you perform a fetch, it would bring the parent and child objects initialized.
Another modification I would make is to change the type of the identifier to non-primitive, so that it accepts null values, necessary for insertion using sequences

Hibernate Postgresql select for update with outer join issue

I have faced with issue trying to select for update row using Spring data with Hibernate as JPA implementation and Postgresql.
Suppose we have entities:A,B,C.
public class A{
#Id
private Long id;
#OneToMany(fetch = FetchType.EAGER)
private Set<B> bSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<C> cSet;
}
Suppose we want to select A with all related B and C entities for update i.e. with locking row related to A table.
#Query(SELECT a FROM A a
LEFT JOIN FETCH a.bSet
LEFT JOIN FETCH a.cSet
WHERE a.id=?)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public A selectAndLockA(Long Aid);
The query will look like
SELECT a.column1, ... from tableA a LEFT JOIN tableB b ... FOR UPDATE of a,c
FOR UPDATE of a,c
The query will try to lock two tables what leads to exception like :
org.postgresql.util.PSQLException: ERROR: FOR UPDATE cannot be applied to the nullable side of an outer join
What I try to archive is locking only first table "FOR UPDATE OF a"
Is it possible to configure somehow or tell Hibernate to lock only first table.
This is not supported by PostreSQL. If you do an outer SELECT nothing can prevent somebody from inserting a row into the LEFT JOINED table thereby modifiying the result set you are looking at (e.g. the columns would not be NULL anymore on a repeated read).
For a detailed explanantion see here
It's been a long time since question was created, but I have a similar problem and hope my answer will help somebody.
Suppose that we have this JPA entities:
#Entity
#Table(name = "card_transactions")
public class CardTransactionsEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
#SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
#JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
private ReceiptsEntity receipt;
#Column
#Enumerated(EnumType.STRING)
private CardTransactionStatus requestStatus;
...
}
#Entity
#Table(name = "receipts")
public class ReceiptsEntity {
#EmbeddedId
private OfdReceiptId id;
...
}
#Embeddable
public class OfdReceiptId implements Serializable {
#Column(name = "ofd_id")
#Enumerated(EnumType.STRING)
private OfdId ofdId;
#Column(name = "receipt_id")
private String receiptId;
...
}
And we want select CardTransactionsEntity with fetched ReceiptsEntity for pessimistic update only CardTransactionsEntity. This can be done using Hibernate and Spring Data JPA repository as
public interface CardTransactionRepository extends JpaRepository<CardTransactionsEntity, Long> {
#Query("select ct from CardTransactionsEntity ct left join fetch ct.receipt r where ct.requestStatus = :requestStatus")
#Lock(value = LockModeType.PESSIMISTIC_WRITE)
#QueryHints(value = {
#QueryHint(name = "javax.persistence.lock.timeout", value = "-2"), // LockOptions.SKIP_LOCKED
#QueryHint(name = "org.hibernate.lockMode.r", value = "NONE") // "r" is alias for ct.receipt and will excluded from PESSIMISTIC_WRITE
})
List<CardTransactionsEntity> loadCardTransactions(#Param("requestStatus") CardTransactionStatus requestStatus, Pageable pageable);
}
This repository method will execute query like
SELECT ct.*, r.* from card_transactions ct LEFT OUTER JOIN receipts r ON ct.ofd_id = r.ofd_id and ct.receipt_id = r.receipt_id WHERE ct.request_status=? LIMIT ? FOR UPDATE OF ct SKIP LOCKED
You can bypass this error with joining the tables with FetchType.LAZY. This fetch type is the default one and it is not required to specify for #OneToMany joins.
public class A{
#Id
private Long id;
#OneToMany
private Set<B> bSet;
#OneToMany
private Set<C> cSet;
}

How do I use JPA to select the record with the closest date?

Let's say I have a collection of Rates that all inherit from an AbstractRate
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "name")
#Table(name = "rates")
public abstract class AbstractRate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(precision = 13, scale = 4)
private BigDecimal value;
#OneToOne
private EffectiveDate effectiveDate;
...
}
And, that all have an EffectiveDate:
#Entity
public class EffectiveDate {
#Id
private LocalDateTime date;
...
}
(I acknowledge that a separate date object is a little over-kill for this model, but it's allowing me to bind multiple rates and other figures in the database.)
Now, I'd like to get a specific Rate, say SalaryRate, that is effective as of a certain date. I can do something like
salaryRateRepository.findByEffectivedate(
effectiveDateRepository.findTopByDateLessThanEqualOrderByDateDesc(sampleDate)
);
This should effectively give me a the MAX(date) and its matching Rate. Is this the right way to query these things? Some posts suggest
As an additional option, I have Querydsl setup and the repositories extend QuerydslPredicateExecutor. However, I'm not really familiar with how Querydsl's syntax works.
I think all is OK with findTopByDateLessThanEqualOrderByDateDesc(sampleDate).
Another variants should be:
#Query(value = "select * from effective_date ed where ed.date <= ?1 order by ed.date desc limit 1", nativeQuery = true)
EffectiveDate findEffectiveDate(LocalDateTime dateTime);
or
Predicate p = QEffectiveDate.effectiveDate.date.loe(sampleDate);
Pageable page = new PageRequest(0, 1, new Sort(Sort.Direction.DESC, "date"));
//...
EffectiveDate effectiveDate = effectiveDateRepository.findAll(p, page).getContent().stream().findFirst().orElse(null);
(not tested...)

JPA how to aggregate columns in a join

I'm trying to implement a query like this in JPA:
SELECT
ta.field_aggr_1,
ta.field_aggr_2,
MIN(tb.date_inv) AS min_date_inv,
MAX(tb.date_inv) AS max_date_inv
FROM table_a ta
INNER JOIN table_b tb ON ta.idB = tb.id
GROUP BY
ta.field_aggr_1,
ta.field_aggr_2
The key point is the MIN and MAX functions that apply in one column of a joined table.
I've created the entities:
#Entity
#Table(name="table_a")
public class EntityA extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="field_aggr_1")
private String field_aggr_1;
#Column(name="field_aggr_2")
private String field_aggr_2;
#ManyToOne
#JoinColumn(name="idB")
private EntityB entityB;
// Getters & Setters & HashCode & equals & toString
}
#Entity
#Table(name="table_b")
public class EntityB extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="date_inv")
private String date_inv;
// Getters & Setters & HashCode & equals & toString
}
And in the service I want to create query:
EntityManager em = ...;
Root<EntityA> root = criteriaQuery.from(EntityA.class);
EntityType<EntityA> type = this.em.getMetamodel().entity(EntityA.class);
Join<EntityA, EntityB> join = root.join(type.getDeclaredSingularAttribute("entityB", EntityB.class));
List<Selection<?>> fields = new ArrayList<Selection<?>>();
// grouping fields
fields.add(root.<EntityA>get("field_aggr_1"));
fields.add(root.<EntityA>get("field_aggr_2"));
I've managed to include fields from the joined table,
fields.add(join.<EntityB>get("date_inv"));
BUT I haven't succeed in implement the min aggregation.
Thanks in advance for your answers!
I have managed to solve the question. First, I needed to have one more "root" and one more "entityType" for the joined entity:
Root<EntityB> rootB = criteriaQuery.from(EntityB.class);
EntityType<EntityB> typeB = this.em.getMetamodel().entity(EntityB.class);
With these, now I can do what I needed:
fields.add(builder.least(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
fields.add(builder.greatest(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
Hope that it helps someone!

How to make a CriteriaBuilder join with a custom "on" condition?

I want make a query where I join 2 tables, using the CriteriaBuilder. In MySQL the query I'm trying to make would look like this:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
AND item.type_id = 1
I want to get all orders and if they have an item of type #1, I want to join with this item. However, if no item of type #1 is found, I still want to get the order. I can't figure out how to make this with the CriteriaBuilder. All I know how to make is:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
Join<Item, Type> type = order.join(Item_.type, JoinType.LEFT);
cq.select(order);
cq.where(cb.equal(type.get(Type_.id), 1));
This query is broke, since it results in something like this in MySQL:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
WHERE item.type_id = 1
The result will only contain orders with items of type #1. Orders without are excluded. How can I use the CriteriaBuilder to create a query like in the first example?
It is possible starting from the version 2.1 of JPA using the on method Join<Z, X> on(Predicate... restrictions);
Here is how:
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
item.on(cb.equal(item.get(Item_.type), 1));
I think this is the same problem as posed in this question. It looks like it is not possible in CriteriaBuilder. It is possible in Hibernate Criteria API, but that probably won't help you.
JPA Criteria API: Multiple condition on LEFT JOIN
I know this question was made a long time a go, but recently a had the same problem and i found this solution from an Oracle forum, i copied and pasted just in case the link is not longer available.
MiguelChillitupaArmijos 29-abr-2011 1:41 (en respuesta a 840578) Think
you should use something like:
em.createQuery("SELECT DISTINCT e.Id" +
" from Email e " +
" left join e.idEmailIn e2 *with* e2.responseType = 'response'" +
" where e.type = 'in' and e.responseMandatory = true").getSingleResult();
An this is the link.
JPA Criteria : LEFT JOIN with an AND condition
There is a workaround if you are using Hibernate 3.6 with JPA 2.0
It is not the better solution, however it works perfect for me.
I´ve duplicate the entity with the #Where hibernate annotation.It means that everytime you use the join with this entity, hibernate will add the extra condition on the join statement at generated SQL.
For instance, initially we have the follow example:
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
}
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
In order to add extra conditions on criteria Join, we need duplicate the Address #Entity mapping , adding the #Where annotation #Where(clause = " ADDRESS_TYPE_ID = 2").
#Entity
#Table(name = "ADDRESS")
#Where(clause = " ADDRESS_TYPE_ID = 2")
public class ShippingAddress {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#OneToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
Also, we need to add the duplicate mapping association for the new entity.
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
#OneToOne(mappedBy = "person")
private ShippingAddress shippingAddress;
}
Finally, you can use a join with this specific Entity in your criteria :
PersonRoot.join(Person_.shippingAddress, JoinType.LEFT);
The Hibernate Snippet SQL should seems like this :
left outer join
address shippingadd13_
on person11_.person_id=shippingadd13_.person_id
and (
shippingadd13_.ADDRESS_TYPE_ID = 2
)
ON clause is supported in Hibernate 4.3 version, anyone is aware if there is a parameter indexing issue between the parameter index of the additional custom conditions with the index of the existing mapping filters when doing an outer join with ON clause?
Using the Person entity class below as an example, say I am adding this filter to limit the address types and the filter is enabled to populate the IN clause. The parameter index for the IN clause will cause the issue [2] when I add additional conditions (such as using 'street' column) part of the ON clause. Is is a known issue?
[1] #Filter(name = "addressTypes", condition = "ADDRESS_TYPE in (:supportedTypes)")
[2]
Caused by: ERROR 22018: Invalid character string format for type BIGINT.
private Set addresses;