JPQL ManyToMany select - java

I have a Many To Many relationship between two entities called: Car and Dealership.
In native MySQL I have:
car (id and other values)
dealership (id and other values)
car_dealership (car_id and dealership_id)
And the query I want to make in JPQL is:
#Select List of cars in multiple dealerships
SELECT car_id FROM car_dealership WHERE dealership_id IN(1,2,3,56,78,999);
What is the proper way to make the JPQL equivalent?
My Java method signature is:
public List<Car> findByDealership(List<Dealership> dealerships);
I have tried
//TOTALLY WRONG QUERY CALL!!!
Query query = em.createQuery("SELECT c FROM Car c WHERE :dealer_ids IN c.dealerships");
List<Long> dealerIds = new ArrayList<Long>();
for(Dealership d : dealerships) {
dealerIds.add(d.getId());
}
query.setParameter(":dealer_ids", dealerIds);
List<Dealership> result = (List<Dealership>) query.getResultList();
return result;
}
Here is my JPA Annotations for such relationship in java:
#Entity
#Table(name = "car")
public class Car implements Serializable {
//Setup of values and whatnot....
#ManyToMany
#JoinTable(name = "car_dealership", joinColumns =
#JoinColumn(name = "car_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "dealership_id", referencedColumnName = "id"))
private List<Dealership> dealerships;
... other stuff (getters/setters)
}
#Entity
#Table(name = "property")
public class Dealership implements Serializable {
//Setting of values and whatnot
#ManyToMany(mappedBy = "dealerships")
private List<Car> cars;
.... other stuff(getters/setters)
}
EDIT
I also have tried:
Query query = em.createQuery("SELECT c FROM Car c INNER JOIN c.dealerships d WHERE d IN (:deals)");
query.setParameter("deals", dealerships);
Which threw the Error:
org.eclipse.persistence.exceptions.QueryException
Exception Description: Object comparisons can only use the equal() or notEqual() operators.
Other comparisons must be done through query keys or direct attribute level comparisons.
Expression: [
Relation operator [ IN ]
Query Key dealerships
Base stackoverflow.question.Car
Constant [
Parameter deals]]

select car from Car car
inner join car.dealerships dealership
where dealership in :dealerships
The parameter must be a collection of Dealership instances.
If you want to use a collection of dealership IDs, use
select car from Car car
inner join car.dealerships dealership
where dealership.id in :dealershipIds
Remamber that JPQL always use entities, mapped attributes and associations. Never table and column names.

Related

Using new Constructor in hql query cannot find proper consturctor or could not extract ResultSet

I have entities:
#Entity
public class Author{
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "author_book",
joinColumns = { #JoinColumn(name = "author_id") },
inverseJoinColumns = { #JoinColumn(name = "book_id") }
)
public List<Book> books;
}
#Entity
public class Book{
#ManyToMany(mappedBy = "books")
public List<Author> authors;
#OneToMany(mappedBy = "book")
public List<Fact> facts;
public Book(){}
public Book(List<Author> authors, List<Fact> facts){
this.authors = authors;
this.facts = facts;
}
}
#Entity class Fact{
#ManyToOne
#JoinColumn(name="book_id")
public Book book;
}
Book has more Authors and Authors can write more books. Every book has more facts about it and each act belongs only to one book.
Now what i want achieve is to recieve object of type Book based on author ID. The object of type Book should containt all authors and all facts.
I am using hibernate query for this:
List<Book> books = (List<Book>) session.createQuery("Select new Book (books.authors, books.facts ) FROM Author u JOIN u.books books JOIN books.facts WHERE u.id IN :ids ").setParameter("ids", authorId).list();
However it results in error:
Unable to locate appropriate constructor on class [Book]. Expected
arguments are: java.util.Collection, java.util.Collection
I have tried to adjust constructor to take Object or Collection, and cast it by myself:
public Book(Object authors,Object facts){
this.authors = (List<Author>)authors;
this.facts = (List<Fact>)facts;
}
But this complains:
org.hibernate.exception.SQLGrammarException: could not extract
ResultSet
What is the right way to fetch entity with wanted data then? It Complains only about relations (e.g collections), i have not found any solution for this anywhere ( nor documentation for hql ).
Thanks for help!
You cannot use the NEW operator with Entities!
This is for DTO (Data Transfer Object) projection.
In your case you don't even need the NEW because you can simply select the book which will be constructed by Hibernate using the default constructor and setting the fields.
List<Book> books =
(List<Book>) session.createQuery(
"Select books FROM Author u JOIN u.books books JOIN books.facts WHERE u.id IN :ids ")
.setParameter("ids", authorId).list();
and if you want to have the collections eager loaded use JOIN FETCH like:
List<Book> books =
(List<Book>) session.createQuery(
"Select b FROM Book b JOIN FETCH b.authors a JOIN FETCH b.facts f WHERE a.id IN :ids ")
.setParameter("ids", authorId).list();
Important notice:
Hibernate cannot fetch two Lists eagerly. There for I suggest to change the Lists to Sets:
#ManyToMany(mappedBy = "books")
public Set<Author> authors;
#OneToMany(mappedBy = "book")
public Set<Fact> facts;

