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

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.

Related

Hibernate and Criteria Api generates wrong Join condition

I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee

JPA #Query to count how many relations each object has

I've been trying to get a query inside a join table for a many to many relation working. The query was meant to count how many users follow a specific game. The entity itself is very simple, looks like this:
#Entity
#Table(name = "followed_users_games", uniqueConstraints = {
#UniqueConstraint(columnNames = "followed_id")
})
public class FollowedEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "followed_id", unique = true, nullable = false)
private Integer followedId;
#ManyToOne
#JoinColumn(name = "game_id")
private GameEntity games;
#ManyToOne
#JoinColumn(name = "user_id")
private UserEntity users;
#Column(name = "notify")
#NonNull private Boolean notify;
}
And the query I've been trying to get running looks like so
#Query("select f.gameId, count(f) as usercount from FollowedEntity f group by f.games.gameId order by usercount desc")
List<GameEntity> findMostFollowed(Pageable pageable);
I have tested the query on my database itself, and it seems to be working fine. However my application returns an error as such:
org.postgresql.util.PSQLException: ERROR: column "gameentity1_.game_id" must appear in the GROUP BY clause or be used in an aggregate function
Any help would be appreciated.
It looks like you have to use join in your query like
#Query(value = "SELECT g.gameId, COUNT(g) as usercount FROM FollowedEntity f JOIN f.games g GROUP By g.gameId ORDER BY usercount DESC")
List<GameEntity> findMostFollowed(Pageable pageable);
You try to map a pair (game_id, count) to the whole GameEntity that has a different structure, that's why sql query that is generated is not what you expect.
What can probably help you is mapping your query result to DTO.
Every time when you have an issue like this I would recommend to have a look at SQL query that JPA is generating. See, e.g., https://www.baeldung.com/sql-logging-spring-boot

JPA Criteria multiselect with fetch

