I currently set my entities in Hibernate using OnetoMany joins within my entities. I need to pull InvTran documents nested within my InventoryReceiptsLine Result. My issue is that I don't want to add data to these tables this way and rather setup a custom query to pull this type of report. My goal is to get the following output where the invTran populates if there is an existing order assignment to InventoryReceiptLine.
{
"receipt_number": 5000027,
"items": [
{
"receipt_number": 5000027,
"inv_mast_uid": 22428,
"qty_received": 100,
"unit_of_measure": "EA",
"item_id": "TRI620-104-706",
"item_desc": "HSS104 CLAMP 4-8/64\"-7\"",
"invTran": []
},
{
"receipt_number": 5000027,
"inv_mast_uid": 13628,
"qty_received": 200,
"unit_of_measure": "EA",
"item_id": "DIXHSS72",
"item_desc": "ALL STAINLESS WORMGEAR CLAMPS",
"invTran": []
},
{
"receipt_number": 5000027,
"inv_mast_uid": 22412,
"qty_received": 100,
"unit_of_measure": "EA",
"item_id": "TRI620-008-706",
"item_desc": "620-008 CLAMP (HAS 8)",
"invTran": [
{
"transaction_number": 4245,
"document_no": 1000064,
"qty_allocated": 51,
"unit_of_measure": "EA",
"inv_mast_uid": 22412,
"oe": null,
"receipt_id": 5000027
}
]
}
]
}
I tried setting up my repository as follows to eliminate the need for the OneToMany join on the InvTran object but I get the following error
"Cannot read field "value" because "s1" is null [SELECT IHDR FROM com.emrsinc.patch.models.InventoryReceiptsHDR IHDR left join fetch InventoryReceiptsLine IRL on IRL.receipt_number = IHDR.receipt_number left join fetch InvTran on InvTran.sub_document_no = IRL.receipt_number WHERE IHDR.receipt_number = :receipt]"
when building by query as
#Query(value = "SELECT IHDR " +
"FROM InventoryReceiptsHDR IHDR " +
"left join fetch InventoryReceiptsLine IRL on IRL.receipt_number = IHDR.receipt_number " +
"left join fetch InvTran on InvTran.sub_document_no = IRL.receipt_number " +
"WHERE IHDR.receipt_number = :receipt")
InventoryReceiptsHDR getReceipt(#Param("receipt") int receipt);
Inventory ReceiptsHDR class
#Entity
#Getter
#Table(name = "inventory_receipts_hdr")
public class InventoryReceiptsHDR implements Serializable {
#Id
private Integer receipt_number;
#OneToMany
#JoinColumn(name = "receipt_number")
#JsonProperty("items")
private Set<InventoryReceiptsLine> inventoryReceiptsLineList;
}
InventoryReceiptsLine class
#Entity
#Getter
#Table(name = "inventory_receipts_line")
public class InventoryReceiptsLine implements Serializable {
#Id
#JsonBackReference
private Long line_number;
private Long receipt_number;
private Long inv_mast_uid;
private Long qty_received;
private String unit_of_measure;
#OneToOne
#JoinColumn(name = "inv_mast_uid", insertable = false, updatable = false)
#JsonUnwrapped
private InvMast invMast;
#OneToMany
#JoinColumns({
#JoinColumn(name = "inv_mast_uid", referencedColumnName = "inv_mast_uid"),
#JoinColumn(name="sub_document_no", referencedColumnName = "receipt_number")
})
private Set<InvTran> invTran;
}
InvTran class
#Entity
#Getter
#Table(name = "inv_tran")
public class InvTran implements Serializable {
#Id
private Integer transaction_number;
private Integer document_no;
private Integer qty_allocated;
private String unit_of_measure;
#JsonProperty(value = "receipt_id")
private Integer sub_document_no;
private Integer inv_mast_uid;
#OneToOne()
#JoinColumn(name = "transaction_number", referencedColumnName = "order_no")
private OeHDR oe;
}
What you want here is an ad-hoc join and you can't fetch entities this way, you need a DTO.
I think this is a perfect use case for 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(InventoryReceiptsHDR.class)
public interface InventoryReceiptsHDRDto {
#IdMapping
Integer getId();
Set<InventoryReceiptsLineDto> getInventoryReceiptsLineList();
#EntityView(InventoryReceiptsLine.class)
interface InventoryReceiptsLineDto {
#IdMapping("line_number")
Long getId();
Long getReceipt_number();
#Mapping("InvTran[sub_document_no = VIEW(receipt_number)]")
Set<InvTranDto> getInvTran();
}
#EntityView(InvTran.class)
interface InvTranDto {
#IdMapping("transaction_number")
Integer getId();
Integer getDocument_no();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
InventoryReceiptsHDRDto a = entityViewManager.find(entityManager, InventoryReceiptsHDRDto.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
Page<InventoryReceiptsHDRDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
Related
I have two (Hibernate-based) Spring Data JPA domain classes, the "One" side Customer.class:
#Entity
#Table(name = "sys_customer")
#Data
public class Customer implements Serializable {
#Id
#Column(name = "cust_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "cust_name")
private String customerName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
private Set<Order> orders;
}
and the "Many" side Order.class:
#Entity
#Table(name = "sys_order")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Order implements Serializable {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_name")
private String orderName;
#ManyToOne
#JoinColumn(name = "order_cust_id", referencedColumnName = "cust_id")
private Customer customer;
public Order( String orderName) {
this.orderName = orderName;
}
public Order(String orderName, Customer customer) {
this.orderName = orderName;
this.customer = customer;
}
}
I have OrderRepository interface which extends JpaRepository interface and JpaSpecificationExecutor interface:
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
}
I have a OrderSpecification.class with the static method searchByCustomerName:
public class OrderSpecification {
public static Specification<Order> searchByCustomerName(String customerName) {
return new Specification<Order>() {
#Override
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Join<Order, Customer> join = root.join("customer");
return criteriaBuilder.like(join.get("customerName"), "%" + customerName + "%");
//return criteriaBuilder.like(root.get("customer").get("customerName"), "%" + customerName + "%");
}
};
}
}
To find the differences between get("property") chain and Join, I wrote a simple test method and comment out the above OrderSpecificatin.class code
#Test
#Transactional
public void testFindOrderByCustomerName(){
String name = "adam";
List<Order> orders = orderRepository.findAll(OrderSpecification.searchByCustomerName(name));
for(Order order: orders){
Customer customer = order.getCustomer();
log.info(new StringBuilder().append(customer.getId()).append(" ").append(customer.getCustomerName()).toString());
}
}
I found that:
get("property") chain use a cross-join(which is very bad performancing) while Join use inner-join(since ManyToOne() by default is Fetch= FetchType.EAGER)
/* get("property") chain: Hibernate: select order0_.order_id as
order_id1_1_, order0_.order_cust_id as order_cu3_1_,
order0_.order_name as order_na2_1_ from sys_order order0_ cross join
sys_customer customer1_ where order0_.order_cust_id=customer1_.cust_id
and (customer1_.cust_name like ?) Hibernate: select customer0_.cust_id
as cust_id1_0_0_, customer0_.cust_name as cust_nam2_0_0_ from
sys_customer customer0_ where customer0_.cust_id=? */
/** * "Join": * Hibernate: select order0_.order_id as order_id1_1_,
order0_.order_cust_id as order_cu3_1_, order0_.order_name as
order_na2_1_ from sys_order order0_ inner join sys_customer customer1_
on order0_.order_cust_id=customer1_.cust_id where customer1_.cust_name
like ? * Hibernate: select customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_name as cust_nam2_0_0_ from sys_customer customer0_
where customer0_.cust_id=? */
My questions are:
Can I specify the Join type(inner, all three outers) or Fetch Type(LAZY, EAGER) when using get("property") chain approach to avoid cross-join?
What scenario/best practice should I use get("chain") or always stay in Join?
Does the approach OrderSpecification.class with static method obey a good OOP design pattern?
You can't specify the join type for paths. It will use INNER join semantics by default and that is mandated by the JPA specification. If you want a different join type, you will have to create joins explicitly. The fact that using get renders as cross joins is a limitation of the old query model of Hibernate, but Hibernate 6.0 will fix this. The semantics are the same though and the query planner of your database should be able to treat both queries the same way. Maybe you just need to update your database version?
There is no "best practice" i.e. this really depends on your needs. Explicit joins are just that, explicit. So multiple calls to join will create multiple joins in SQL.
As for the OOP question, I think this is fine, yes.
I've got Cartesian when run #Query with JOIN FETCH
I have two simple Entities: Parent and Child joining by field 'parent_id'.
Parent has #ElementCollection field witch supposed to contain all child's names (see code).
The SpringData repository contains just simple findAll method with #Query which I'm trying to fetch Parents with Child together.
When I use just plain findAll() method, it returns expected amount of the Parents, each of them with collection of child_names in it as expected. But it produces SQL query by the each parent.
When I'm trying to optimize SQL queries, I'm adding the #Query with "JOIN FETCH" predicate , it produces only one query as expected, BUT instead of one parent it returns parent for each child entry.
Could you please give me any idea what is wrong with my #Query, why it returns Cartesian instead of just join two entries and return only one Parent ?
#Entity
#Table(name = "PARENT")
public class Parent {
#Id #Column(name = "parent_id") private String parentId;
#Column(name = "parent_name") private String parentName;
#ElementCollection (fetch = FetchType.LAZY)
#CollectionTable(name = "CHILD",
joinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
)
#Column(name = "child_name")
private List<String> childNames;
#Entity
#Table(name = "CHILD")
public class Child {
#EmbeddedId private ChildKey key;
#Column(name = "child_name") private String childName;
... getters-setters are omitted ...
}
#Repository
public interface ParentRepo extends JpaRepository<Parent, String> {
#Query("from Parent s JOIN FETCH s.childNames names")
List<Parent> findAll();
}
#RestController
#RequestMapping("/api")
public class MyController {
#Autowired private ParentRepo repo;
#GetMapping("/findAll")
List<Parent> findAll() {
return repo.findAll();
}
}
I expect the output when configure JPA repository with #Query with JOIN FETCH:
[ {
"parentId": "000001",
"parentName": "parent1",
"childNames": ["child1.1","child1.2","child1.3"]
} ]
But the actual result is:
[ {
"parentId": "000001",
"parentName": "parent1",
"childNames": ["child1.1","child1.2","child1.3"]
},
{
"parentId": "000001",
"parentName": "parent1",
"childNames": ["child1.1","child1.2","child1.3"]
},
{
"parentId": "000001",
"parentName": "parent1",
"childNames": ["child1.1","child1.2","child1.3"]
}
]
Have a "full Entity" class:
#Entity(name = "vacancy_dec_to_words")
public class VacancyDescriptionToWords {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "vacancy_description_id")
#ManyToOne(cascade = CascadeType.ALL)
private VacancyDescription vacancyDescription;
#JoinColumn(name = "words_id")
#ManyToOne
private Words words;
#Column(name = "qty")
private int qty;
#Column(name = "create_date")
private Date date;
//...getters and setters
In some methods I need use only 2 column from this database: word_id and qty
I try the follow way:
Projections
https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#projections
public interface QtyWords {
Long getWords();
Integer getQty();
}
JpaReposytory:
*Query, that I use tested and it workable, I use him in JpaRepository:
#Repository
public interface SmallVDTWRepository extends JpaRepository<VacancyDescriptionToWords, Long> {
#Query(nativeQuery = true,
value = "SELECT sum(qty), words_id FROM vacancy_desc_to_words WHERE vacancy_description_id IN (" +
"SELECT id FROM vacancy_description WHERE vacancy_id IN (" +
"SELECT id FROM vacancy WHERE explorer_id = :exp))" +
"GROUP BY words_id")
List<QtyWords> getDistinctWordsByExplorer(#Param("exp") long exp);
}
But I get some interesting result when I get list of entities:
List<QtyWords> list = vdtwService.getByExplorerId(72);
I am not get any exceptions, but I have the list with are unknowns objects. This objects contains my data, which I need(qty and words_id), but I cannot get them from him.
Can I use this method (Projection) to implement this task and, in general, how to correctly implement the 'Light Entity' in this case?
Spring provides two mechanisms that can be used to limit data to be fetched.
Projections
Projections can help you to reduce data, retrieved from database, by setting what exactly attributes you want to fetch.
Example:
#Entity
class Person {
#Id UUID id;
String firstname, lastname;
#OneToOne
Address address;
}
#Entity
static class Address {
#Id UUID id;
String zipCode, city, street;
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
#Repository
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
Entity graph
Annotation EntityGraph can help you to reduce amount of queries to database, by setting what exactly related entities you need to fetch.
Example:
#Entity
#NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = #NamedAttributeNode("members"))
public class GroupInfo {
#Id UUID id;
#ManyToMany //default fetch mode is lazy.
List<GroupMember> members = new ArrayList<GroupMember>();
}
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name); //Despite of GroupInfo.members has FetchType = LAZY, it will be fetched because of using EntityGraph
}
There are two types of EntityGraph:
EntityGraphType.LOAD - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
EntityGraphType.FETCH - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY.
PS: Also remember that you can set lazy fetch type: #ManyToOne(fetch = FetchType.LAZY) and JPA will not fetching child entities when parent is being fetched.
I have 2 entities CallRecords and CallRecordOperators with one-to-many relation as given below
public class CallRecords {
#Id
#Column(name = "id", unique = true)
private String id;
#Column(columnDefinition = "varchar(255) default ''")
private String callerNumber = "";
#OneToMany(mappedBy="callrecord")
private List<CallRecordOperators> callRecordOperators = new ArrayList<CallRecordOperators>();
//getter setters
}
public class CallRecordOperators {
#Id
#Column(name = "id", length = 50, unique = true, nullable = false, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "callRecordId")
private CallRecords callrecord;
#ManyToOne
#JoinColumn(name = "operatorId")
private Operator operator;
#Formats.DateTime(pattern = "yyyy-MM-dd HH:mm:yy")
#Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")
private Date startTime = new Date();
#Column(columnDefinition = "varchar(100) default ''")
private String dialStatus;
//getter setter
}
So if the user ask for all "CallRecords" data I also have to give "CallRecordOperators" as they are related.
Current code for Mapper and DTOs
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
public abstract CallRecordOperatorsDto toTarget(CallRecordOperators source);
List<CallRecordsDto> callRecordsToCallRecordsDtos(List<CallRecords> callRecords);
}
public class CallRecordsDto {
private String callerNumber;
private List<CallRecordOperatorsDto> operators;
//getter setters
}
public class CallRecordOperatorsDto {
private String callRecordsId;
private String operatorId;
private String operatorName;
private String currentTime;
// getter setter
}
But for above code I am getting
{
"callerNumber": "9898989898",
"operators": [{
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}, {
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}]
}
the values of operator array are null. what could be he issue?
It seems your are lacking the mappings from CallRecordOperators to CallRecordOperatorsDto:
#Mapper
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
#Mapping(target = "callRecordsId", source = "callrecord.id")
#Mapping(target = "operatorId", source = "operator.id")
#Mapping(target = "operatorName", source = "operator.name")
#Mapping(target = "currentTime", source = "startTime")
CallRecordOperatorsDto callRecordOperatorsToDto(CallRecordOperators source);
}
When you do a Hibernate query of A elements, you can fetch the related B elements of the bs collection using different strategies. Some of them are:
If you use HQL to construct your queries, you can do a JOIN FETCH or LEFT JOIN FETCH to populate the bs collection:
String hql = "SELECT DISTINCT a FROM " + A.class.getName()
+ " a LEFT JOIN FETCH a.bs WHERE ...";
This query will load all data using a single SQL query.
Use eager fetching of the bs collection, changing the #OneToMany annotation:
#OneToMany(fetch=FetchType.EAGER)
private List<B> bs;
In this case, when you run a query of A elements, a SQL query will be launched to retrieve the A data, and for each A object in the result, a SQL query will be executed to load the corresponding bs collection.
If you use Criteria to build the query, you can change the fetch mode of the bs collection in a way similar to the HQL JOIN FETCH:
Criteria c = session.createCriteria(A.class);
c.setFetchMode("bs", FetchMode.JOIN);
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
How about switching to a slightly different approach that also performs better? By using Blaze-Persistence Entity Views you can define your mapping directly on the DTO classes and apply that onto a query builder to generate efficient queries that perfectly fit your DTO structure.
#EntityView(CallRecords.class)
public interface CallRecordsDto {
// The id of the CallRecords entity
#JsonIgnore
#IdMapping("id") String getId();
String getCallerNumber();
#Mapping("callRecordOperators")
List<CallRecordOperatorsDto> getOperators();
}
#EntityView(CallRecordOperators.class)
public interface CallRecordOperatorsDto {
// The id of the CallRecordOperators entity
#JsonIgnore
#IdMapping("id") Long getId();
#Mapping("callrecord.id")
String getCallRecordId();
#Mapping("operator.id")
String getOperatorId();
#Mapping("operator.name")
String getOperatorName();
#Mapping("startTime")
String getCurrentTime();
// Whatever properties you want
}
See how you can map the entity attributes right in your DTOs? And here comes the code for querying
EntityManager entityManager = // jpa entity manager
CriteriaBuilderFactory cbf = // query builder factory from Blaze-Persistence
EntityViewManager evm = // manager that can apply entity views to query builders
CriteriaBuilder<User> builder = cbf.create(entityManager, CallRecords.class)
.where("callerNumber").eq("123456789");
List<CallRecordsDto> result = evm.applySetting(
builder,
EntityViewSetting.create(CallRecordsDto.class)
).getResultList();
Note that this will roughly generate the following optimized query
SELECT
c.id,
c.callerNumber,
o.callrecord.id,
o.id,
o.startTime,
op.id,
op.name
FROM CallRecords c
LEFT JOIN c.callRecordOperators o
LEFT JOIN o.operator op
WHERE c.callerNumber = :param_1
This question already has answers here:
Selectively expand associations in Spring Data Rest response
(2 answers)
Closed 5 years ago.
Is there a way to return the full details of a joined entity instead of a link? In the example below I want to also return the details of the product, if I have list of 100 purchases, it would avoid having to make 100 calls to get the product details.
The repositories for Product, User and Purchase entities are all created using spring-data-jpa
{
"_embedded" : {
"purchase" : [ {
"_links" : {
"product" : {
"href" : "http://localhost:8080/webapp/purchase/1/product"
},
"user" : {
"href" : "http://localhost:8080/webapp/purchase/1/user"
}
},
"purchasedOn" : "2014-02-23",
"amount" : 1
} ]
}
}
Entities and Repositories;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Purchase.class, orphanRemoval = true)
#JoinColumn(name = "user_id", updatable = false)
private List<Purchase> purchases = new ArrayList<>();
}
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
#Entity
public class Purchase implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = Product.class)
#JoinColumn(name = "product_id", referencedColumnName = "id")
private Product product;
#Column(name = "purchase_date")
private Date purchaseDate;
private Integer amount;
}
#Repository
public interface PurchaseRepository extends JpaRepository<Purchase, Long> {}
It would seems there is already a New Feature request for this functionality;
https://jira.springsource.org/browse/DATAREST-221
https://jira.springsource.org/browse/DATAREST-243
I'll leave the question open until the feature has been implemented.
Yes.
You can use something like a TypedQuery.
So you would create a Plain Old Java Object (POJO) which holds all the columns (fields) you need. You then use a TypeQuery to get all the fields from the database and return in a custom result set (i.e. your POJO or a collection of your POJO).
Here is a an example:
TypedQuery<BookExport> q = (TypedQuery<SOME_POJO>) entityManager.createQuery(
"select new SOME_PJOP(a.field1, a.field2, a.field2) " +
"from SOME_ENTITY AS a",
SOME_POJO.class);
UPDATE
It looks like you have related entities already. Assuming you are using "Eager Loading", all you need to do is perform a simple query in your repository implementation. Eager Loading means JPA will automatically select the related attributes.If you are using Jersey to serialize the result in JSON, it by default, will serialize of the fields of the result set.
Here is an example from a JPA project I recently completed. The scheme is simple. I have a "Book" entity. Each "Book" has a related "Author". Each "Author" has a first and last name. If you select a "Book" the JPA query also select the Author's first and last name. I believe that is analogous to what you are trying to accomplish:
#Override
#Transactional
public Object findOne(Serializable id) {
Query q = entityManager.createQuery("select a from Book a where a.id = ?1");
q.setParameter(1, id);
return q.getSingleResult();
}
The input id is simply the Book key (a number like 105).
Hope that helps :)
You can write projecton to get the details.
For example PurchaseProjection can be written as.
#Projection(name="purchaseProduction",types={Purchase.class})
interface PurchaseProjection{
public User getUser();
public Product getProduct();
public Long getAmount();
}
You can access the result using
http://localhost:8080/webapp/purchase?projection=purchaseProduction