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

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...)

Related

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

JPA CRITERIA QUERY with order by joined columns

How to invoke order by on a joined entity? I am trying to achieve the following with:
select * from person p inner join telephone t on p.id=t.person_id join sim s on s.id=t.sim_id order by s.name DESC
#Entity
public class Person implements Serializable{
#Id
private Long id;
#OneToMany(orphanRemoval = true, mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Telephone> telephonesNumber;
#Entity
public class Telephone implements Serializable {
#Id
private String number;
#Id
#ManyToOne()
#JoinColumn(name = "person_id")
private Person person;
#Id
#ManyToOne(cascade = {})
#JoinColumn(name = "sim_id")
private Sim sim;
#Entity
public class Sim implements Serializable {
#Id
private Long id;
#Column(unique = true)
private String name;
I use specification interface, in this example sorting is on the field person.id and it works
public class PersonSpecification implements Specification<Person> {
#Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// there is many different conditions for example
// if(someCondition!=null) {
// predicates.add(builder.like(root.get("someProperty"), someValue));
// }
query.groupBy(root.get("id"));
//there I want to order by Sim.name i dont know how
query.orderBy(builder.asc(root.get("phone")));//this works
return builder.and((predicates.toArray(new Predicate[predicates.size()])));
}
I want to order by Sim.name but i dont know how.
In JPA specification you can use:
query.orderBy(builder.asc(root.join("telephonesNumber").get("sim").get("name")));
to sort by sim name.
For more details:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship
If you using JPA Query:
#Query("select s from Person p
join p.telephonesNumber t
join t.sim s order
by t.sim.id desc")
It will produce this:
select * from person p
inner join telephone t on p.id=t.person_id
inner join sim s on t.sim_id=s.id
order by t.sim_id desc
For more details:
https://github.com/abhilekhsingh041992/spring-boot-samples/blob/master/jpa/src/main/java/example/springboot/jpa/repository/PersonRepository.java
another way for that would be using Query method:
List<Telephone> findAllByOrderBySimIdAsc();
Look at this findAllByOrderBySimIdAsc
With the code before, you can get all rows from Telephone ordered by Sim Id.

EclipseLink Batch Fetch Hint Not Working For Two Fields

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.

Issues with bidirectional OneToMany mapping and NamedQuery

I have two entities connected bidirectional and I want to query the Location and its votes only for a specific date.
Location:
#Entity
#Table(name = "TAB_LOCATION")
#NamedQueries({
#NamedQuery(name = "Location.getVotedLocations", query = "SELECT l FROM Location l JOIN l.votes v WHERE v.location = l AND DATE(v.createdAt) = DATE(:date) ORDER BY l.name")
})
public class Location extends AbstractEntity {
#Basic
#Size(min = 5, max = 50)
private String name;
#Basic
#Size(min = 0, max = 50)
private String address;
#OneToMany(mappedBy = "location")
private Set<Vote> votes;
#Basic
private String description;
Vote:
#Entity
#Table(name = "TAB_VOTE")
public class Vote extends AbstractEntity {
#Basic
#ManyToOne
#NotNull
private User user;
#Basic
#ManyToOne
#NotNull
private Location location;
I was trying to use the named query but it seems that the Location always contains all votes regardless the condition.
Isn't it possible to map the queried values back to the object?
entityManager.createNamedQuery("Location.getVotedLocations").
setParameter("date", date).getResultList();
What is wrong?
If it isn't possible with NamedQueries, I also can use the Criteria API.
Here's a simple MySql statement that will return locationIDs not Location entity which are voted on a particular date.
Select DISTINCT LocationID FROM VOTE WHERE DATE == dateCreated;
And you can then get the Location entity by LocationID.
When you get an entity as a result from some query, you get the whole entity. It is not possible in JPA to get just a subset of all data, trimmed by where condition.
Well, if you use Hibernate, take a look at Hibernate Filters, with them you could get the result you want.
Note about your query, you have JOIN l.votes so you don't need to join it again with WHERE v.location = l.

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;