Given the query example here: http://www.hibernatespatial.org/tutorial-hs4.html
Query query = em.createQuery("select e from Event e where within(e.location, :filter) = true", Event.class);
query.setParameter("filter", filter);
Is it possible to rewrite the query using jpa 2 criteria api?( I am unsure how i should deal with the within(e.location, :filter) part.
I recently work at the exact same problem. My solution is a own Predicate for the within-keyword.
public class WithinPredicate extends AbstractSimplePredicate implements Serializable {
private final Expression<Point> matchExpression;
private final Expression<Geometry> area;
public WithinPredicate(CriteriaBuilderImpl criteriaBuilder, Expression<Point> matchExpression, Geometry area) {
this(criteriaBuilder, matchExpression, new LiteralExpression<Geometry>(criteriaBuilder, area));
}
public WithinPredicate(CriteriaBuilderImpl criteriaBuilder, Expression<Point> matchExpression, Expression<Geometry> area) {
super(criteriaBuilder);
this.matchExpression = matchExpression;
this.area = area;
}
public Expression<Point> getMatchExpression() {
return matchExpression;
}
public Expression<Geometry> getArea() {
return area;
}
public void registerParameters(ParameterRegistry registry) {
// Nothing to register
}
#Override
public String render(boolean isNegated, RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder();
buffer.append(" within(")
.append(((Renderable) getMatchExpression()).render(renderingContext))
.append(", ")
.append(((Renderable) getArea()).render(renderingContext))
.append(") = true ");
return buffer.toString();
}
}
Your query would look like this:
public List<Event> findEventInArea(Geometry area){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Event> c = cb.createQuery(Event.class);
Root<Event> event = c.from(Event.class);
c.where(new WithinPredicate((CriteriaBuilderImpl) cb, event.get(Event_.location), area));
Query query = entityManager.createQuery(c);
return query.getResultList();
}
JPA does not support spatial. However, you can unwrap the hibernate session from your JPA EntityManager and run spatial criteria.
The lat lon bounds in this code sample is arbitrary.
#PersistenceContext(unitName = "myPuName")
private EntityManager entityManager;
#Override
public List<City> findCities() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
Session session = entityManager.unwrap(Session.class);
Criteria criteria = session.createCriteria(City.class);
GeometryFactory geometryFactory = new GeometryFactory();
Coordinate[] coordinates = {new Coordinate(-9,-9,0),new Coordinate(-9,9,0),new Coordinate(9,9,0),new Coordinate(9,-9,0),new Coordinate(-9,-9,0)};
LinearRing polygon = geometryFactory.createLinearRing(coordinates);
Polygon po = geometryFactory.createPolygon(polygon,null);
criteria.add(SpatialRestrictions.within(City_.location.getName(), po));
List list = criteria.list();
return list;
}
Here is some more code not directly related to the question. This class can be used as an "Order" criteria to be added to a hibernate criteria. It will sort results by distance from the argument location:
public class KnnOrder extends Order {
private final Point fromPoint;
public KnnOrder(String propertyName, boolean ascending, Point fromPoint) {
super(propertyName, ascending);
this.fromPoint = fromPoint;
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
Dialect dialect = criteriaQuery.getFactory().getDialect();
if (!dialect.getClass().isAssignableFrom(PostgisDialect.class)) {
throw new UnsupportedOperationException("This supports only postgis dialect. Was requested: " + dialect.toString());
}
// final String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, super.getPropertyName());
// String fromPointWkt = WKTWriter.toPoint(fromPoint.getCoordinate());
return "location <-> st_setsrid(st_makepoint(" + fromPoint.getX() + "," + fromPoint.getY() + "),4326)";
}
}
In JPA2 you can use the function expression builder. No dedicated stuff required anymore. Works for the order expression too.
public List<Event> listThem(Geometry area) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Event> cq = cb.createQuery(Event.class);
Root<Event> root = cq.from(Event.class);
ParameterExpression<Geometry> circleParm = cb.parameter(Geometry.class);
cq.where(cb.isTrue(cb.function("st_within", Boolean.class,
root.get(Event_.location), circleParm)));
TypedQuery<Event> tq = em.createQuery(cq);
tq.setParameter(circleParm, area);
return tq.getResultList();
}
Small price: The function name is database-dependent. In PostgreSQL the within function is called st_within.
Related
I have 4 tables in DB, such as
Product => PK productid
Variant => PK variantid FK productid
Images => PK imageid FK variantid
Attribute => PK attributid FK variantid
So need to perform following action in Java Rest API
Pagination with sorting and filtering
One Product Object has different Variant with there images and attributes.
Below is the #Repository code for single table pagination i.e Product
#Repository
public class PaginProductCriteriaRepository {
private final EntityManager entityManager;
private final CriteriaBuilder criteriaBuilder;
public PaginProductCriteriaRepository(EntityManager entityManager) {
this.entityManager = entityManager;
this.criteriaBuilder = entityManager.getCriteriaBuilder();
}
public Page<Product> findAllWithFilter(PaginProductPage paginPage, PaginProductSearchCriteria paginSearchCriteria){
CriteriaQuery<Product> productCriteriaQuery = criteriaBuilder.createQuery(Product.class);
Root<Product> rootProduct = productCriteriaQuery.from(Product.class);
Predicate predicate = getPrediate(paginSearchCriteria,rootProduct);
productCriteriaQuery.where(predicate);
setOrder(paginPage,productCriteriaQuery,rootProduct);
TypedQuery<Product> typedQuery = entityManager.createQuery(productCriteriaQuery);
typedQuery.setFirstResult(paginPage.getPageNumber() * paginPage.getPageSize());
typedQuery.setMaxResults(paginPage.getPageSize());
Pageable pageable = getPageable(paginPage);
long paginCount = getPaginCountMethod(predicate);
return new PageImpl<>(typedQuery.getResultList(),pageable,paginCount);
}
private Predicate getPrediate(PaginProductSearchCriteria paginSearchCriteria, Root<Product> root) {
List<Predicate> predicates = new ArrayList<>();
if (Objects.nonNull(paginSearchCriteria.getpName())) {
predicates.add(criteriaBuilder.like(root.get("pName"),"%" + paginSearchCriteria.getpName() + "%"));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private void setOrder(PaginProductPage paginPage, CriteriaQuery<Product> criteriaQuery, Root<Product> root) {
if (paginPage.getSortDirection().equals(Sort.Direction.ASC)) {
criteriaQuery.orderBy(criteriaBuilder.asc(root.get(paginPage.getSortBy())));
} else {
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(paginPage.getSortBy())));
}
}
private Pageable getPageable(PaginProductPage paginPage) {
Sort sort = Sort.by(paginPage.getSortDirection(),paginPage.getSortBy());
return PageRequest.of(paginPage.getPageNumber(),paginPage.getPageSize(),sort);
}
private long getPaginCountMethod(Predicate predicate) {
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
Root<Product> countRoot = countQuery.from(Product.class);
countQuery.select(criteriaBuilder.count(countRoot)).where(predicate);
return entityManager.createQuery(countQuery).getSingleResult();
}
}
Expectation:
Using above code pagination is working fine using single Table i.e Product BUT I need to join multiple tables with product and get full product object contain variants, images and attributes.
Note: Single Product has multiple variants, variant has muluple attribute and images
My system required to add filters,and I'm wonder if there any query that like this
SELECT *
FROM posts p
when byDate is not null then (where p.createAt BETWEEN :startDate AND :endDate)
when byType is not null then (where p.type = :type)
I knew that the query is not valid, but I want at one query to get the data wherever the request has (no filter or all filters or some of filters).
My goal is to create one query to achieve all cases.
It's usually not a good idea to write a big SQL query when you can tell in advance the actual query you want to run.
If you want to run a different query based on conditions you know before running the query, there are different approaches in JPA or Spring that you can use
Spring
You can define the different queries using Spring Data query methods?
public class PostRepository implements JpaRepository<Post, Long> {
List<Post> findByCreatedAtBetween(Date startDate, Date endDate);
List<Post> findByTypeIs(String type);
}
And then somewhere in the code, you can:
List<Post> results = null;
if (byDate != null) {
results = repository.findByCreatedAtBetween(startDate, endDate);
} else if (byType != null) {
results = repository.findByTypeIs(type);
} else {
results = repository.findAll();
}
Criteria
With criteria you can create a dynamic query at runtime and execute it:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(Post.class);
Root<User> user = query.from(Post.class);
if ((filter.getByDate() != null)) {
// byDate is not null
ParameterExpression<Date> startDate = builder.parameter( Date.class );
ParameterExpression<Date> endDate = builder.parameter( Date.class );
query.where(builder.between( b.get( "createdAt" ), startDate, endDate));
return em.createQuery(query)
.setParameter(startDate, ...)
.setParameter(endDate, ...)
.getResultList();
}
if (filter.getByType() != null) {
ParameterExpression<Date> typeParam = builder.parameter( Date.class );
query.where(builder.and(root.get("type"), typeParam));
return em.createQuery(query)
.setParameter(typeParam, ...)
.getResultList();
}
return entityManager.createQuery(query)
.getResultList();
}
}
Assuming that your entity has the fields type and createdAt.
This approach works well if you don't know in advance what's your query looks like. For example, when you don't know how many conditions you will have to add to it.
But, if I know already which query I want to run, then I prefer to use HQL/JPQL.
HQL
If your queries don't change and you already know what they look like,
I find it easier to define them with HQL:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
if (filter.getByDate() != null) {
return em.createQuery("from Post p where p.createdAt between :startDate and :endDate", Post.class)
.setParameter("startDate", ...)
.setParameter("endDate", ...)
.getResultList();
}
if (filter.getByType() != null) {
return em.createQuery("from Post p where p.type =:type", Post.class)
.setParameter("type", ...)
.getResultList();
}
return em.createQuery("from Post", Post.class)
.getResultList();
}
}
You can refactor the code to make it more elegant, but it should give you an idea. Note that if you need to reuse the same queries in different services, it might be helpful to define them using the annotation #NamedQuery.
Filters
In Hibernate (not JPA) you can also define filters. They are SQL filter conditions that one can apply at runtime:
#Entity
#FilterDef(name = Post.BY_DATE, defaultCondition = "createdAt between :startDate and :endDate", parameters = {#ParamDef(name = "startDate", type = "date"), #ParamDef(name = "startDate", type = "date") })
#FilterDef(name = Post.BY_TYPE, defaultCondition = "type = :type", parameters = #ParamDef(name = "startDate", type = "date"))
#Filter(name = Post.BY_DATE)
#Filter(name = Post.BY_TYPE)
class Post {
static final String BY_DATE = "Post.byDateFilter";
static final String BY_TYPE = "Post.byFilter"
private String type;
private Date createdAt;
...
}
Then:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
enableFilters(em);
return em.createQuery("from Post", Post.class).getResultList();
}
private void enableFilters(Filter filter, EntityManager em) {
if (filter.getByDate() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_DATE )
.setParameter("startDate", ...)
.setParameter("endDate", ...);
} else if (filter.getByType() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_TYPE )
.setParameter("type", ...);
}
}
}
I have two classes: News and Comments with one-to-many association between them.
I am using Hibernate Criteria to fetch news from database. I would like my news to be ordered by the count of its comments.
session.createCriteria(News.class, "n");
criteria.createAlias("n.comments", "comments");
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("comments.id"))
.add(Projections.count("comments.id").as("numberOfComments")));
criteria.addOrder(Order.desc("numberOfComments"));
List<News> news = criteria.list();
With the following code I'm getting not the list of news but the list of objects with two Long's in each of them.
What should I do to get the list of sorted news objects?
I've found the answer to my question here:
Hibernate Criteria API - how to order by collection size?
I've added the new hibernate Order implementation:
public class SizeOrder extends Order {
protected String propertyName;
protected boolean ascending;
protected SizeOrder(String propertyName, boolean ascending) {
super(propertyName, ascending);
this.propertyName = propertyName;
this.ascending = ascending;
}
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
String role = criteriaQuery.getEntityName(criteria, propertyName) + '.' + criteriaQuery.getPropertyName(propertyName);
QueryableCollection cp = (QueryableCollection) criteriaQuery.getFactory().getCollectionPersister(role);
String[] fk = cp.getKeyColumnNames();
String[] pk = ((Loadable) cp.getOwnerEntityPersister())
.getIdentifierColumnNames();
return " (select count(*) from " + cp.getTableName() + " where "
+ new ConditionFragment()
.setTableAlias(
criteriaQuery.getSQLAlias(criteria, propertyName)
).setCondition(pk, fk)
.toFragmentString() + ") "
+ (ascending ? "asc" : "desc");
}
public static SizeOrder asc(String propertyName) {
return new SizeOrder(propertyName, true);
}
public static SizeOrder desc(String propertyName) {
return new SizeOrder(propertyName, false);
}
}
And then applied that to my criteria as
criteria.addOrder(SizeOrder.desc("n.comments"));
Now everything works fine,
thanks everyone a lot :)
I'm selecting records in JAVA with JPA and playframework like this:
EntityManager em = JPA.em();
List<News> resultUrl = News.find("link", url).fetch();
if (resultUrl.isEmpty()) { //check if it is exist
}
But i want to select records with two condition, like this:
where link='url' and name='joe'
How can i do this?
Thanks for helping.
Best wishes.
Use:
Query q = em.createQuery("FROM News n WHERE n.link=:url and n.name=:name");
q.setParameter("url", "url").setParameter("name", "joe");
List<News> resultUrl = q.getResultList();
...
One way to do it with Play is
List<News> resultUrl = News.find("byLinkAndName", url, "joe").fetch();
if (resultUrl.isEmpty()) { //check if it is exist
}
Another:
List<News> resultUrl = News.find("link = ? and name = ?", url, "joe").fetch();
if (resultUrl.isEmpty()) { //check if it is exist
}
My proposal is to define a named query:
#Entity
#NamedQueries({
#NamedQuery(name = News.FIND_BY_URL_AND_NAME, query = "Select n FROM News as n WHERE n.url=:" + News.PARAM_URL + " AND n.name=:" + News.PARAM_NAME)
})
public class News {
public static final String FIND_BY_URL_AND_NAME = "News.findByUrlAndName";
public static final String PARAM_URL = "url";
public static final String PARAM_NAME = "name";
//CONTINUE
}
Then you call it like that:
Query query = em.createNamedQuery(News.FIND_BY_URL_AND_NAME);
query.setParameter(News.PARAM_URL, "url");
query.setParameter(News.PARAM_NAME, "name");
List<News> news = query.getResultList();
Get a look at CriteriaBuilder, CriteriaQuery and Predicate :
EntityManager em = JPA.em();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = cb.createQuery(News.class);
Root<T> root = criteriaQuery.from(News.class);
criteriaQuery.select(root);
List<Predicate> ps = new ArrayList<Predicate>();
ps.add(sb.equal(root.get("link", url));
ps.add(sb.equal(root.get("name", "joe"));
criteriaQuery.where(cb.and(ps.toArray(new Predicate[0])));
List<News> resultUrl = em.createQuery(criteriaQuery).getResultList();
Regards
Calling javax.persistence.criteria.Path.get(String name) fails for the simple class hierarchy detailed below. The call succeeds if #IdClass and 2nd id field (i.e. id2) are removed. Anyone know why this is so. Does this mean it is not possible to query on a single id field where that id field forms part of a composite key?
failing call is: Path<Object> path = entity.get(name);
private static final EntityManager em;
private final CriteriaBuilder cb = em.getCriteriaBuilder();
private final CriteriaQuery<Y> query = cb.createQuery(Y.class);
private final Root<Y> entity = query.from(Y.class);
static {
Map<String, Object> properties = new HashMap<String, Object>();
// initialise properties appropriately
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("test", properties);
em = emf.createEntityManager();
}
interface PK {
Object getPK();
}
public static class YPK implements Serializable {
int id;
int id2;
YPK(int id, int id2) { }
// Override hashCode() and equals() appropriately
}
#IdClass(YPK.class)
#Entity
public static class Y implements Serializable, PK {
#Id
int id;
#Id
int id2;
protected Y() { }
public Y(int id) {
this.id = id;
}
#Override
public Object getPK() {
return id;
}
}
#Test
public void simpleTest() {
List<Y> yy = new ArrayList<Y>();
Y yX1 = new Y(5);
yy.add(yX1);
Y yX2 = new Y(6);
yy.add(yX2);
saveItems(yy);
String name = "id";
Path<Object> path = entity.get(name);
Predicate restriction = cb.conjunction();
restriction = cb.and(restriction, cb.and(new Predicate[]{cb.equal(path, 5)}));
TypedQuery<Y> tq = em.createQuery(this.query);
Y result = null;
try {
result = tq.getSingleResult();
} catch (NoResultException e) {
}
assertNotNull(result);
}
To access fields that are members of the IdClass you need to use metamodel.
I suggest to go with static metamodel, because it is cleaner and kind of type safe. You can generate it with the tools or write it by yourself. For class Y it will be something like:
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
#StaticMetamodel(Y.class)
public abstract class Y_ {
public static volatile SingularAttribute<Y, Integer> id;
public static volatile SingularAttribute<Y, Integer> id2;
// + similar definitions for other fields:
// <TYPE_OF_ENTITY, TYPE_OF_ATTRIBUTE> NAME_OF_FIELD_IN_ENTITY
}
Then you can use IdClass fields in criteria query:
Path<Integer> pathToId = entity.get(Entity2_.id);
Path<Integer> pathToId2 = entity.get(Entity2_.id2);
If you don't want to generate static metamodel, then there is still following rather bad way to access attributes of id:
//find set of all attribute that form id
Metamodel mm = em.getMetamodel();
EntityType et = mm.entity(Y.class);
Set<SingularAttribute> idAttributes = et.getIdClassAttributes();
SingularAttribute idAttribute = null;
//and pick out right one from [id, id2]
for (SingularAttribute candidate : idAttributes) {
if (candidate.getName().equals("id")) {
idAttribute = candidate;
break;
}
}
Path<Integer> path = entity.get(idAttribute);