I have following model:
#Entity
#Table(name = "SAMPLE_TABLE")
#Audited
public class SampleModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "SHORT_NAME", nullable = true)
private String shortName;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
Now I would like to query only columns: id, name, shortName and mentor which referes to User entity (not complete entity, because it has many other properties and I would like to have best performance).
When I write query:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
I have following exception:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
Query before exception:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
I know that I can replace fetch with join but then I will have N+1 problem. Also I do not have back reference from User to SampleModel and I do not want to have..
I ran into this same issue, and found that I was able to work around it by using:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
instead of
CriteriaQuery<X> crit = builder.createQuery(X.class);
A little extra work has to be done to produce the end result, e.g. in your case:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
It's been a long time since the question was asked. But I wish some other guys would benefit from my solution:
The trick is to use subquery.
Let's assume you have Applicant in your Application entity (one-to-one):
#Entity
public class Application {
private long id;
private Date date;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
This code will generate a select statement for Application, and select statements for Applicant per each Application.
Note that you have to define an appropriate constructor corresponding to your multiselect.
I got the same problem using EclipseLink as the JPA provider : I just wanted to return the id of a mapped entity («User» in Gazeciarz's example).
This can be achieved quite simply by replacing (in the query.multiselect clause)
root.get(SampleModel_.mentor)
with something like
root.get(SampleModel_.mentor).get(User_.id)
Then, instead of returning all the fields of User, the request will only return the its id.
I also used a tuple query but, in my case, it was because my query was returning fileds from more than one entity.

#Where clause does not work inside hibernate join query

I have 2 entities with #Where annotation. First one is Category;
#Where(clause = "DELETED = '0'")
public class Category extends AbstractEntity
and it has the following relation;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
private Set<SubCategory> subCategories = Sets.newHashSet();
and second entity is SubCategory;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
and contains corresponding relation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
Whenever I call the following Dao method;
#Query(value = "select distinct category from Category category join fetch category.subCategories subcategories")
public List<Category> findAllCategories();
I got the following sql query;
select
distinct category0_.id as id1_3_0_,
subcategor1_.id as id1_16_1_,
category0_.create_time as create2_3_0_,
category0_.create_user as create3_3_0_,
category0_.create_userip as create4_3_0_,
category0_.deleted as deleted5_3_0_,
category0_.update_time as update6_3_0_,
category0_.update_user as update7_3_0_,
category0_.update_userip as update8_3_0_,
category0_.version as version9_3_0_,
category0_.name as name10_3_0_,
subcategor1_.create_time as create2_16_1_,
subcategor1_.create_user as create3_16_1_,
subcategor1_.create_userip as create4_16_1_,
subcategor1_.deleted as deleted5_16_1_,
subcategor1_.update_time as update6_16_1_,
subcategor1_.update_user as update7_16_1_,
subcategor1_.update_userip as update8_16_1_,
subcategor1_.version as version9_16_1_,
subcategor1_.category_id as categor11_16_1_,
subcategor1_.name as name10_16_1_,
subcategor1_.category_id as categor11_3_0__,
subcategor1_.id as id1_16_0__
from
PUBLIC.t_category category0_
inner join
PUBLIC.t_sub_category subcategor1_
on category0_.id=subcategor1_.category_id
where
(
category0_.DELETED = '0'
)
Could you please tell me why the above query lacks
and subcategor1_.DELETED = '0'
inside its where block?
I have just solved a similar problem in my project.
It is possible to put #Where annotation not only on Entity, but on also on your child collection.
According to the javadoc:
Where clause to add to the element Entity or target entity of a collection
In your case, it would be like :
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
#Where(clause = "DELETED = '0'")
private Set<SubCategory> subCategories = Sets.newHashSet();
Please find a similar issues resolved here
I believe thus solution is not as invasive compared to using Hibernate Filters.These filters are disabled by default and operate on Session level, thus enabling them each time new Session opens is extra work especially when your DAO works through abstractions like Spring Data
This is a quick reply;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
Where will effect when direct query for SubCategry.
To not get deleted sub categories use Hibernate Filters
as exampled on here

Joining tables without relation using JPA criteria

I have two tables with no modeled relation:
Table comm with columns:
name
date
code
Table persondesc with columns:
code
description
Relationship between the two tables is many to one (many comm to one persondesc):
com.code = persondesc.code
These two tables are mapped with annotations but I have no relation declared.
What I'm trying to is to select comm table ordered by persondesc.description.
How can I do this JPA and Hibernate?
So if your classes have no "relation", then you do a query like
SELECT a FROM A a
CROSS JOIN B b
WHERE a.someField = b.otherField
ORDER BY b.anotherField
Which can be achieved using JPA Criteria, something like
CriteriaBuilder cb = emf.getCriteriaBuilder();
CriteriaQuery<A> query = cb.createQuery(A.class);
Root<A> aRoot = query.from(A.class);
Root<B> bRoot = query.from(B.class);
aRoot.alias("a");
bRoot.alias("b");
query.select(aRoot)
.where(cb.equal(aRoot.get(A_.someField), bRoot.get(B_.otherField))
.orderBy(cb.asc(bRoot.get(B_.anotherField)));
... Or just redesign your classes and do your developers a favour.
Hibernate 5.1 introduced explicit joins on unrelated entities for JPQL. So now you can just write a JOIN like native SQL:
List<Comm> results = entityManager
.createQuery("""SELECT c FROM Comm c
JOIN PersonDesc pd ON c.code = pd.code
ORDER BY pd.description""", Comm.class)
.getResultList();
Click here for more detailed example.
In case you need to sort by column which is in another table, you can create "fake" dependency with disabled insertable and updatable attributes. Domain model would looks like this:
Primary entity:
#Entity
public class Comm {
#Id
private Long id;
#Column(name = "name")
private String name;
#Column(name = "date")
private Date date;
#Column(name = "code")
private String code;
#OneToOne(fetch = FetchType.LAZY) // #ManyToOne is also possible
#JoinColumn(name = "code", referencedColumnName = "code", insertable = false, updatable = false)
private PersonDesc personDesc;
}
Entity with required data for sorting:
#Entity
public class PersonDesc {
#Id
private String code;
#Column(name = "description")
private String description;
}
After you define your domain model, it possible to create criteria query:
CriteriaBuilder cb = emf.getCriteriaBuilder();
CriteriaQuery<Comm> cq = cb.createQuery(Comm.class);
Root<Comm> root = cq.from(Comm.class);
Join<Comm, PersonDesc> leftJoin = root.join("personDesc", JoinType.LEFT);
cq.select(root);
cq.orderBy(cb.asc(root.get("personDesc.description")));
One of the simplest solution is to create view.
Then create an Entity class for that view and execute query against view.
View:
create or replace view view_comm_persondesc as select c.name, c.date, c.code, p.description from comm c inner join persondesc p on c.code = p.code;
Code
#Entity(name = "view_comm_persondesc")
public class ViewCommPerson{
#Id
private String code;
private String name;
private Date date;
private String description;
... All Getters/Setters ...
}
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ViewCommPerson> query = cb.createQuery(ViewCommPerson.class);
Root<ViewCommPerson> root = query.from(ViewCommPerson.class);
// You can add your filter here
List<ViewCommPerson> result = entityManager.createQuery(query).getResultList();
Hope it servers your use case.

Categories