criteria api--root.fectch() how to fetch a collection? - criteria

The args' type of method fetch() can be SingularAttribute, PluralAttribute, why not can't be ListAttribute ?
Then, how to fetch a collection with critria api ? Thank you.

Of course it can, as Rasmus Franke said. Just check from the javadocs for FetchParent
or try this:
#Entity
public class SomeEntity {
#Id int id;
#OneToMany List<OtherEntity> others;
}
#Entity
public class OtherEntity {
#Id int id;
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<SomeEntity> cq = cb.createQuery(SomeEntity.class);
Root<SomeEntity> root = cq.from(SomeEntity.class);
ListAttribute<? super SomeEntity, OtherEntity> listAttribute = root.getModel().getList("others", OtherEntity.class);
root.fetch(listAttribute, JoinType.LEFT);
cq.select(root);

ListAttribute extends PluralAttribute, as does any attribute based on a collection. Did you actually try to use root.fetch(ListAttribute)?

Related

How to filter a OneToMany field using Spring Data JPA?

I'm trying to filter out posts associated with a category depending on whether the post is set as hidden or not.
I can do this with a post-query filter just fine (see below) but I was wondering if it's possible to construct the query using the JPA methods? (Specifically the query building methods like FindAllBy..., I'm hoping to keep database agnostic by sticking to these types of queries)
I could also probably call FindAllByCategory on the PostRepository and construct the return that way but it feels hacky and backwards.
So to summarize I'd like to find a way to declare FindAllAndFilterPostsByIsHidden(boolean isHidden)
Category Class
#Entity
public class Category {
public Category(String name, Post... posts) {
this.name = name;
this.posts = Stream.of(posts)
.collect(Collectors.toSet());
this.posts.forEach(post -> post.setCategory(this));
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "category")
private Set<Post> posts;
}
Post Class (stripped to basics for brevity )
#Entity
public class Post {
public Post(Category category, boolean isHidden) {
this.category = category;
this.isHidden = isHidden
}
#ManyToOne
#JoinColumn
private Category category;
private boolean isHidden;
}
Right now I'm doing this to filter the posts associated with categories in the CategoryController
#GetMapping
public List<Category> list(Authentication authentication) {
boolean canViewHidden = securityService.hasAuthority(Permissions.Post.VIEWHIDDEN, authentication.getAuthorities());
List<Category> categories = categoryRepository.findAll();
categories.forEach(
category -> {
Set<Post> filteredPosts = category.getPosts().stream()
.filter(post -> canViewHidden || !post.isHidden())
.collect(Collectors.toSet());
category.setPosts(filteredPosts);
}
);
return categories;
}
I'd try using a custom query in your JPA-Repository for the Post Class like this:
#Query(value = "SELECT p FROM Post p INNER JOIN Category c ON p.id = c.post.id "
+ "WHERE p.hidden = false AND c.id = :id")
List<Post> findViewablePostsByCategory(#Param("id") Long categoryId);
I know this might not be the exact approach you were looking for but as K.Nicholas pointed out there is no way to use joins with the query building methods of JPA-Repositories.

How specify a JPA criteria API with "MEMBER IN" and a many-to-many relationship in a mapped superclass?

I would like to query ConcreteEntity where AUser user IS MEMBER of ConcreteEntity.createdBy with
#Entity
public class AUser implements Serializable {
#Id
#GeneratedValue
private Long id;
private String property;
and
#MappedSuperclass
public class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#ManyToMany
private Set<AUser> createdBy = new HashSet<>();
and
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
ppublc class ConcreteEntity extends AbstractEntity {
private String property1;
i.e.
EntityManager em = ...; //set to null or else
AUser user = ...; //set to null or else
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ConcreteEntity> criteria = cb.createQuery(ConcreteEntity.class);
Root<ConcreteEntity> concreteRoot = criteria.from(ConcreteEntity.class);
criteria.select(concreteRoot);
SetAttribute<AbstractEntity, AUser> setAttribute = ConcreteEntity_.createdBy;
PluralAttribute<ConcreteEntity, Set<AUser>, AUser> pluralAttribute = setAttribute;
//richtercloud/jpa/criteria/api/metamodel/member/of/NewMain.java:[40,77] error: incompatible types: SetAttribute<AbstractEntity,AUser> cannot be converted to PluralAttribute<ConcreteEntity,Set<AUser>,AUser>
Expression<Set<AUser>> createdByExpression = concreteRoot.get(pluralAttribute);
criteria.where(cb.isNotMember(user, createdByExpression));
TypedQuery<ConcreteEntity> query = em.createQuery(criteria);
List<ConcreteEntity> result = query.getResultList();
I don't get the transition from SetAttribute to PluralAttribute. I know that I'm not supposed to post question with code which doesn't compile, but although I'd say I have a fair understanding of generics, I don't get it here.
I'm using Eclipselink 2.5.2 and JPA 2.1.
look at the Path.get methods:
<Y> Path<Y> get(SingularAttribute<? super X, Y> attribute);
<E, C extends Collection<E>> Expression<C> get(PluralAttribute<X, C, E> collection);
the second should be instead:
<E, C extends Collection<E>> Expression<C> get(PluralAttribute<? super X, C, E> collection);
So, IMHO, it's not your fault, the API is bugged.
A workaround is needed. Just erase the compile-time type (it's erased on run-time, anyway).
You have to do:
Expression<Set<AUser>> createdByExpression = ((Root<AbstractEntity>) (Object) root).get(AbstractEntity_.createdBy);
or
Expression<Set<AUser>> createdByExpression = root.get((SetAttribute<ConcreteEntity, AUser>) (Object) AbstractEntity_.createdBy);
or else
Expression<Set<AUser>> createdByExpression = root.get("createdBy");

How to expose a complete tree structure with Spring Data REST and HATEOAS?

I have a JPA tree structure
#Entity
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String text;
#ManyToOne
#JoinColumn(name = "parent")
Document parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
Set<Document> children;
(getters and setters)
}
and a projection
#Projection(name = "all", types = Document.class)
public interface AllDocumentsProjection {
int getId();
String getText();
Set<Document> getChildren();
}
When I make a GET request with url
localhost:8080/documents/1?projection=all
I only get the first children of the root document. Not children of the children. Is this possible with projections? Or is there an other way?
#Projection(name = "all", types = Document.class)
public interface AllDocumentsProjection {
int getId();
String getText();
Set<AllDocumentsProjection> getChildren();
}
This works perfect for me.
I'm almost certain there is no way to recursively embed resources via projections. Only other thing I think of is to handle this logic manually in the controller :/
Try excerpts.
You should add to your repository definition the excerptProjection field like below:
#RepositoryRestResource(excerptProjection = AllDocumentsProjection.class)
interface DocumentRepository extends CrudRepository<Document, Integer> {}

Hibernate-search search from any indexed entity

I am using Hibernate-search for searching data in my Jboss application. I have 3 JPA entity classes that all extend BaseEntity class and each are indexed by Lucene. For example:
#MappedSuperclass
public abstract class BaseEntity implements Serializable {
#Temporal(TemporalType.TIMESTAMP)
private Date created;
public abstract Long getId();
}
#Entity
#Table(name = "DVD")
public class Dvd extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Field
private String title;
}
#Entity
#Table(name = "BOOK")
public class Book extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Field
private String author;
}
Now I would like to search for either DVD title or Book author by wildcard search query and get the result list as List. This is what I have this far:
public List<BaseEntity> search(String query, int firstResult, int maxResults) {
List<BaseEntity> results = null;
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
Query luceneQuery = new WildcardQuery(new Term("*", "*" + query + "*"));
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, BaseEntity.class);
fullTextQuery.setFirstResult(firstResult);
fullTextQuery.setMaxResults(maxResults);
results = fullTextQuery.getResultList();
return results;
}
But with this I am not getting any results. How is it possible to get this to work or is there even way without using buildQueryBuilder for each entity? Thanks!
You'll want to use the varargs-style method for the classes, like so:
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, DVD.class, Book.class);
This is because when Hibernate Search creates the search query, it adds the class name(s) to the query (for the _hibernate_class field, which is the indexed class' name).

JPA Criteria API predicates for objects in OneToMany relationship

Given the following code
#Entity
public class Invoice {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
public Long id;
#Embedded
private InvoiceData data = new InvoiceData();
}
#Embeddable
public class InvoiceData {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Collection<InvoiceLineItem> lineItems;
}
#Entity
public abstract class InvoiceLineItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String description;
}
#Entity
public class GoodsLineItem extends InvoiceLineItem {
}
#Entity
public class CostLineItem extends InvoiceLineItem {
}
How would I write a criteria api query that returns all Invoices with a CostLinesItem that's description is 'TAX'?
I am using the metadata API. I have tried various approaches most of which are variations of the 2 listed below. Any pointers/help or 'go read this's will be greatly appreciated.
Attempt 1 (of many):
#Test
public void criteria_api_and_collections() throws Exception {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Invoice> query = builder.createQuery(Invoice.class);
Root<Invoice> root = query.from(Invoice.class);
Join<InvoiceData, InvoiceLineItem> lineItems = root.join(Invoice_.data).join(InvoiceData_.lineItems);
query.where(builder.equal(lineItems.get(InvoiceLineItem_.description), ""));
List<Invoice> resultList = em.createQuery(query).getResultList();
System.out.println(resultList);
}
Attempt 2 (of a many):
#Test
public void criteria_api_and_collections() throws Exception {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Invoice> query = builder.createQuery(Invoice.class);
Root<Invoice> root = query.from(Invoice.class);
Join<InvoiceData, InvoiceLineItem> lineItems = root.join(Invoice_.data).join(InvoiceData_.lineItems, JoinType.LEFT);
Subquery<CostLineItem> subquery = query.subquery(CostLineItem.class);
Root<CostLineItem> fromLineItem = subquery.from(CostLineItem.class);
subquery.select(fromLineItem);
subquery.where(builder.equal(lineItems.get(InvoiceLineItem_.description), "TAX"));
query.where(builder.in(lineItems).value(subquery));
List<Invoice> resultList = em.createQuery(query).getResultList();
}
Both attempts causes a SQL grammer Exception. An alias is referred to in the resulting SQL that is never created. It looks like the alias should have been assigned to a join in the SQL that does not exist. In other words the InvoiceLineItems are not fetched in the query.
I am not able to make a test right now, but sticking to the Java EE 6 Tutorial, we see that
Embeddable classes may also contain relationships to other entities or
collections of entities. If the embeddable class has such a
relationship, the relationship is from the target entity or collection
of entities to the entity that owns the embeddable class.
This makes me think that the Join Predicate should be defined with the starting Entity Invoice instead of InvoiceData. And this is supported also by the fact that normally the starting Entity should be the query root itself. I would try with something like this:
Join<Invoice, InvoiceLineItem> lineItems = root.join(Invoice_.data).join(InvoiceData_.lineItems);
I swapped out Hibernate 4.1.0.Final for EclipseLink 2.0.0 and it worked.