Hibernate query by example - java

I have class A that has some primitive attributes and also member of type B.
type B has a map:
// mapping name to number
private Map<String, Double> myMap = null;
#ElementCollection(fetch=FetchType.EAGER)
#MapKeyColumn(name = "NAME")
#Column(name = "NUMBER")
#CollectionTable(name = "NAME_MAPPING", uniqueConstraints = { #UniqueConstraint(columnNames = { "NAME", "NUMBER" }) })
public Map<String, Double> getMyMap()
{
return this.myMap;
}
Snippet of A:
private String name = null;
private B b = null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "B_FK")
public B getB()
{
return b;
}
Now I want to find A by Example. I defined the following:
public List<A> findByExample(A a)
{
Session session = getSession();
Criteria criteria = session.createCriteria(A.class);
Example example = Example.create(a);
Criteria bCriteria = criteria.createCriteria("b");
B b = material.getB();
bCriteria.add(Example.create(b));
criteria = criteria.add(example);
criteria = criteria.setFetchMode("b", FetchMode.JOIN);
return criteria.list();
}
I tried all kinds of variations but with no success. the method returns all DB entries with the same A.name and ignore the equality of the Map in B.
any clue on what am I doing wrong?
Thanks,
Ronen.

The Example restrictions ignore associations (and, although this is not documents, element collections). Even if you used just the Criteria API (without using Example), element collections can't be queries with Criteria (se https://hibernate.onjira.com/browse/HHH-869). You'll have to revert to HQL or SQL restrictions.

Related

DTO Query with JPA CriteriaQuery and Hibernate using entity as a DTO constructor creates many selects