Mapping NativeQuery results when using inner join

I have the follow situation:
#Entity
#XmlRootElement
#Table(name = "TB_A")
public class A implements Serializable {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
#Entity
#XmlRootElement
#Table(name = "TB_B")
public class B implements Serializable {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CD_B")
#JsonIgnore
private Set<C> c = new HashSet<>();
}
#Entity
#XmlRootElement
#Table(name = "TB_C")
public class C implements Serializable {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
I need to run the follow code:
String sql = "SELECT * FROM TB_A a "
+ "INNER JOIN TB_B b ON ... "
+ "LEFT JOIN TB_C c ON ... ";
Query query = em.createNativeQuery(sql, A.class);
List<A> AList = query.getResultList();
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Analising the executed queries, I notice that JPS is running a SELECT each time I access elements B and C.
How can I correctly map this nativeQuery to use the lazyLoad?
Obs: I MUST use NativeQuery, because in my WHERE clause I need to use an especific function from Oracle. So JPQL is not an option for me (If I could use it, my life would be much more easy!).
Short answer: you can't.
What this line will do:
Query query = em.createNativeQuery(sql, A.class);
Is execute your SQL, then throw away anything not related to A.class.
Then your for loop:
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Fetches everything again.
Here you use JPA again, which doesn't know anything about the query you executed before.
In some cases, you can try to map to C.class instead.
Then you'll be able to avoid the loop entirely.
If in your case you need both A.class and C.class, you can look into ResultSetMapping instead: http://www.java2s.com/Tutorial/Java/0355__JPA/SqlResultSetMappingForHierarchicalEntity.htm

Criteria Add all the tables from the entity class when it only needs part of it

when i create a count query with hibernate - Criteria - add all the possible table from the entity class as left join which is bad performance .
The entity :
#Entity
#Table(name = "employees")
Public Class Employees {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "lz_job_stat_id")
private Integer id;
#ManyToOne
#JoinColumn(name = "departments_id")
private Departments departments;
#ManyToOne
#JoinColumn(name = "managers_id")
private Managers managers;
}
And the criteria :
public class EmployeeDao {
public List<EmpDao> findIt(){
.....
Criteria crit = createEntityCriteria().setFetchMode("departments", FetchMode.SELECT);
crit.add(Restrictions.eq("managers.deleted", false));
crit.setProjection(Projections.count("id"));
return crit.list();
}
}
And the produced SQL :
select count() as y0_
from employees this_
left outer join departments department3_
on this_.department_id=department3_.department_id
left outer join managers manager2_
on this_.manager_id=manager2_.manager_id
now when i try the crit.list - it create a left join for all the possible tables.
when its not supposed to create a join for all of them.
isnt Criteria smart enought to know i dont need this tables ? only the one i use the "WHERE CLAUSE"
is there a way to explicitly tell Criteria "DO NOT JOIN THIS TABLES !!!"
without SQL
Specify fetch type on ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "departments_id")
private Departments departments;
or IMHO more preferably in criteria:
criteria.setFetchMode("departments", FetchMode.SELECT)

