JPA or Play Framework list from query joining 2 tables - jpa

Mainly I work with JSF so am totally new to this annotation subject
If anyone can help
I wanna a list from this query
SELECT f.CODE ,f.NAME || '-' || e.NAME
FROM FS.ELIGIBLE e RIGHT
OUTER JOIN FS.FINANCIAL_SUPPORT f ON e.CODE = f.CODE ;
The query above retrieves a list from 2 tables and concatenating the name field from both tables!!
How can i do this in JPA or in play with another query supported by Play Framework ???

Have a read of the Play Framework documentation, specifically the part about JPA and your Domain Model.
You can access the entity manager at any time by calling
EntityManager entityManager = JPA.em();
Using this you can create any query that you want, even a "Native" Query. For example:
List<Object> results = JPA.em().createNativeQuery(
"SELECT f.CODE ,f.NAME || '-' || e.NAME "+
"FROM FS.ELIGIBLE e RIGHT "+
"OUTER JOIN FS.FINANCIAL_SUPPORT f ON e.CODE = f.CODE").getResultList()

JPA is not like relational database system which you can do your queries like join, left join or outer joins it is a mapping technology of objects. You can also do the same fetch just like those RDBMS counterparts but with a different approach.
What you have to do is make an Object then relate your second Object to your first Object, that is the proper way to relate 2 or more Objects. The Objects I'm talking about is your Table. See my example below:
Table1: Items.java
...
// do your imports here ...
// do your annotations here like
#Entity
#Table(name="Items")
public class Items implements Serializable {
private String id;
private String itemno;
private String description;
private Set<Vendors> vendors; //this is the 2nd table (1:n relationship)
...
// don't forget your constructor
// in your setter and getter for Vendors do the ff:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="id")
public Set<Vendors> getVendors() {
return vendors;
}
public void setVendors(Set<Vendors> vendors) {
this.vendors = vendors;
}
...
}
Table2: Vendors.java
#Entity
#Table(name="Vendors")
public class Vendors implements Serializable {
private long id;
private String company;
private String contact;
private String sequence;
...
public Vendors() { }
...
// in your setter & getter
#Id
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
...
}
Now on your query, just do a regular select as in the ff:
public void makeQuery(String seq) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(...);
EntityManager em = emf.createEntityManager();
TypedQuery<Items> query = em.createQuery("
SELECT i, j.contact, j.company, j.sequence FROM Items i LEFT OUTER JOIN i.vendors j WHERE i.vendors.sequence = :seq
ORDER BY i.id", Items.class);
List<Items> items = query.setParameter("sequence", seq).getResultList();
...
}
Now you can refer to your 2nd table Vendors by using items.vendors.company ... and so on.
One disadvantage with this one is that, JPA make its related objects in the form of Set, see the declaration in Table1 (Items) - private Set vendors. Since it was a Set, the sequence is not in order it was received, unlike using List.
Hope this will help ...
Regards, Nelson Deogracias

Related

JPA Criteria API join on 3 tables and some null elements

