I define two Entity
#Entity
class Template {
#Id
private String id;
private String name;
#OneToMany(fetch=FetchType.LAZY, mappedBy="template")
private List<Edition> editions;
}
class Edition {
#Id
private String id;
private Integer version;
private String state;
#ManyToOne(fetch=FetchType.LAZY)
private Template template;
}
And I want to query each template's newest version and edition's state, so my native sql is:
select
a.id, a.name, b.version, b.state
from
tb_pm_template a,
tb_pm_edition b
where
a.id = b.template_id and
(b.template_id, b.version) in (select template_id, max(version) from tb_pm_edition group by template_id)
The navtive sql work fine. But I want to write in jpa Criteria api way.
So, I try the code below:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> maxVersionQuery = cb.createTupleQuery();
Root<Edition> editionRoot = maxVersionQuery.from(Edition.class);
maxVersionQuery.multiselect(
editionRoot.get("template").get("id").alias("templateId"),
cb.max(editionRoot.get("version")).alias("maxVersion")
).groupBy(editionRoot.get("template").get("id"));
List<Tuple> maxVersion = entityManager.createQuery(maxVersionQuery).getResultList();
List<Map<String, Object>> maxVersionResult = new ArrayList<>(maxVersion.size());
for (Tuple tuple: maxVersion) {
Map<String, Object> row = new HashMap<>(2);
for (TupleElement element: tuple.getElements()) {
row.put(element.getAlias(), tuple.get(element.getAlias()));
}
maxVersionResult.add(row);
}
// the maxVersion or maxVersionResult contain the template's newest version info I want, then I want to combine the version state and template name
CriteriaQuery<Tuple> templateQuery = cb.createTupleQuery();
Root<Edition> editionRoot1 = templateQuery.from(Edition.class);
templateQuery.multiselect(
editionRoot1.get("template").get("id").alias("id"),
editionRoot1.get("template").get("name").alias("name"),
editionRoot1.get("version").alias("version"),
editionRoot1.get("state").alias("versioinState")
).where(
// here I don't know how to connect the conditions
// I try the cb.in, but it needs Expression type
// I also try to use the Subquery api, but since I need the subquery return template_id and version, so I define Subquery<Tuple>, but the Subquery instance's select method only take one parameter
// I check the official document in comments, the example only show the aggregate without groupby
);
Is my implementation way wrong? On this basis, I also need to add paging and sorting, so if it is divided into two or more SQL statements, will it affect the paging count?
Related
I am using CriteriaBuilder to create a Query that returns a List of MyOwnEntitiy. In my entity I have a #Id that is type of Long but I have the need to query the entity when id is like '%3%'. In plain sql I have several options:
select * from MyOwnTable where concat(id,id) like '%3'
select * from MyOwnTable where id::text like '%3'
select * from MyOwnTable where cast(id as text) like '%3'
but when using hibernate and criteriaBuilder I get stuck when trying to cast to text.
I tried to use #Formula annotation in myOwnEntity:
#Formula("id::Text")
private String idToText;
but still get the exception:
java.lang.IllegalArgumentException: Parameter value [%3%] did not
match expected type [java.lang.Long (n/a)]
final CriteriaQuery<MyOwnEntitiy> criteriaQuery = CriteriaBuilder.createQuery(MyOwnEntitiy.class);
final Root<MyOwnEntitiy> myOwn = criteriaQuery.from(MyOwnEntitiy.class);
criteriaQuery.where(CriteriaBuilder.like(myOwn.get("idToText"), "%3%")))
I expect to see in hibernate sql the same or similar that I see in plain sql.
Leave the id field as Long
public class MyOwnTable {
#Id
private Long id;
and use .as(String.class) in criteria builder:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<MyEntity> criteriaQuery = criteriaBuilder.createQuery(MyEntity.class);
final Root<MyEntity> myOwn = criteriaQuery.from(MyEntity.class);
criteriaQuery.where(criteriaBuilder.like(myOwn.get("id").as(String.class), "%3%"));
TypedQuery<MyEntity> typedQuery = entityManager.createQuery(criteriaQuery);
List<MyEntity> myEntitiesContainingThreeInId = typedQuery.getResultList()
I created a a query to only get 4 items from a row in a table which does not include the column cloth style, so i understand why i get the error, but how can i tell Spring Jpa or JPA it is on purpose. and i just want the id, name and color table ?
this is my code:
#RequestMapping(value = "/query/material",method = RequestMethod.GET)
public String QueryMaterialTable(HttpServletRequest request){
DataTableRequest<Material> dataTableInRQ = new DataTableRequest<Material>(request);
PaginationCriteria pagination = dataTableInRQ.getPaginationRequest();
String baseQuery = "SELECT id as id, time as time, name as name, color as color, price as price, (SELECT COUNT(1) FROM MATERIAL) AS totalrecords FROM MATERIAL";
String paginatedQuery = AppUtil.buildPaginatedQuery(baseQuery, pagination);
System.out.println(paginatedQuery);
Query query = entityManager.createNativeQuery(paginatedQuery, Material.class);
#SuppressWarnings("unchecked")
List<Material> materialList = query.getResultList();
DataTableResults<Material> dataTableResult = new DataTableResults<Material>();
dataTableResult.setDraw(dataTableInRQ.getDraw());
dataTableResult.setListOfDataObjects(materialList);
if (!AppUtil.isObjectEmpty(materialList)) {
dataTableResult.setRecordsTotal(String.valueOf(materialList.size())
);
if (dataTableInRQ.getPaginationRequest().isFilterByEmpty()) {
dataTableResult.setRecordsFiltered(String.valueOf(materialList.size()));
} else {
dataTableResult.setRecordsFiltered(String.valueOf(materialList.size()));
}
}
return new Gson().toJson(dataTableResult);
}
If I got the question right, your problem is with the following two lines:
Query query = entityManager.createNativeQuery(paginatedQuery, Material.class);
List<Material> materialList = query.getResultList();
You have various options to fix this:
provide a complete column list, i.e. provide the missing column in the SQL statement and just make them NULL;
Don't use Material but a new class that has the matching attributes.
Don't use a native query but JPQL and a constructor expression.
Use a ResultTransformer.
Use Spring Data and a Projection.
Use a Spring JdbcTemplate.
I would like to sort a Map of by the value. For example I have Person class which has a map of details that are stored in a map with key-value Map<String, String>.
I am using springboot with hibernate5. This is the mapping.
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#DocumentId
#Column(name = "personid")
private Integer id;
#Column(name = "name")
private String name;
// this is a collection of person details
#ElementCollection()
#MapKeyColumn(name = "detailkey")
#Column(name = "detailvalue")
#CollectionTable(name = "details", joinColumns = #JoinColumn(name = "personid"))
Map<String, String> details = new HashMap<>();
//getter and setters omitted
So far I am able to retrieve a person with some specific detailskey and specific detail value. So for example a person table in the DB has eyecolor as detail attribute and as value can have "green", "blue", "brown". Note this is not a real example, just for clarity purposes.
So for example I can get the list of persons and sort them by their name, in the controller I can do
Sort sort = new Sort(Sort.Direction.ASC, "name");
and the opposite direction
Sort sort = new Sort(Sort.Direction.DESC, "name");
Pageable pageable = new PageRequest(1, 10, sort);
pageResult = personRepository.findAll(
"eyecolor", "green", pageable
);
and this one will return the list of persons that have "eyecolor" as green. So far so good and this is working as expected. Now I would like to define a sorting on the detailvalue.
For example I would like to get a list of person sorted by their eyecolor. So first I should have the persons that have "blue", "brown", "green".
how can the Sort be specified in this case ?
In standard SQL it would be something like this:
SELECT p.* from persons p LEFT JOIN details d ON
p.personid = d.personid AND p.detailkey='eyercolor' ORDER BY
p.detailvalue ASC;
The following query worked for me:
SELECT p FROM Person p JOIN p.details d WHERE KEY(d) = 'eyecolor' ORDER BY d
(note that ORDER BY VALUE(d) would fail since VALUE(d) still seems to behave as described here: JPA's Map<KEY, VALUE> query by JPQL failed)
Now, I'm not particularly well versed with Spring Data, but I suppose you should be able to use the above query (without the ORDER BY part) with the #Query annotation on your PersonRepository.findAll method (I'm assuming that's a custom method) and provide the sorting using JpaSort.unsafe("d").
I use JPA 2.0 criteria builder. I need get data from one table and sort them by column from other. This tables have relations OneToMany:
class Club{
#OneToMany(mappedBy = "club")
private List<Address> addresses;
...
}
class Address{
#JoinColumn(name = "club_id", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Club club;
#Column(name = "type")
private Long type;
#Column(name = "full_address")
private String full_address;
...
}
May be several address of some type but I need only one row of this specific address.
I write native queries with subquery, but it's has problem because subquery doesn't use in order clause and in select clause in JPA 2.0.
select c.full_name from club c
ORDER BY (select a.full_address from address a WHERE c.id= a.club_id and a.type=1 LIMIT 1)
select c.full_name, (select a.full_address from address a WHERE a.type=1 AND c.id=a.club_id LIMIT 1) as full_address FROM club c
ORDER BY fullAddress;
How I can replace native order by clause on JPA equivalent?
Thanks!
This native query also resolve problem and it can replace by JPA query
select c.full_name, min(a.full_address) FROM club c LEFT JOIN address a on c.id = a.club_id
where a.id is null or a.type=1 or not exists(SELECT 1 from address aSub WHERE aSub .club_id=c.id AND aSub.type=1)
GROUP BY c.id, c.full_name ORDER BY min(a.full_address);
JPA equivalent
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ClubItem> query = builder.createQuery(ClubItem.class);
Root<Club> root = query.from(Club.class);
Join<Club, Address> addressJoin = root.join(Club_.address, JoinType.LEFT);
query.select(builder.construct(ClubItem.class, root.get(Club_.id), root.get(Club_.fullName), builder.function("min", String.class, addressJoin.get(Address_.fullAddress))));
Subquery<Address> subquery = query.subquery(Address.class);
Root<Address> addressRoot = subquery.from(Address.class);
subquery.select(addressRoot);
subquery.where(
builder.and(
builder.equal(addressRoot.get(Address_.type), 1),
builder.equal(addressRoot.get(Address_.clubId), root.get(Club_.id))));
query.where(builder.or(builder.isNull(addressJoin), builder.equal(addressJoin.get(Address_.type), builder.literal(new Long(1))),
builder.not(builder.exists(subquery))));
query.groupBy(root.get(Club_.id), root.get(Club_.fullName))
Order order = builder.asc(builder.function("min", String.class, addressJoin.get(Address_.fullAddress)));
query.orderBy(order);
TypedQuery<ClubItem> contentQuery = em.createQuery(query);
It's not terribly elegant, but it gets the job done...
Make your "Club" class implement Comparable. Put the order-by logic into the Comparable. Then use Collections.sort(unsortedList) to get the list into sorted form. There's also a Collections.sort(unsortedList, Comparable) method which could be useful, especially if you are doing a bunch of similar methods that just vary on order-by.
i have the next class
#Entity
#Table(name = "table_order")
#IdClass(OrderPK.class)
public class Order {
/** Unique identifier of the currency code in which the transaction was negociated. */
#Column(name = "TRADECURRE", nullable = false, length = 5)
private String tradeCurrencyCode;
/** Currency Entity for Trade. */
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "TRADECURRE", referencedColumnName = "codigo", updatable = false, insertable = false) })
private Currency currencyEntity;
.. here get and sets
}
then execute the next query:
StringBuilder jpaQuery = new StringBuilder();
StringBuilder whereClause = new StringBuilder();
jpaQuery.append("SELECT o, o.currencyEntity");
List orders = query.getResultList();
in this point the log of jpa show 2 querys executed, one to order table and other to Currency table.
bellow i write the next code (in the same class and method of the previous code)
for (Object orderElement : orders) {
int indexArray = 0;
Object[] orderArray = (Object[]) orderElement;
Order orderEntity = (Order) orderArray [indexArray++];
orderEntity.setCurrencyEntity((Currency) orderArray [indexArray++]);
}
When the line
orderEntity.setCurrencyEntity((Currency) orderArray [indexArray++]);
is executed, the query over the table currency is executed once again at database. I need avoid this query to fix some performance problems, i have all the data in the orderArray.
i'm using eclipselink 1.1
thanks in advance
This is happening because you haven't told JPA to pre-fetch the currentEntity in the initial select (although I think that's what you were trying to do with SELECT o, o.currencyEntity). As a result, JPA has to fetch the currentEntity each time round the loop, and it's a real performance killer.
The way to do this with JPA is with a fetch join (documented here). You'd write your query like this:
SELECT o from Order o LEFT JOIN FETCH o.currencyEntity
This also makes it easier to navigate the result set than with SELECT o, o.currencyEntity, since you'll only have a single entity returned, with its currencyEntity property intact:
List<Order> orders = query.getResultList();
for (Order order : orders) {
// fully-populated, without requiring another database query
Currency ccy = order.getCurrentEntity();
}