JPQL query with WHERE on nested fields - jpa

I have a java entity class UserBean with a list of events:
#OneToMany
private List<EventBean> events;
EventBean has Date variable:
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
private Date eventDate;
Now in UserBean I want to create a NamedQuery that returns all dates that fall within a specific range:
#NamedQuery(name="User.findEventsWithinDates",
query="SELECT u.events FROM UserBean u WHERE u.name = :name AND u.events.eventDate > :startDate AND u.events.eventDate < :endDate")
The above query does not compile though. I get this error:
The state field path 'u.events.eventDate' cannot be resolved to a valid type.
By the way, I use EclipseLink version 2.5.0.v20130507-3faac2b.
What can I do to make this query work? Thanks.

Path u.events.eventDate is an illegal construct in JPQL, because it is not allowed to navigate via a collection valued path expression. In this case u.events is a collection valued path expression. In JPA 2.0 specification this is told with following words:
It is syntactically illegal to compose a path expression from a path
expression that evaluates to a collection. For example, if o
designates Order, the path expression o.lineItems.product is illegal
since navigation to lineItems results in a collection. This case
should produce an error when the query string is verified. To handle
such a navigation, an identification variable must be declared in the
FROM clause to range over the elements of the lineItems collection.
This problem can be solved by using JOIN:
SELECT distinct(u)
FROM UserBean u JOIN u.events e
WHERE u.name = :someName
AND e.eventDate > :startDate
AND e.eventDate < :endDate

Related

EF Core completely ignores my selected properties in select

As I understand it, the following code should generate a query containing only the RouteId, RouteNo, and ShipId
var tow = (from t in _context.AllTowData
where t.RouteId == id
orderby t.RouteNo descending
select new TowDefaults {
Id = t.RouteId,
TowNo = t.RouteNo,
ShipId = t.ShipId,
LastTow = t.RouteNo
})
.FirstOrDefault();
However, I get:
SELECT v.route_id, v.route_no, v.tow_id, v.analysis_complete, v.checks_complete, v.cpr_id, v.date_created, v.date_last_modified, v.factor, v.fromportname, v.instrument_data_file, v.instrument_id, v.internal_number, v.mastername, v.message, v.miles_per_division, v.month, v.number_of_samples, v.number_of_samples_analysed_fully, v.prop_setting, v.route_status, v.sampled_mileage, v.serial_no_per_calendar_month, v.ship_speed, v.silk_reading_end, v.silk_reading_start, v.toportname, v.tow_mileage, v.validity, v.year
FROM view_all_tow_data AS v
WHERE v.route_id = '#__id_0'
ORDER BY v.route_no DESC
LIMIT 1
That's every column except the explicitly requested ShipId! What am I doing wrong?
This happens using both a SQL Server and a PostGres database
The property ShipIdis not mapped, either by a [NotMapped] annotation or a mapping instruction. As far as EF is concerned, the property doesn't exist. This has two effects:
EF "notices" that there's an unknown part the final Select and it switches to client-side evaluation (because it's a final Select). Which means: it translates the query before the Select into SQL which doesn't contain the ShipId column, executes it, and materializes full AllTowData entities.
It evaluates the Select client-side and returns the requested TowDefaults objects in which ShipId has its default value, or any value you initialize in C# code, but nothing from the database.
You can verify this by checking _context.AllTowData.Local after the query: it will contain all AllTowData entities that pass the filter.
From your question it's impossible to tell what you should do. Maybe you can map the property to a column in the view. If not, you should remove it from the LINQ query. Using it in LINQ anywhere but in a final Select will cause a runtime exception.

Spring data repository how to query ElementCollection of String containing substring

