How to retrieve specific rows from joined second table through JPA - java

I have two tables one is customer and another one is customerDepartment. Customer is having one to many relationship with customerDepartment.
I have a specific search condition where i need to search for the department name, If it equals i need to retrieve all the customer department rows including the customers.
This is what i have tried to get the results
public interface CustomerRepository extends JpaRepository<Customer,Integer>{
#Query(value="select DISTINCT c from Customer c left join c.custDept cd where cd.deptName like %?1% ")
Page<Customer> findByName(String name, Pageable pageable);
}
Customer
#Entity
#Table(name="customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name= "customer_no",updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int customerNo;
#Column(name= "customer_name")
private String customerName;
#Column(name= "industry")
private String industry;
#JsonManagedReference
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL,
fetch=FetchType.LAZY)
private Set<CustomerDepartment> custDept;
}
CustomerDepartment:
#Entity
#Table(name = "customer_department")
public class CustomerDepartment implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "dept_id",updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int depId;
#Column(name = "dept_name")
private String deptName;
#Column(name = "primary_contact")
private String primaryContact;
#JsonBackReference
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
When i searched for DepartmentName=it, The above JPA Query returning the below results
{
"content": [
{
"customerNo": 33,
"customerName": "Test1",
"industry": "High-Tech",
"country": "Australia",
"state": "Sa-Jose",
"city": "Sydney",
"custDept": [
{
"depId": 34,
"deptName": "It",
"primaryContact": "Banglore,Kormangala",
},
{
"depId": 35,
"deptName": "sales",
"primaryContact": "Banglore,Kormangala",
}
]
}
]
}
What i am expecting is more like.
{
"content": [
{
"customerNo": 33,
"customerName": "Test1",
"industry": "High-Tech",
"country": "Australia",
"state": "Sa-Jose",
"city": "Sydney",
"custDept": [
{
"depId": 34,
"deptName": "It",
"primaryContact": "Banglore,Kormangala",
}
]
}
]
}
If this is not possible in JPA, Is there any way i can do this.
Thanks for the help