I have one parent entity that has two child entities as attributes.
I want to select all elements from the parent entity that have EITHER a childOne with a given parameter as personal attribute OR childTwo with that same given parameter as personal attribute.
Here are my three classes simplified:
The Parent Object:
#Entity
public class ParentObject {
#Id
private int id;
private int fkChildOne;
private int fkChildTwo;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildOne childOne;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildTwo childTwo;
// getters and setters
}
The Child One Object:
#Entity
public class ChildOne {
#Id
private int childOneId;
private String nameChildOne;
#OneToMany
#JoinColumn(name = "fk_child_one_id")
private List<ParentObject> parents;
// getters and setters
}
The Child Two Object:
#Entity
public class ChildTwo {
#Id
private int childOneId;
private String nameChildTwo;
#OneToMany
#JoinColumn(name = "fk_child_two_id")
private List<ParentObject> parents;
// getters and setters
}
The Specs Class:
public static Specification<ParentObject> checkName(String name) {
return Specifications.where(
(root, query, builder) -> {
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne");
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo");
return builder.or(
builder.equal(joinchildOne .get("nameChildOne"), name),
builder.equal(joinchildTwo .get("nameChildTwo"), name)
);
}
);
}
When this spec is called in my service, I get no results. However, if I comment out one of the two joins and the corresponding Predicate in my builder.or method, then I get some results but they obviously don't match what I'm looking for, which is to select every ParentObject that have either ChildOne with that parameter or ChildTwo with that paramater.
Any clue what's wrong with the code ?
Finally got the solution : to fetch all the corresponding results, I had to add the type of the join which would be left join, since I wanted to fetch all ParentObjects regardless of owning childOne or ChildTwo objects.
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne", JoinType.LEFT);
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo", JoinType.LEFT);
Great, now you have to choose if you need to join or fetch.To optimize the query and the memory, you should establish the relations as Lazy (#ManyToMany (fetch = FetchType.LAZY)), so you will only bring the objects that you demand.
The main difference is that Join defines the crossing of tables in a variable and allows you to use it, to extract certain fields in the select clause, for example, on the other hand, fetch makes it feed all the objects of that property. On your example,
a select from parent with join of children (once the relation is set to lazy) would only bring initialized objects of type parent, however if you perform a fetch, it would bring the parent and child objects initialized.
Another modification I would make is to change the type of the identifier to non-primitive, so that it accepts null values, necessary for insertion using sequences

Convert specific query into JPQL or Criteria Builder query

Here is the code for 2 entities (it generates three tables in the database). A Book entity:
#Entity
public class Book {
#Id
private long id;
private String name;
#ManyToMany
private List<Author> authors;
}
An Author entity:
#Entity
public class Author {
#Id
private long id;
#Column(unique=true)
private String name;
}
I'm trying to find books by the list of authors. Here is a sql query:
select book.id, ARRAY_AGG(author.name)
from book
join book_authors ba on book.id=ba.book_id
join author on ba.authors_id=author.id
group by book.id
having ARRAY_AGG(distinct author.name order by author.name)=ARRAY['a1', 'a2']::varchar[]
['a1', 'a2'] is a list of book authors, it must be passed as a parameter. The idea is to aggregate authors and then compare them with the list of passed parameters.
How to rewrite this SQL-query into either a JPQL or CriteriaBuilder query?
#Query("select distinct b from Book b join b.authors a where a.name in(:names)")
List<Book> findByAuthorsNames(#Param("names") List<String> names)
If you want to fetch b.authors use join fetch instead of join
If the exact match is necessary you can use Specification like this
public class BookSpecifications {
public static Specification<Book> byAuthorsNames(List<String> names) {
return (root, query, builder) -> {
Join<Book, Author> author = root.join("authors", JoinType.LEFT);
Predicate predicate = builder.conjunction;
for(String name : names) {
Predicate namePredicate = builder.and(author.get("name"), name);
predicate = builder.and(predicate, namePredicate);
}
return predicate;
}
}
}
BookRepository have to extend JpaSpecificationExecutor.
Usage:
BookRepository repository;
public List<Book> findByAuthorsNames(List<String> names) {
return repository.findAll(BookSpecifications.byAuthorsNames(names));
}

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.

JPA how to aggregate columns in a join

I'm trying to implement a query like this in JPA:
SELECT
ta.field_aggr_1,
ta.field_aggr_2,
MIN(tb.date_inv) AS min_date_inv,
MAX(tb.date_inv) AS max_date_inv
FROM table_a ta
INNER JOIN table_b tb ON ta.idB = tb.id
GROUP BY
ta.field_aggr_1,
ta.field_aggr_2
The key point is the MIN and MAX functions that apply in one column of a joined table.
I've created the entities:
#Entity
#Table(name="table_a")
public class EntityA extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="field_aggr_1")
private String field_aggr_1;
#Column(name="field_aggr_2")
private String field_aggr_2;
#ManyToOne
#JoinColumn(name="idB")
private EntityB entityB;
// Getters & Setters & HashCode & equals & toString
}
#Entity
#Table(name="table_b")
public class EntityB extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="date_inv")
private String date_inv;
// Getters & Setters & HashCode & equals & toString
}
And in the service I want to create query:
EntityManager em = ...;
Root<EntityA> root = criteriaQuery.from(EntityA.class);
EntityType<EntityA> type = this.em.getMetamodel().entity(EntityA.class);
Join<EntityA, EntityB> join = root.join(type.getDeclaredSingularAttribute("entityB", EntityB.class));
List<Selection<?>> fields = new ArrayList<Selection<?>>();
// grouping fields
fields.add(root.<EntityA>get("field_aggr_1"));
fields.add(root.<EntityA>get("field_aggr_2"));
I've managed to include fields from the joined table,
fields.add(join.<EntityB>get("date_inv"));
BUT I haven't succeed in implement the min aggregation.
Thanks in advance for your answers!
I have managed to solve the question. First, I needed to have one more "root" and one more "entityType" for the joined entity:
Root<EntityB> rootB = criteriaQuery.from(EntityB.class);
EntityType<EntityB> typeB = this.em.getMetamodel().entity(EntityB.class);
With these, now I can do what I needed:
fields.add(builder.least(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
fields.add(builder.greatest(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
Hope that it helps someone!

EclipseLink Batch Fetch Hint Not Working For Two Fields

I'm using QueryHints in Spring Data JPA to use EclipseLink Batch Fetch with a type of IN. Ultimately, I need to use this around 30 fields but it doesn't seem to work right for 2 fields. Field A has a ManyToOne relationship and Field B has a ManyToMany. Based on the results of the initial query, I would expect the batch hint to generate an IN clause with 2 ids for Field A and 12 for Field B. This works fine when the hint is turned on for one field at a time. When it is enabled for both fields, the hint only applies to whichever field is the last hint in the list of QueryHints. I've tried EAGER and LAZY fetch on the fields as a shot in the dark, but it had not impact.
Is there a limitation with mixing batch fetch hints based on the relationship type? Is there something different going on? The EclipseLink documentation isn't very detailed for this feature.
EDIT: It seems it doesn't matter what fields I enable it only, it only works for one at at time. Here is sample code for two entities. The BaseEntity defines the PK id generation.
#Entity
#Table(name = "MainEntity")
public class MainEntity extends BaseEntity implements Cloneable {
...
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinTable(
name="EntityBMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="bId", referencedColumnName="id")})
#JsonIgnore
private Set<EntityB> bSet = new HashSet<>();
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(
name="EntityAMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="aId", referencedColumnName="id")})
#JsonIgnore
#OrderColumn(name="order_index", columnDefinition="SMALLINT")
private List<EntityA> aList = new ArrayList<>();
...
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityA")
public class EntityA extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityASet", fetch=FetchType.LAZY)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityB")
public class EntityB extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityBSet", cascade=CascadeType.ALL)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
The repository query:
#QueryHints(value = {
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_TYPE, value = "IN"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_SIZE, value = "250"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.aList")},
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.bSet")},
forCounting = false)
List<MainEntity> findAll(Specification spec);
Generated queries:
SELECT id, STATUS, user_id FROM MainEntity WHERE ((STATUS = ?) OR ((STATUS = ?) AND (user_id = ?)))--bind => [ONESTAT, TWOSTAT, myuser]
..
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac]
...
SELECT t1.id, t1.name, t0.mainId FROM EntityBMapping t0, EntityB t1 WHERE ((t1.id = t0.bId) AND (t0.mainId IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac, 1c07a3a9-7028-48ba-abe8-2296d58ebd57, 235bb4f2-d724-4237-b73b-725db2b9ca9f, 264f64b3-c355-4476-8530-11d2037b1f3c, 2d9a7044-73b3-491d-b5f1-d5b95cbb1fab, 31621c93-2b0b-4162-9e42-32705b7ba712, 39b33b19-c333-4523-a5a7-4ba0108fe9de, 40ba7706-4023-4b7e-9bd5-1641c5ed6498, 52eed760-9eaf-4f6a-a36f-076b3eae9297, 71797f0c-5528-4588-a82c-5e1d4d9c2a66, 89eda2ef-80ff-4f54-9e6a-cf69211dfa61, 930ba300-52fa-481c-a0ae-bd491e7dc631, 96dfadf9-2490-4584-b0d4-26757262266d, ae079d02-b0b5-4b85-8e6f-d3ff663afd6e, b2974160-33e8-4faf-ad06-902a8a0beb04, b86742d8-0368-4dde-8d17-231368796504, caeb79ce-2819-4295-948b-210514376f60, cafe838f-0993-4441-8b99-e012bbd4c5ee, da378482-27f9-40b7-990b-89778adc4a7e, e4d7d6b9-2b8f-40ab-95c1-33c6c98ec2ee, e557acf4-df01-4e66-9d5e-84742c99870d, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a76, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a77]
...
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [1c07a3a9-7028-48ba-abe8-2296d58ebd57]
As Chris mentioned, Named Queries are the best work around for this issue. The other option is to use a custom repository and call setHint on the EntityManager yourself for each hint specified (plenty of examples out there for creating custom repos in Spring Data JPA). You could attempt to override findOne(...) and protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) on SimpleJpaRepository to try and create a generic way to properly set the hints but you'll likely want to check that you don't duplicate hint setting on getQuery(...) as you'll still want to call super() for that and then apply your additional hints before returning the query. I'm not sure what the behavior would be if you applied a duplicate hint. Save yourself the trouble and use Named Queries is my advice.