I'm trying to create a query with Criteria, but I don't succeed to map data from a joined entity.
With this Criteria query the id of the Order entity is override with the id of the ShippingCondition entity :
final Criteria criteria = session.createCriteria(Order.class, "o")
.createAlias("o.shippingCondition", "sc", JoinType.INNER_JOIN)
.setProjection(Projections.projectionList()
.add(Projections.property("o.id"), "id")
.add(Projections.property("o.orderNum"), "orderNum")
.add(Projections.property("o.notes"), "notes")
.add(Projections.property("sc.id"), "id"))
.add(Restrictions.eq("o.id", id))
.setResultTransformer(Transformers.aliasToBean(Order.class));
return (Order) criteria.uniqueResult();
My entities :
#Table(name = "order", schema = "myschema")
public class Order {
private Integer id;
private String orderNum;
private String notes;
private ShippingCondition shippingCondition;
...
}
#Table(name = "shipping_condition", schema = "myschema")
public class ShippingCondition {
private Integer id;
private String shippingCondition;
private Integer sorting;
...
}
I have tryed to replace .add(Projections.property("sc.id"), "id") by .add(Projections.property("sc.id"), "shippingCondition.id") but then I get a ClassCastException (java.lang.ClassCastException: entity.Order cannot be cast to java.util.Map)
Do you have any idea how I can do that ?
Thanks
Hibernate doesn't support nested projections. You will need to create DTO for that. You can extend DTO from Order class and add methods to set fields of ShippingCondition.
class OrderDto extends Order {
public OrderDto() {
setShippingCondition(new ShippingCondition());
}
public void setShippingConditionId(Integer id) {
getShippingCondition().setId(id);
}
}
You can use a special nested transformer if you don't want to use DTO
How to transform a flat result set using Hibernate
Additional notes
JPA doesn't support any transformers at all. And it is hard to implement such transformer by consistent way. For example, my transformer doesn't support child collections like #OneToMany, only single associations. Also, you can't use nested projections with HQL, because HQL doesn't support parent.child aliases.
Related
I have two tables and they maintain the parent-child relationship between them by a foreign key.
The query looks something like below. I want to use the criteriaquery along with jpa. So can anyone help me with the criteriaquery & how the two entity classes would look like
ps:if there is any custom enity class required apart from these two entities classes help me with that as well.
Select parent.notification_id,parent.city,parent.name,parent.accountNo,
case when child.accountNo is not null then 'Yes' else 'No' end as checked
FROM parent
JOIN child ON parent.notification_id=child.notification_id_child
AND child.accountNo='test' WHERE parent.city='delhi' or parent.city='all' or parent.accountNo="test";
The column 'notification_id_child' of table 'child' is the foreign key and refers to the primarykey of table 'parent'.
There are multiple strategies that you can use to implement this:
MappedSuperclass (Parent class will be mapped with this annotation and not entity)
Single Table (Single table for each hierarchy, you can use #DiscriminatorColumn JPA annotation for identifying each hierarchy)
Joined Table (Each class for the parent and child)
In this scenario, you would have to join both the tables on the common column to fetch the results.
These are some good answers on joining tables
Joining two table entities in Spring Data JPA
Link for some good answers on usage of discrimintaorColumn
How to access discriminator column in JPA
Finally, I managed to solve the problem. My entity classes and criteria query looks something like the below.
Parent Entity
#Entity
#Table(name="parent")
public class Parent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="notification_id")
private Long notificationId;
#Column(name="city")
private String city;
#Column(name="name")
private String name;
#Column(name="accountNo")
private String accountNo;
#JoinColumn(name="notification_id_child")
#OneToMany
private List<Child> child;
//Getters Setters
}
Child Entity
#Entity
#Table(name="child")
public class Child{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="accountNo")
private String accountNo;
#Column(name="notification_id_child")
private String notificationIdChild;
//Getters Setters
}
Custom Entity
public class CustomEntity{
private Long notificationId;
private String city;
private String accountNo;
private String checked;
}
Criteria Query
#PersistenceContext
EntitiManager em;
CriteraBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CustomEntity> cq = cb.createQuery(CustomEntity.class);
Root<Parent> parentEntity = cq.from(Parent.class);
Join<Parent,Child> join = parentEntity.join("child", JoinType.LEFT);
join.on(cb.equal(join.get("accountNo"),"test"));
Path<String> notificationIdPath = parentEntity.get("notificationId");
Path<String> cityPath = parentEntity.get("city");
Path<String> accountNoPath = parentEntity.get("accountNo");
cq.multiselect(notificationIdPath, cityPath, accountNoPath,
cb.selectCase().when(join.get("accountNo").isNotNull(),"Yes").otherwise("No"));
Path<String> accountNoPath = parentEntity("accountNo");
Predicate accountNoPredicate = cb.equal(accountNoPath, "test");
Predicate cityPredicateAll = cb.equal(cityPath,"all");
Predicate cityPredicateSpecified = cb.equal(cityPath,"delhi");
cq.where(cb.or(cityPredicateAll, cityPredicateSpecified, accountNoPredicate));
TypedQuery<CustomEntity> query = em.createQuery(cq);
List<CustomEntity> CustomEntityList = query.getResult();
I need to convert to json an Entity with JsonManagedReference and JsonBackReference implementations:
#Entity
#Table(name = "myparenttable", schema = "myschema", catalog = "mydb")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Parent implements Serializable {
private Integer id_parent;
private String name;
#JsonManagedReference
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<Child> children;
//getters and setters
}
#Entity
#Table(name = "mychildtable", schema = "myschema", catalog = "mydb")
public class Child implements Serializable {
private Integer id_child;
private String description;
#JsonBackReference
private Parent parent;
//getters and setters
}
With this setup, the persist function is straightforward, I just perform a
em.persist(parent);
and both entities are inserted into the database; but also I need to convert those entities into json for audit purposes. I get a infinite recursion error when doing this:
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(parent);
Is there a way to do both?
You may want to annotate your parent object into Child class with
#JsonIgnore
private Parent parent;
in this way the reference of the parent object isn't put into the serialized json object.
Check if you realy need to implement Serializable interface
This is a perfect use case for using DTOs with Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Parent.class)
public interface ParentDto {
#IdMapping
Integer getId();
String getName();
List<ChildDto> getChildren();
#EntityView(Child.class)
interface ChildDto {
#IdMapping
Integer getId();
String getDescription();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ParentDto a = entityViewManager.find(entityManager, ParentDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
On top solving your serialization issue, using Blaze-Persistence Entity-Views will also improve performance because it only select the columns that are actually needed.
I've encounterd weird behaviour of JPA (provider: EclipseLink) using order by functionality. I have TransactionData class, which has reference to CustomerData class:
#Entity
public class TransactionData {
//...
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID")
private CustomerData customer;
//...
}
#Entity
public class CustomerData {
//...
#Column(name = "LAST_NAME")
private String lastName;
//...
}
In my project, there are some specific cases, where there are transactions, which are not assigned to any customer (called non-customer transactions).
I try to get list of all registered transactions (both cusotmer transactions and non-customer transactions) and sort them by customer's last name. To acheive this I've written following Criteria Api
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TransactionData> criteriaQuery = criteriaBuilder.createQuery(TransactionData.class);
Root<TransactionData> from = criteriaQuery.from(TransactionData.class);
criteriaQuery.orderBy(criteriaBuilder.asc(from.get("customer").get("lastName"));
TypedQuery<TransactionData> query = entityManager.createQuery(criteriaQuery);
return query.getResultList();
I think I should get list of all transactions, of course, with those, where customer field is set to NULL value. But JPA behaviour is different, because it cut out all transactions, where reference to customer is empty.
from.get("customer").get("lastName") will do implicitly an INNER JOIN. If some transactions have no customer assigned then what you need is a LEFT JOIN:
Join<TransactionData , CustomerData> customer = from.join("customer", JoinType.LEFT);
criteriaQuery.orderBy(criteriaBuilder.asc(customer.get("lastName"));
I am implementing search/filtering service for list of entities, using Spring Data JPA repository with specifications and pagination features. I am trying to reduce number of queries (n+1 problem) and fetch nested data using criteria fetch mechanism.
I have two entity classes:
#Entity
#Table(name = "delegations")
public class Delegation {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#ManyToOne
private Customer customer;
// more fields, getters, setters, business logic...
}
and
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
// more fields, getters, setters, business logic...
}
DTO filter class:
public class DelegationFilter {
private String customerName;
// more filters, getters, setters...
}
And search / filtering service:
public class DelegationService {
public Page<Delegation> findAll(DelegationFilter filter, Pageable page) {
Specifications<Delegation> spec = Specifications.where(
customerLike(filter.getCustomerName())
);
return delegationRepository.findAll(spec, page);
}
public List<Delegation> findAll(DelegationFilter filter) {
Specifications<Delegation> spec = Specifications.where(
customerLike(filter.getCustomerName())
);
return delegationRepository.findAll(spec);
}
private Specification<Delegation> customerLike(String customerName) {
return (root, query, cb) -> {
Join<Delegation,Customer> join = (Join) root.fetch(Delegation_.customer);
return cb.like(cb.lower(join.get(Customer_.name)), addWildCards(customerName.toLowerCase()));
};
}
private static String addWildCards(String param) {
return '%' + param + '%';
}
}
Problem:
When I call findAll(DelegationFilter filter, Pageable page) I am getting exception:
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.QueryException: query specified join fetching, but the owner
of the fetched association was not present in the select list
Is there a way to solve this problem?
findAll(DelegationFilter filter) (method without pagination) works like charm... Using join only (without fetch) also works fine (even with pagination)
I know that there is solution for JPQL:
Spring-Data FETCH JOIN with Paging is not working
But I want to stick with criteria api...
I am using Spring Boot 1.4 (spring 4.3.2, spring-data-jpa 1.10.2) and Hibernate 5.0.9
I was facing the same problem, and I found a workaround (source).
You can check the query's return type at runtime, so that if it is Long (the type the count query returns) you join and otherwise you fetch. In your code it will look like this:
...
private Specification<Delegation> customerLike(String customerName) {
return (root, query, cb) -> {
if (query.getResultType() != Long.class && query.getResultType() != long.class) {
Join<Delegation,Customer> join = (Join) root.fetch(Delegation_.customer);
} else {
Join<Delegation,Customer> join = root.join(Delegation_.customer);
}
return cb.like(cb.lower(join.get(Customer_.name)), addWildCards(customerName.toLowerCase()));
};
}
...
I know it's not very clean, but it's the only solution I've ofund ATM.
I have an entity Ride which embeds an embeddable "entity" Route. Route has a List property towns with ManyToMany relation, so it has fetchtype LAZY (and I don't want to use EAGER). So I want to define an NamedEntityGraph for the entity Ride, to load load a Ride object with a Route with instantied List of towns.
But when I deploy my war, I get this exception:
java.lang.IllegalArgumentException: Attribute [route] is not of managed type
Ride
#Entity
#NamedQueries({
#NamedQuery(name = "Ride.findAll", query = "SELECT m FROM Ride m")})
#NamedEntityGraphs({
#NamedEntityGraph(
name = "rideWithInstanciatedRoute",
attributeNodes = {
#NamedAttributeNode(value = "route", subgraph = "routeWithTowns")
},
subgraphs = {
#NamedSubgraph(
name = "routeWithTowns",
attributeNodes = {
#NamedAttributeNode("towns")
}
)
}
)
})
public class Ride implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Embedded
private Route route;
// some getter and setter
}
Route
#Embeddable
public class Route implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToMany
private List<Town> towns;
// some getter and setter
}
Looking at Hibernate's implementation of org.hibernate.jpa.graph.internal.AttributeNodeImpl lead us to the conclusion that #NamedAttributeNode cannot be:
simple types (Java primitives and their wrappers, strings, enums, temporals, ...)
embeddables (annotated with #Embedded)
element collections (annotated with #ElementCollection)
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED ) {
throw new IllegalArgumentException(
String.format("Attribute [%s] is not of managed type", getAttributeName())
);
}
I didn't find similar restriction in JPA 2.1 spec, therefore it may be Hibernate's shortcoming.
In your particular case the problem is that #NamedEntityGraph refers to the Route class which is an embeddable, thus its usage in the entity graph seems to be forbidden by Hibernate (unfortunately).
In order to make it work you would need to change your entity model a little. A few examples that come into my mind:
define Route as entity
remove Route and move its towns field into Ride entity (simplifies the entity model)
move route field from Ride into Town entity, add map of routedTowns map to Ride entity:
#Entity
public class Ride implements Serializable {
...
#ManyToMany(mappedBy = "rides")
private Map<Route, Town> routedTowns;
...
}
#Entity
public class Town implements Serializable {
...
#ManyToMany
private List<Ride> rides;
#Embeddable
private Route route;
...
}
Of course the entity graph may require changes accordingly.