hibernate 5.2.10.Final
jpa 2.1
I want to map a projection query to a DTO (Data Transfer Object) with JPA Criteria Query and Hibernate. I specify a constructor that will be applied to the results of the query execution.
If the constructor is for entire entity class, I have multiple of selects instead one(it is a long running process for thousands of records). If the constructor is for a set of params of the Entity then I see only one select in the console. I can't understand where I've mistaken or is it a bug?
public class ServiceDAO {
public List<ServicesDTO> getAllServicesByFilter(ServicesFilter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ServicesDTO> criteria = cb.createQuery(ServicesDTO.class);
Root<ServicesEntity> serviceEntity = criteria.from(ServicesEntity.class);
// here is only one select to get list of services
criteria.select(cb.construct(ServicesDTO.class, serviceEntity.get("active"), serviceEntity.get("providerId"), serviceEntity.get("serviceId")));
// in this case I have multiple selects
//criteria.select(cb.construct(ServicesDTO.class, serviceEntity));
if(filter != null) {
List<Predicate> pcl = new ArrayList<Predicate>();
if(filter.getActive() != null)
pcl.add(cb.equal(serviceEntity.get("active"), filter.getActive()));
if(filter.getProviderId() != null)
pcl.add(cb.equal(serviceEntity.get("providerId"), filter.getProviderId()));
if(filter.getServiceId() != null)
pcl.add(cb.equal(serviceEntity.get("serviceId"), filter.getServiceId()));
criteria.where(pcl.toArray(new Predicate[pcl.size()]));
}
return entityManager.createQuery(criteria).getResultList();
}
}
-
public class ServicesDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Boolean active;
private Integer providerId;
private Integer serviceId;
public ServicesDTO() {}
public ServicesDTO(Boolean active, String providerId, Integer serviceId) {
this.active = active;
this.providerId = Integer.parseInt(providerId);
this.serviceId = serviceId;
}
public ServicesDTO(ServicesEntity service) {
if(service != null) {
this.active = service.isActive();
this.providerId = Integer.parseInt(service.getProviderId());
this.serviceId = service.getServiceId();
}
// getters & setters
}
-
#Entity
#Table
public class ServicesEntity {
#Id
#Column(name = "id", unique = true)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "serviceId", nullable = false)
private int serviceId;
#Column(nullable = false)
private String providerId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="categoryId")
private Categories categoryId;
private boolean active;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "service", cascade = CascadeType.ALL)
private List<Service_Area_Ref> areas = new ArrayList<Service_Area_Ref>();
#ManyToOne(fetch=FetchType.LAZY, optional = true)
#JoinColumn(name="parentCatId")
private Categories parentCatId;
public ServicesEntity() {}
public ServicesEntity(int serviceId) {
this.serviceId = serviceId;
}
// getters & setters
// equals & hashcode
}
Yea, so it does. There is probably not much of a use case for that. Given
#Entity
public class A {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Integer value;
public class ADto {
private Integer va;
public ADto(A a) {
this.va = a.getValue();
}
public ADto(Integer va) {
this.va = va;
}
Then
tx.begin();
A a1 = new A();
a1.setValue(1);
A a2 = new A();
a1.setValue(2);
em.persist(a1);
em.persist(a2);
tx.commit();
em.clear();
System.out.println("As usual");
em.createQuery("select new dto.ADto(a.value) from A a where a.value <= 2", ADto.class).getResultList();
System.out.println("As A");
em.createQuery("select new dto.ADto(a) from A a where a.value <= 2", ADto.class).getResultList();
gives you
create table A (id integer generated by default as identity (start with 1), value integer, primary key (id))
create table B (id integer generated by default as identity (start with 1), value integer, primary key (id))
insert into A (id, value) values (default, ?)
insert into A (id, value) values (default, ?)
As usual
select a0_.value as col_0_0_ from A a0_ where a0_.value<=2
As A
select a0_.id as col_0_0_ from A a0_ where a0_.value<=2
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
And you don't like the fact that entity A is selected each time for a new ADto instance. It's probably done that way because you could have created a DTO with multiple entities, not just A, like A, B, and C and so how would JPA/Hibernate do that conveniently in a single select statement? While it could select all the attributes and then keep track of which attributes belong to which entities and then construct them and pass them to your DTO so you can deconstruct them that seems like a lot of work for a rare thing. It's probably more efficient and better all around if you select the attributes you want and make a constructor out of whatever that is, as in the first case.
I am using Hibernate 5.3 and also encounter this behaviour. But I found that if using JPA Tuple as a DTO container and multiselect, this problem will not happen. So my final solution is use Tuple to query the result set first and then convert it to DTO manually , something likes:
CriteriaQuery<Tuple> criteria = cb.createTupleQuery();
.......
criteria.multiselect(serviceEntity);
List<ServicesDTO> result = entityManager.createQuery(criteria).getResultList().stream()
.map(t->new ServicesDTO(t.get(0,ServicesEntity.class)))
.collect(toList());

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

Hibernate criteria join table issue

I have 3 entities as you can see below. I want to write a query that fetches products. In this query the parameter is a list of optionValues id.
now my question is how to join these entities?
Product:
public class Product{
//other col
#OneToMany(mappedBy = "product")
private Set<Attribute> attributeSet = new HashSet<>();
}
Attribute:
public class Attribute{
#OneToOne
#JoinColumn(name = "OPTION_VALUE_ID")
private OptionValue optionValue;
#ManyToOne
#JoinColumn(name="PRODUCT_ID",referencedColumnName="id")
private Product product;
}
optionValue:
public class OptionValue{
#Column(name = "id")
private Long id;
#Column(name = "value",updatable = true)
private String value;
}
I wrote a query but I think my code is not a good solution.
Criteria aCriteria = null;
if (!optionValueList.isEmpty()) {
aCriteria = currentSession().createCriteria(Attribute.class, "attribute");
aCriteria.createAlias("attribute.optionValue", "optionValue");
aCriteria.add(Restrictions.in("optionValue.id", optionValueList));
attributes = aCriteria.list();
}
PagingData<Product> pagingData = new PagingData<>();
Criteria criteria = currentSession().createCriteria(Product.class, "product");
if (!attributes.isEmpty()) {
for (Attribute attribute:attributes){
longList.add(attribute.getId());
}
criteria.createAlias("product.attributeSet", "attribute");
criteria.add(Restrictions.in("attribute.id", longList));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
The general idea is to start with creating criteria of objects you want to return, and travel further by adding criteria which is joined. So I start with Parent class, add qualifiers and end up with most nested element, OptionValue.
Code below is untested, but you should get the idea:
Criteria criteria = currentSession()
.createCriteria(Product.class)
.createCriteria("attributeSet", "join_between_product_and_attribute");
if (!attributes.isEmpty()) {
Set<String> attributeIds = new HashSet<>();
for (Attribute attribute : attributeList) {
attributeIds.add(attribute.getId());
}
criteria.add(Restrictions.in("id", attributeIds));
}
criteria = criteria.createCriteria("optionValue", "join_between_attribute_optionvalue");
if (!optionValueList.isEmpty()) {
criteria.add(Restrictions.in("id", optionValueList));
}
an even easier solution would be to use a CriteriaQuery. i did not test the following code, but i think it should work correctly. it requires hibernate 5, but also works with some modifications in hibernate 4:
CriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return sessionFactory.getCurrentSession().createQuery(query).getResultList();
i am assuming, that you can access the optionValueList since you posted it in your question.
For the solution with EntityManager i am assuming you already were able to instantiate one.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return entityManager.createQuery(query).getResultList();
if you have an EntityManagerFactory, replace the first entityManager with it and the second one with entityManagerFactory.createEntityManager()

HQL Query want's to return Map<OneColumnValue,BeanClassObject>? [duplicate]

i have a table
Permission:
id
name
desc
what i am doing right now
is to make a query that returns a permission object then put the values in the map programmatically
1- But i was wondering if it's possible to make an HQL (or native sql if not possible) to select the permission_id, permission_name and return them in a map.
2- is it possible to return map in one to many relationship instead of following list or set
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map", joinColumns = { #JoinColumn(name = "perm_cat_id") }, inverseJoinColumns = { #JoinColumn(name = "permission_id") })
private List<Permission> permissions = new ArrayList<Permission>(0);
is it possible to have something like:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map", joinColumns = { #JoinColumn(name = "perm_cat_id") }, inverseJoinColumns = { #JoinColumn(name = "permission_id") })
private Map<String,String> permissions = new ArrayList<String,String>(0);
where the two strings are permission_id, permission_name.
Use the select new map syntax in HQL to fetch the results of each row in a Map. Take a look at the following question, that addresses the issue: How to fetch hibernate query result as associative array of list or hashmap.
For instance, the following HQL: select new map(perm.id as pid, perm.name as pname) from Permission perm will return a List of Maps, each one with keys "pid" and "pname".
It is not possible to map an association to a Map<String, String>. It is possible to map the key of the Map to a column with the #MapKeyColumn annotation in the association. See this question, that also addresses the issue, for an example: JPA 2.0 Hibernate #OneToMany + #MapKeyJoinColumn. Here is another example.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map",
joinColumns = { #JoinColumn(name = "perm_cat_id") },
inverseJoinColumns = { #JoinColumn(name = "permission_id") })
#MapKeyColumn(name="permission_id")
private Map<String, Permission> permissions = new HashMap<String,Permission>(0);
try like this,
Session session = sessionFactory.getCurrentSession();
String HQL_QUERY = "select new map(user.id as id, user.firstName as fullName) from User user";
List<Map<String,String>> usersList = session.createQuery(HQL_QUERY).list();
1- But i was wondering if it's possible to make an HQL (or native sql
if not possible) to select the permission_id, permission_name and
return them in a map.
its posible with Resulttransformer
String queryString="select id, name from Permission ";
List<List<Object>> permission= session.createQuery(queryString)
.setResultTransformer(Transformers.TO_LIST).list();
//now you just expect two columns
HashMap<Integer,String> map= new HashMap<Integer,String>();
for(List<Object> x: permission){
map.put((Integer)x.get(0),(String)x.get(1))
}
String sqlQuery="select userId,name,dob from user"
Pass the query to following method.
public List<Map<String,Object>> getDataListBySQL(final String sql, final Long adId){
List<Map<String,Object>> list=(List<Map<String,Object>>)getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException,SQLException {
Query query=session.createSQLQuery(sql);
query.setParameter("adId", adId);
return query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
}
});
return list;
}
Iterate this list in this way-
for(int i=0;i<list.size();i++){
Map<String,Object> map=list.get(i);
System.out.println(map.get("userId"));
System.out.println(map.get("name"));
}
In JPA 2.0 (which recent versions of Hibernate support), you can map collections of primitives using an #ElementCollection annotation.
For some samples of such mappings see the hibernate collections docs.
If you're not actually mapping it in this way but want to create a map using either HQL or a Criteria query, you can create a ResultTransformer to create a map from the returned result set.
Judging from Xavi's answer, I guess there is also support in HQL for creating a map without using a transformer.

How to use CriteriaQuery for ElementCollection and CollectionTable

I have a very simple entity Product which has a code, name and tags. Tags are stored in another table (product_tag) with product_id and tag columns.
I need to search for products with certain tags using CriteriaQuery. To give an example I want to find products having 'fruit' and 'red' tags.
Using spring 4.1.x, spring-data-jpa 1.8 and hibernate 4.2.x.
My entity simply is;
#Entity
#Table(name = "product", uniqueConstraints ={
#UniqueConstraint(columnNames = "code")
}
)
#NamedQueries({
#NamedQuery(name = "Product.findAll", query = "select p from Product p")
})
public class Product extends EntityWithId {
#Column(name = "code", length = 128)
private String code;
#Column(name = "name", length = 512)
protected String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="product_tag", joinColumns=#JoinColumn(name="product_id"))
#Column(name="tag")
private Set<String> productTags = new HashSet<>();
}
here is the code how I initiate the search;
private void search() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> criteriaQuery = builder.createQuery(Product.class);
Root<Product> product = criteriaQuery.from(Product.class);
Predicate where = builder.conjunction();
if (!StringUtils.isEmpty(nameSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("name"), nameSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(codeSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("code"), codeSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.get("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
criteriaQuery.where(where);
List<Product> resultList = entityManager.createQuery(criteriaQuery).getResultList();
}
However when I run the search for tags 'fruit' I get an exception
java.lang.IllegalArgumentException: Parameter value [fruit] did not match expected type [java.util.Set (n/a)]
I really wonder to use CriteriaQuery for ElementCollection and CollectionTable.
productTags is mapped to a separate table, therefore you need to join with that table in your query.
...
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.join("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
...
Note the product.join("productTags") instead of product.get("productTags")
Try to use isMember() rather than in()
Check the example 5 and 7

Categories