Cast Id column type of Long to Text so LIKE can be applied - postgresql

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

Related

JPA Criteria subquery with tuple

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?

How to JPA Query for Postgresql #Type list-array?

I have column in a database in which data are stored in this way {type1,type2,...}. I want to get elements from CARS table which are in Set carTypes.
#Type(type = "list-array")
#Column(name = "TYPES")
private final List<String> types;
Not working:
#Query("SELECT * FROM cars c WHERE (:carTypes) IN (c.types)")
List<Object[]> findCars(#Nullable #Param("carTypes") Set<String> carTypes);
Error:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: bytea = character varying[]
I don't know what the Spring Data abstraction for this (or if there is any), but with plain Hibernate you can do something like this:
BasicType type = (BasicType) session.getFactory()
.getMappingMetamodel()
.getEntityDescriptor(Car.class)
.getPropertyType("types");
List<Car> list = session.createQuery(
"SELECT * FROM cars c WHERE (:carTypes) IN (c.types)",
Car.class
)
.setParameter("carTypes", carTypes, type)
.getResultList();

How can i ignore: PSQLException: The column name clothStyle was not found in this ResultSet

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.

How replace native order by clause on JPA equivalent?

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.

How to set a Collection/List to a named parameter of a JPA criteria query?

A single named parameter can be set to a JPA criteria query something like the following. The parameter is of the type Long in this case.
public StateTable find(Long id)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery = criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
ParameterExpression<Long> parameterExpression=criteriaBuilder.parameter(Long.class);
criteriaQuery.where(criteriaBuilder.equal(root.get(StateTable_.stateId), parameterExpression));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.setParameter(parameterExpression, id).getSingleResult();
}
This query inside the method returns a single object of the StateTable (just say state) entity which I'm dealing with and corresponds to the following JPQL query.
entityManager.createQuery("select s from StateTable s where s.stateId=:id")
.setParameter("id", id)
.getSingleResult();
I need to find more than one row that corresponds to a list of ids supplied via java.util.List<Long>. The following is the incomplete version of the criteria query.
public List<StateTable> find(List<Long> ids)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
ParameterExpression<Long> parameterExpression = criteriaBuilder.parameter(Long.class);
criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.stateId)).value(parameterExpression));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.setParameter(parameterExpression, 1L).getResultList();
}
It uses an in() query but I made it return only a single row, since I don't know whether it is possible to set a list of ids to ParameterExpression or not.
In short, this criteria query should correspond to the following JPQL query.
entityManager.createQuery("from StateTable where stateId in(:id)")
.setParameter("id", ids)
.getResultList();
Is there a way to set a List<Long> to ParameterExpression as specified?
The following approach worked for me.
public List<StateTable> find(List<Long> ids)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
//ParameterExpression<Long> parameterExpression = criteriaBuilder.parameter(Long.class);
//criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.stateId)).value(parameterExpression));
criteriaQuery.where(root.get(StateTable_.stateId).in(ids));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
I just added the following line.
criteriaQuery.where(root.get(StateTable_.stateId).in(ids));
removing the above commented lines from the incomplete version of the query in the question.
I was recently investigating the same thing and found a solution that shouldn't impact server-side query caching.
Using a ParameterExpression as part of the In clause
Please note that this should have been a response to a comment from Jannik Jochem under this page's answer; however, I am few rep short for that, so feel free to kill this post and add a comment if you have enough rep.