I have an entity with an #ElementCollection of strings:
#ElementCollection(fetch = FetchType.EAGER)
private Set<String> names;
While it is no problem to query by names:
List<MyEntity> findByNames(String name);
It seems not to work if i try to create a method with "contains". I want to find entities that have a matching name that contains given substring:
List<MyEntity> findByNamesContains(String substringOfname);
This yields no results but also no error. It seems that the "contains" keyword here is interpreted as the list of name should contain. If i change it to
List<MyEntity> findByNamesContains(List<String> substringOfname);
This methods works.
My question is: is there a combination of keywords to get a method that returns all entities that have a name matching a given substring?
If not how would a JPQL query look like?
As far I knew, No you can't do it wit Spring-JPA-Date repository method names, but it's very easy to be done with JPQL and Spring repository
here's JPQL
select e from MyEntity e join e.names name where name LIKE CONCAT('%', :name, '%')
for case-insensitive query use this
select e from MyEntity e join e.names name where LOWER(name) LIKE CONCAT('%', LOWER(:name), '%')
JPQL + Spring repository
#Query("select e from MyEntity e join e.names name where name LIKE CONCAT('%', :name, '%') ")
List<MyEntity> findByName(#Param("name") String name);
for more information use look here and this answer, Hope this help (:
In case of single string use containing keyword. Works in the same way as "like" in sql queries.
Ref: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_supported_query_keywords

CriteriaBuilder query with sub-query, single column result, max function and generic types using metamodel

My objective is to replace an old JPQL query with a generic type-safe helper method using javax.persistence.metamodel and javax.persitence.criteria.
The query is essentially
select * from table
where field1 = arg1
and field2 = arg2
and field3 = (select max (field3)
from table
where field1 = arg1
and field2 = arg2
and field3 <= arg3
)
Admittedly this is maybe too specialized a query to generalize but I see the need for 2 or 3 other more generic helpers which I can model on this solution.
I have been googling the Criteria documentation (one problem is it's easy to surf a google search result list and mistakenly move from a javax.persitence page to a JBoss Hibernate page... and they are NOT the same).
I have obviously not found a one-stop shop that tells me all I need to know:
how to select a single field in a CriteriaQuery
how to structure a subquery in a CriteriaQuery Expression
how to write a max aggregate function call using CriteriaBuilder
how to properly use Static Metamodel attributes to specify generic classes in a CriteriaBuilder query, when the table being queried has a composite key which is mapped by composite key class (using #EmbeddedId)
OK. I already had the answer before I posted the question but I thought it might be useful to publish what I found.
The use case is a CHARGE table that provides CHG_NU values for ranges of product-option-level values. The appropriate
CHG_NU from the table is the one that matches a PROD_CD and OPTION_TYPE and does not exceed the OPTION_LEVEL.
Here's the method I ended up writing (the comments are specific to the above use-case but the code is generic):
public static <X, KT, PT, BT, NT extends Number> X findWithUpperLimit (Class<X> rootClass, Class<NT> numericClass,
SingularAttribute<X, KT> keyAttr,
SingularAttribute<KT, PT> arg1Attr, PT arg1Val,
SingularAttribute<KT, BT> arg2Attr, BT arg2Val,
SingularAttribute<KT, NT> numericAttr, NT number,
EntityManager em)
{
List<X> results;
CriteriaBuilder cb = em.getCriteriaBuilder ();
// set up the query (returns a full record of the CHARGE table)...
CriteriaQuery<X> cq = cb.createQuery (rootClass);
// ... and the subquery (returns only the BigDecimal OPT_LEVEL)
Subquery<NT> sq = cq.subquery (numericClass);
// set up the root objects for the CHARGE table. Both the query and the subquery are on the same table
Root<X> root = cq.from (rootClass);
Root<X> sqRoot = sq.from (rootClass);
// the query objects and the criteria builder are used to structure the query,
// the root objects are used to get metadata from the table to assign table elements to the criteria
// the subquery gets the closest optLevel to the passed-in number...
sq.select (cb.max (sqRoot.get (keyAttr).get (numericAttr)))
.where (cb.and
(cb.equal (sqRoot.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (sqRoot.get (keyAttr).get (arg2Attr), arg2Val),
cb.le (sqRoot.get (keyAttr).get (numericAttr), number)
));
// ...and the main query matches the passed-in prodCd, optType and the optLevel found by the subquery.
cq.select (root).where (cb.and (cb.equal (root.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (root.get (keyAttr).get (arg2Attr), arg2Val),
cb.equal (root.get (keyAttr).get (numericAttr), sq)
));
results = em.createQuery (cq).getResultList ();
return results.size() == 0 ? null : results.get (0);
}
This is a code snippet that calls it:
Charge charge = DAOHelper.findWithUpperLimit (Charge.class, BigDecimal.class,
Charge_.key,
ChargeKey_.prodCd, invoice.getCharge().getChargeKey().getProdCd(),
ChargeKey_.optType, invoice.getCharge().getChargeKey().getOptType(),
ChargeKey_.optLevel, invoice.getCharge().getChargeKey().getOptType(),
em);
and here's the SQL that it generates:
select charge0_.OPTION_TYPE_CD as OPTION_1_50_,
charge0_.OPTION_LEVEL as OPTION_LEV2_50_,
charge0_.PROD_CD as PROD_CD3_50_,
charge0_.CHG_NU as CHG_NU4_50_
from CHARGE charge0_
where charge0_.PROD_CD=?
and charge0_.OPTION_TYPE_CD=?
and charge0_.OPTION_LEVEL=(select max(charge1_.OPTION_LEVEL)
from CHARGE charge1_
where charge1_.PROD_CD=?
and charge1_.OPTION_TYPE_CD=?
and charge1_.OPTION_LEVEL<=1358.00
)

How to access Map field in JPA via JPQL

for example, if there is an #ElementCollection file which is with a Map type, then if I try to get the map key or value field then how to process?
Class Deal{
.....
private String name;
private String department;
private DealType type;
#AttributeOverrides({
#AttributeOverride(name="value.in.available", column=#Column(name="in_avl")),
#AttributeOverride(name="value.in.unavailable", column=#Column(name="in_unv")),
#AttributeOverride(name="value.out.available", column=#Column(name="out_avl")),
#AttributeOverride(name="value.out.unavailable", column=#Column(name="out_unv"))
})
#ElementCollection(fetch = FetchType.EAGER)
......
}
So if I try to get something like this
select new SummaryAmount(SUM(t.value.in.available), SUM(t.value.in.unavailable),
SUM(t.value.out.available), SUM(t.value.out.unavailable)) from Deal AS d INNER
JOIN d.transactionAmounts t GROUP by t.key;
Is it something possible can work out now? Everything is follow the book except I invent the t.value and t.key as I really don't know how to present map key and value in JPQL.Thanks
Thanks
Try this:
SELECT new SummaryAmount(SUM(VALUE(t).in.available), SUM(VALUE(t)in.unavailable),
SUM(VALUE(t).out.available), SUM(VALUE(t).out.unavailable)) from Deal AS d INNER
JOIN d.transactionAmounts t GROUP by KEY(t);
And now an excerpt from the JPA specification:
An identification variable qualified by the KEY, VALUE, or ENTRY
operator is a path expression. The KEY, VALUE, and ENTRY operators may
only be applied to identification variables that correspond to
map-valued associations or map-valued element collections. The type of
the path expression is the type computed as the result of the
operation; that is, the abstract schema type of the field that is the
value of the KEY, VALUE, or ENTRY operator (the map key, map value, or
map entry respectively).[53]
The syntax for qualified identification variables is as follows.
qualified_identification_variable :: =
KEY(identification_variable) |
VALUE(identification_variable) |
ENTRY(identification_variable)
A path expression using the KEY or VALUE operator can be further
composed. A path expression using the ENTRY operator is terminal. It
cannot be further composed and can only appear in the SELECT list of a
query.

JPQL Aggregation return

Lets say I have the following JPQL query
SELECT e.column1, e.column2, SUM(e.column3), SUM(e.column4) FROM Entity e GROUP BY e.column1, e.column2
Obviously I wont be returning an Entity object but something a bit more complex. How do I return this in the method?
public List<???> query1() {
Query q = entityManager.createQuery("...");
List<Something???> list = q.getResultList();
return list;
}
Such a query returns a List<Object[]>, where each element is thus an array of Objects. The first element of the array will have the type of Entity.column1, the second one will have the type of Entity.column2, and the last 2 ones will be (with Hibernate at least) of type Long (check with EclipseLink).
It's up to you to transform the List<Object[]> in a List<Foo>, by simply looping over the list of objects and transforming each one into a Foo. You may also use the constructor notation directly in the query (provided Foo has such a constructor), but I personally dislike it, because it isn't refactorable:
select new com.baz.bar.Foo(e.column1, e.column2, SUM(e.column3), SUM(e.column4)) from ...