Yea, I thought so. I commented that "I'm thinking your query is okay but when the result is marshalled to JSON then all of the associated departments are being retrieved. You should look at your sql output and debug and check query results before marshalling to see if that is the case." I went ahead and played around with it and I was more or less correct.
The problem is that you have not fetched the custDept set with your query so when the customer is marshalled for your rest response an additional query is executed to get the values and the additional query just queries everything.
2019-05-25 14:29:35.566 DEBUG 63900 --- [nio-8080-exec-2] org.hibernate.SQL : select distinct customer0_.customer_no as customer1_0_, customer0_.customer_name as customer2_0_, customer0_.industry as industry3_0_ from customer customer0_ left outer join customer_department custdept1_ on customer0_.customer_no=custdept1_.customer_no where custdept1_.dept_name like ? limit ?
2019-05-25 14:29:35.653 DEBUG 63900 --- [nio-8080-exec-2] org.hibernate.SQL : select custdept0_.customer_no as customer4_1_0_, custdept0_.dept_id as dept_id1_1_0_, custdept0_.dept_id as dept_id1_1_1_, custdept0_.customer_no as customer4_1_1_, custdept0_.dept_name as dept_nam2_1_1_, custdept0_.primary_contact as primary_3_1_1_ from customer_department custdept0_ where custdept0_.customer_no=?
If you want only what your query supplies you need to do a fetch so the custDept the set is initialized before being marshalled. There are also other issues with your query. You should use a sql parameter :deptName and you should declare that, and you should supply a countQuery since you are returning a Page.
public interface CustomerRepository extends JpaRepository<Customer,Integer>{
#Query(value="select DISTINCT c from Customer c left join fetch c.custDept cd where cd.deptName like %:deptName% ",
countQuery = "select count ( DISTINCT c ) from Customer c left join c.custDept cd where cd.deptName like %:deptName% ")
public Page<Customer> findByName(#Param("deptName") String deptName, Pageable pageable);
Worked for me. Now only the original query is executed and the result is correct.
{
"content": [
{
"customerNo": 1,
"custDept": [
{
"deptName": "it"
}
]
}
],
Finally note that it is better to use Integer for the #Id in your entities as per spring documentation.

Related

How can I do nested joins in hql

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!

Spring boot JPA - bidirectional relationsship: returns foreign key instead of the related object

I have two entities: Customer and Address.
It is a bidirectional relationsship - one address can have many customers (oneToMany) and one customer can only have one address (manyToOne).
Executing GET request for customers returns:
[
{
"id": 1,
"name": "Foo",
"contact": "5512312",
"email": "Foo#gmail.com",
"address": {
"id": 1,
"street": "X",
"postalCode": 123,
"houseNo": "10",
"city": "New York"
}
}
]
When a new customer, with the exact same address properties as the one exists in DB - is being added with POST request, the json response returns the foreign key related to the existing object in DB instead of the object itself:
[
{
"id": 1,
"name": "Foo",
"contact": "5512312",
"email": "Foo#gmail.com",
"address": {
"id": 1,
"street": "X",
"postalCode": 123,
"houseNo": "10",
"city": "New York"
}
},
{
"id": 2,
"name": "Bar",
"contact": "5512312",
"email": "Bar#gmail.com",
"address": 1 <----------- it returns the foreign key instead of the object
}
]
So what I expect is that whenever a new customer, that has an address that already exists in the DB, is being added - it should return the address object instead of the foreign key from the json response.
Code:
Customer.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Customer {
#Id
#SequenceGenerator(
name = "customer_sequence",
sequenceName = "customer_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "customer_sequence"
)
private Long id;
private String name;
private String contact;
private String email;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", nullable = false)
private Address address;
[...]
Address.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Address {
#Id
#SequenceGenerator(
name = "address_sequence",
sequenceName = "address_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "address_sequence"
)
private Long id;
private String street;
private int postalCode;
private String houseNo;
private String city;
#JsonIgnore
#OneToMany(mappedBy = "address")
private Set<Customer> customers;
[...]
CustomerController.java
//...
#PostMapping
public void createCustomer(#RequestBody Customer customer) {
customerService.createCustomer(customer);
}
[...]
And the service that saves the customer to the DB which also makes sure that no address is being created if already exists in database (it checks on every property to be equal from the param):
//...
public void createCustomer(Customer customer) {
Optional<Customer> customerWithExistingAddress = customerRepository.findAll()
.stream()
.filter(x -> x.getAddress().equals(customer.getAddress()))
.findFirst();
customerWithExistingAddress.ifPresent(c -> customer.setAddress(c.getAddress()));
customerRepository.save(customer);
}
[...]
You're probably getting this behavior because of JsonIdentityInfo, so it's a serialization problem you have not a persistence one. I'm assuming you're using a relational database (Hibernate for NoSql has Jpa like annotations but then that would make this a different problem) and that data is being persisted correctly.
See the javadocs:
In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values

How to select multiple columns including the foreign entity using JPA EntityManager

I am trying to select multiple columns from 2 tables with OneToOne relationship, where the users will send the columns they want to search and the sever returns the result list contains only those columns. I have two entities like these:
#Table(name = "user")
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "id" )
#Id
private String id;
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
#JsonManagedReference
private Address address;
//Getters and Setters
}
public class Address implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "user_id" )
#Id
private String id;
private String houseNr;
#OneToOne
#JoinColumn(name = "user_id")
#JsonBackReference
private Networkdata networkdata;
//Getters and Setters
With search functions using EntityManager with Tuple
public List<?> find(String[] neededFields){
if(neededFields.length > 0){
String queryStr = this.createQueryString(neededFields);
TypedQuery<Tuple> query = em.createQuery(queryStr, Tuple.class);
List<Map<String, Object>> resultList = new ArrayList<>();
query.getResultList().forEach(tuple -> {
Map<String, Object> map = new HashMap<>();
List<TupleElement<?>> elements = tuple.getElements();
for (TupleElement<?> element : elements ) {
String alias = element.getAlias();
map.put(alias, tuple.get(alias));
}
resultList.add(map);
});
return resultList;
}
else{
return em.createQuery(this.FIND_ALL_STR, User.class).getResultList();
}
}
When i search using SELECT u FROM User u, Address a WHERE u.id = a.id. It returns result like this:
[
{
"id": "5e4e3c95cd8b290008db6f3c",
"name": "name",
"address": {
"id": "5e4e3c95cd8b290008db6f3c",
"houseNr": "123ABC"
}
}
]
Another case is SELECT u.address as address FROM User u, Address a WHERE u.id = a.id which returns
[
{
"address": {
"id": "5e4e3c95cd8b290008db6f3c",
"houseNr": "123ABC"
}
}
]
But when i add a little spicy ingredient like for example, to select the id and the whole address entity like this SELECT u.id as id, u.address as address FROM User u, Address a WHERE u.id = a.id it returns
[
{
"id": "5e4e3c95cd8b290008db6f3c",
"address": null
}
]
Why the first two queries are perfectly fine but the last just returns a null address?
How can i solve this?
Other ideas are also welcome.
If you searching from two column, you can actually use INNER JOIN key words with FOREIGN KEY on the Adress. If you still want to trim down your search you can always use WHERE clause.
SELECT u.id as Id, a.address as Adress from User u INNER JOIN Address a ON u.id=a.user.id .
to trim down your search you can add “WHERE u LIKE %?1% AND a LIKE % ?1% ORDER BY u.id “
Better still to save yourself from headache you can use RESPONSE CLASS to map out the column you want to search from something like SELECT new UserAddress(u.id, a.address) ......
I hope I’m able to help.