Hibernate: separate sql query for every collection

I have a Person class that has a collection of Contacts. Everything works ok, I get the list of persons with their contacts. However, in log I see that a separate query is made to read collection of every person. That is too bad.
How to make hibernate make a join to read all the data in one query? I use JPA.
This is the person class:
#Entity
#Table(name = "tbl1")
public class PersonItem implements Serializable{
#Id
#Column(name="col1")
private String guid;
.....
#ElementCollection(targetClass = ContactItem.class,fetch=FetchType.EAGER)
#CollectionTable(name="tbl2",joinColumns=#JoinColumn(name="col2"))
private List<ContactItem> contacts;
....
}
This is the contact class
#Embeddable
#Table(name = "tbl2")
public class ContactItem implements Serializable {
#Column(name="col1")
private String guid;
#Column(name="col3")
private String info;
}
This is the way I get the list of persons:
Query query = em.createQuery("Select p from PersonItem p WHERE p.guid IN (:guids)");
query.setParameter("guids", guids);
List<PersonItem> list=query.getResultList();
And this what I see in log (I have three persons in DB):
Hibernate: select personitem0_.col1 as col1_0_, personitem0_.col4 as col2_0_, personitem0_.col2 as col3_0_, personitem0_.col3 as col4_0_ from tbl1 personitem0_ where personitem0_.col1 in (? , ? , ?)
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Please, begin from a more simple mapping. Use plural names, and column prefixes.
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column(name = "f_guid")
private String guid;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Contact> contacts;
}
#Entity
#Table(name = "contacts")
public class Contact {
#Id
#Column(name = "f_guid")
private String guid;
#Column(name = "f_info")
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_person")
private Person person;
}
Person is associated to contacts by a foreign key fk_person in the contacts table.
Update
Looks like JPQL overrides a default fetching strategy. You need to specify a fetch explicitly
select p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
If you have duplicates, cause of joins, you can use distinct
select distinct p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
Try #Fetch on your relation.
Also i would suggest to use #OneToMany relation int this case
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN) //You can use SUBSELECT as well
private List<ContactItem> contacts;
You can read more about fetching strategies here
fetch-“join” = Disable the lazy loading, always load all the collections and entities.
fetch-“select” (default) = Lazy load all the collections and entities.
batch-size=”N” = Fetching up to ‘N’ collections or entities, Not record.
fetch-“subselect” = Group its collection into a sub select statement.

Select DISTINCT values form #ElementCollection List<String> in #NamedQuery

For a project I'm trying to lookup all distinct categories within a List #ElementCollection field. Each foo instance has one or more String categories assigned. The code below does not work as JBOSS/Hibernate throws an exception when deploying the ear to the server:
Error in named query: Foo.listUniqueCategories: org.hibernate.QueryException: not an entity [SELECT DISTINCT f.categories FROM com.Foo f]
I have the class:
#Entity(name = "Foo")
#NamedQuery(name = "Foo.listUniqueCategories", query = "SELECT DISTINCT f.categories FROM Foo f")
public class FooEntity
{
#Id()
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
protected Long id;
#ElementCollection
#CollectionTable(name = "categories", joinColumns = #JoinColumn(name = "foo_id"))
private List<String> categories;
...
}
Is there anything wrong with the select distinct? Is it even supported to perform a 'SELECT DISTINCT' on an #EllementCollection?
Any help is appreciated!
Richard
You are confused between HQL and SQL .. replace your named query with the following. Named Queries are always HQL.
SELECT distinct f.categories FROM FooEntity f
But, I'm not sure if this will work. If you need to find out the distinct categories, why query on FooEntity? Why not create an entity for Categories and run a query like below. Also, you are calling join column on a list which is wrong as well ,it should join on entity types like below
#ElementCollection
#CollectionTable(name = "categories", joinColumns = #JoinColumn(name = "foo_id"))
private List<**Category**> categories; // replace String with Category
--
select distinct category.name from Category c
Where Category is a new entity that you have to create.

Categories