Spring boot/Spring data jpa - how to update related entity?

I have following entities:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private ProfileContacts profileContacts;
...
}
and
#Entity
#Table(name = "profile_contacts")
public class ProfileContacts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
I am trying to update it by sending this JSON with update to REST controller:
{
"id": 1,
"description": "an update",
"profileContacts": {
"firstName": "John",
"lastName": "Doe"
}
}
so in the end it calls
profileRepository.save(profile);
where profileRepository is instance of ProfileRepository class:
public interface ProfileRepository extends JpaRepository<Profile, Long> {
}
which is spring-data-jpa interface.
But each time after such update it updates profile table but adds new row to profile_contacts table (table which corresponds to ProfileContactsentity) instead of updating existing ones.
How can I achieve updating?
As per your JSON structure. Yes it will create new profileContacts entry for every time.
The problem every time while saving profile entity you are passing "id": 1 that means Hibernate can identify the entity by this id value (primary key) but for profileContacts mapping you are not sending the id that's why Hibernate considering it has a new entity every time.
To update your profileContacts entity make sure to pass the id of it.
Example:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id" : yourEntityId
"firstName": "John",
"lastName": "Doe"
}
}
Well, that's the expected behavior.
You're not telling hibernate to update the profileContacts.
For the framework to be able to update it, you need to send the profileContact's primary key - which in your case is the ProfileContacts#id.
Something like this:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1
"firstName": "John",
"lastName": "Doe"
}
}
Need to specify the join column in the parent Entity.
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
**#JoinColumn(name = "id")** //
private ProfileContacts profileContacts;
...
}
Now when you try to save Profile entity it will save the child entity also.
And also needs to include Id in jason request for child entity also
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
}
Ok, I see the problem. As #Matheus Cirillo pointed out, you need to tell the hibernate to update the row.
Now, how do you tell the hibernate to update a row - By providing the primary key of the existing row.
But, creating an object with the primary key set is not enough. You need that entity class to be attached to the entity manager and the persistence context.
You can have something like,
//This attaches the entity to the entity manager
ProfileContacts existingProfileContacts = profileContactRepository.getOne(2);
Profile profile = new Profile();
....
....
profile.setProfileContacts(existingProfileContacts);
profileRepository.save(profile);
I hope this helps.

Prevent entities from stealing each other's mapped child entities in JPA

I have two entities, Subject and Lesson. Subject has OneToMany relationship with Lesson.
My database already has 2 sets of objects:
{
"id": 1,
"name": "wow",
"lessons": [
{
"id": 2,
"difficulty": "hard"
}
]
}
and
{
"id": 3,
"name": "wow2",
"lessons": [
{
"id": 4,
"difficulty": "hardy"
}
]
}
Entities are defined as follows
#Entity
public class Lesson {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String difficulty;
#ManyToOne(targetEntity = Subject.class)
private Subject subject;
}
#Entity
public class Subject {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(orphanRemoval=false, cascade= CascadeType.ALL)
#JoinColumn(name = "subject_id")
private List<Lesson> lessons;
}
I want to prevent this object to be saved successfully since it is stealing (1, "wow")'s lesson:
{
"id": 3,
"name": "wow2",
"lessons": [
{
"id": 2,
"difficulty": "hard_overrriden"
}
]
}
Manually iterating over all lessons is an obvious way, but I want an easy solution.
EDIT :
My solution fixes the stealing problem but, creates another too
#ManyToOne
#JoinColumn(updatable = false)
private Subject subject;
.
.
.
#OneToMany(cascade= CascadeType.ALL, mappedBy="subject", orphanRemoval=true)
private List<Lesson> lessons;
This fixes the stealing problem but deletes the orphan if I want to disassociate lesson from subject.
On removing, orphanRemoval = true it just stops disassociating lesson from subject.
Disassociation object is as follows:
{
"id": 1,
"name": "wow",
"lessons": []
}

Categories

Resources