I have three entity classes of the following:
Shipments Entity:
#Entity
#Table(name = "SHIPMENT")
public class Shipment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "SHIPMENT_ID", nullable = false)
private int shipmentId;
#Column(name = "DESTINATION", nullable = false)
private String destination;
#OneToMany(mappedBy = "shipment")
private List<ShipmentDetail> shipmentDetailList;
//bunch of other variables omitted
public Shipment(String destination) {
this.destination = destination;
shipmentDetailList = new ArrayList<>();
}
Shipment Details Entity:
#Entity
#Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
Product Entity:
#Entity
#Table(name = "Product")
public class Product implements Serializable {
#Id
#Column(name = "PRODUCT_ID", nullable = false)
private String productId;
#Column(name = "PRODUCT_NAME", nullable = false)
private String productName;
//bunch of other variables omitted
public Product() {
}
public Product(String productId, String productName) {
this.productId = productId;
this.productName = productName;
}
I am receiving JSONs through a rest API. The problem is I do not know how to deserialize a new Shipment with shipmentDetails that have relationships to already existing objects just by ID. I know you can simply deserialize with the objectmapper, but that requires all the fields of product to be in each shipmentDetail. How do i instantiate with just the productID?
Sample JSON received
{
"destination": "sample Dest",
"shipmentDetails": [
{
"productId": "F111111111111111"
},
{
"productId": "F222222222222222"
}
]
}
Currently my rest endpoint would then receive the JSON, and do this:
public ResponseEntity<String> test(#RequestBody String jsonString) throws JsonProcessingException {
JsonNode node = objectMapper.readTree(jsonString);
String destination = node.get("destination").asText();
Shipment newShipment = new Shipment(destination);
shipmentRepository.save(newShipment);
JsonNode shipmentDetailsArray = node.get("shipmentDetails");
int shipmentDetailsArrayLength = shipmentDetailsArray.size();
for (int c = 0; c < shipmentDetailsArrayLength; c++) {
String productId = node.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(newShipment, product, quantity);
shipmentDetailRepository.save(shipmentDetail);
}
}
what i want to do is:
public ResponseEntity<String> test2(#RequestBody String jsonString) throws JsonProcessingException {
JsonNode wholeJson = objectMapper.readTree(jsonString);
Shipment newShipment = objectMapper.treeToValue(wholeJson, Shipment.class);
return new ResponseEntity<>("Transfer Shipment successfully created", HttpStatus.OK);
}
I tried this solution to no. avail:
Deserialize with Jackson with reference to an existing object
How do I make the product entity search for an existing product instead of trying to create a new product. The hacky extremely inefficient workaround I have been using is to traverse the json array, and for every productId find the product using the productRepository, and then set the shipmentDetail with the product one by one. Im not sure if this is best practice as im self learning spring.
So in pseudocode what im trying to do would be:
Receive JSON
Instantiate Shipment entity
Instantiate an array of shipmentDetail entities
For each shipmentDetail:
1. Find product with given productId
2. Instantiate shipmentDetail with product and shipment
Code has been significantly simplified to better showcase the problem,
You have a bottleneck in your code in this part:
Product product = productRepository.findById(productId)
Because you are making a query for each productId, and it will perform badly with large number of products. Ignoring that, I will recommend this aproach.
Build your own deserializer (see this):
public class ShipmentDeserializer extends JsonDeserializer {
#Override
public Shipment deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String destination = node.get("destination").asText();
Shipment shipment = new Shipment(destination);
JsonNode shipmentDetailsNode = node.get("shipmentDetails");
List shipmentDetailList = new ArrayList();
for (int c = 0; c < shipmentDetailsNode.size(); c++) {
JsonNode productNode = shipmentDetailsNode.get(c);
String productId = productNode.get("productId").asText();
Product product = new Product(productId);
ShipmentDetail shipmentDetail = new ShipmentDetail(product);
shipmentDetailList.add(shipmentDetail);
}
shipment.setShipmentDetailList(shipmentDetailList);
return shipment;
}
}
Add the deserializer to your Shipment class:
#JsonDeserialize(using = ShipmentDeserializer .class)
public class Shipment {
// Class code
}
Deserialize the string:
public ResponseEntity test2(#RequestBody String jsonString) throws JsonProcessingException {
Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class);
/* More code */
return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK);
}
At this point, you are only converting the Json into classes, so we need to persist the data.
public ResponseEntity test2(#RequestBody String jsonString) throws JsonProcessingException {
Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class);
shipmentRepository.save(newShipment);
List<ShipmentDetail> shipmentDetails = newShipment.getShipmentDetailList();
for (int i = 0; i < shipmentDetails.size(); c++) {
ShipmentDetail shipmentDetail = shipmentDetails.get(i);
shipmentDetail.setShipment(newShipment);
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
shipmentDetail.setProduct(product);
shipmentDetailRepository.save(shipmentDetail);
}
return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK);
}
I know you want to reduce the code in the test method, but I DO NOT RECOMMEND to combine the Json deserialize with the persistence layer. But if you want to follow that path, you could move the productRepository.findById(productId) into the ShipmentDeserializer class like this:
public class ShipmentDeserializer extends JsonDeserializer {
#Override
public Shipment deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String destination = node.get("destination").asText();
Shipment shipment = new Shipment(destination);
JsonNode shipmentDetailsNode = node.get("shipmentDetails");
List shipmentDetailList = new ArrayList();
for (int c = 0; c < shipmentDetailsNode.size(); c++) {
JsonNode productNode = shipmentDetailsNode.get(c);
String productId = productNode.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(product);
shipmentDetailList.add(shipmentDetail);
}
shipment.setShipmentDetailList(shipmentDetailList);
return shipment;
}
}
But if you want to do that, you need to inject the repository into the deserializer (see this).
I think your current approach is not a bad solution, you are dealing with the problem correctly and in few steps.
Any way, one thing you can try is the following.
The idea will be to provide a new field, productId, defined on the same database column that supports the relationship with the Product entity, something like:
#Entity
#Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
#Column(name = "PRODUCT_ID", nullable = false)
private String productId;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", insertable = false, updatable = false)
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
}
The product field must be annotated as not insertable and not updatable: on the contrary, Hibernate will complaint about which field should be used to maintain the relationship with the Product entity, in other words, to maintain the actual column value.
Modify the Shipment relationship with ShipmentDetail as well to propagate persistence operations (adjust the code as per your needs):
#OneToMany(mappedBy = "shipment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ShipmentDetail> shipmentDetailList;
Then, you can safely rely on the Spring+Jackson deserialization and obtain a reference to the received Shipment object:
public ResponseEntity<String> processShipment(#RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
// Save the object: it should persist the whole object tree in the database
shipmentRepository.save(shipment);
}
This approach has an obvious drawback, the existence of the Product is not checked beforehand.
Although you can ensure data integrity at database level with the use of foreign keys, perhaps it would be convenient to validate that the information is right before perform the actual insertion:
public ResponseEntity<String> processShipment(#RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
List<ShipmentDetail> shipmentDetails = shipment.getShipmentDetails();
if (shipmentDetails == null || shipmentDetails.isEmpty()) {
// handle error as appropriate
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No shipment details provided");
}
shipmentDetails.forEach(shipmentDetail -> {
String productId = shipmentDetail.getProductId();
Product product = productRepository.findById(productId).orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"No product with ID of: " + productId + " exists!")
)
});
// Everything looks fine, save the object now
shipmentRepository.save(shipment);
}
i use querydsl, hibernate
i want select data by Dto in Dto list but not working
here is my code
#Data
#Entity
public class Team {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
#Entity
#Setter
public class Member {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "team_id")
private Team team;
}
#Setter
public class TeamDto {
private Long id;
private String name;
private List<MemberDto> members = new ArrayList<>();
}
#Setter
public class MemberDto {
private Long id;
private String name;
}
test
#BeforeEach
void setup() {
queryFactory = new JPAQueryFactory(em);
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member("memberA");
member.setTeam(team);
em.persist(member);
Member member2 = new Member("memberB");
member2.setTeam(team);
em.persist(member2);
em.flush();
em.clear();
}
#Test
void t1() {
TeamDto teamDto = queryFactory
.select(Projections.fields(
TeamDto.class,
team.id,
team.name,
Projections.fields(
MemberDto.class,
member.id,
member.name
).as("members")
))
.from(team)
.fetchOne();
System.out.println("teamDto = " + teamDto);
}
error log is = java.lang.IllegalArgumentException: com.blog.querydsltest.domain.dto.MemberDto is not compatible with java.util.List
what is problem?? is impossible bring data by List dto??
i try to change Projections.fields to bean, construct, ... but not working
how can i do ?
Multi level aggregations are currently not supported by QueryDSL. There are also no concrete plans to support it as of now.
For a DTO solution that can fetch associations with it, I recommend you to have a look at Blaze-Persistence Entity Views. With Entity Views the code for your DTO would look something like the following:
#EntityView(Team.class)
public interface TeamDto {
#IdMapping public Long getId();
#Mapping("name") public String getName();
#Mapping("members") public List<MemberDTO> getMembers();
}
If members is not an association on your TeamEntity, you can map it through a #MappingCorrelated binding.
Disclaimer: I am a contributor for Hibernate, QueryDSL and Blaze-Persistence.
I have next entities:
Attachment:
#Data
#ToString
#Entity
#EqualsAndHashCode
#Accessors(chain = true)
public class Attachment {
private Long id;
private String attachmentName;
private String code;
}
Document:
#Data
#ToString
#Entity
#EqualsAndHashCode
#Accessors(chain = true)
public class Document{
private Long id;
private String documentName;
#LazyCollection(LazyCollectionOption.FALSE)
#OrderBy
#OneToMany(mappedBy = "...", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Attachment> attachments = new ArrayList<>();
}
I need to build Specification (I should use only Specification because document service was written by 3rd party and requires Specification as Param) which must contain filtering by attachmentName. It should looks something like this:
public class DocumentPredicate {
public Specification<Document> getPredicate(String attachmentFilteringName) {
Specifications<T> result = Specifications.where(AnotherSpecification);
if (attachmentFilteringName != null) {
result = result.and((root, query, cb) ->
cb.equal(root.get("attachments"), attachmentFilteringName));
}
}
}
I have tried a lot of solutions but all of them doesn't resolve this task.
The main problems is:
I can't do smth like: root.get("attachments").get("0"), i.e. I can't use list elements in predicate. Because I get exception, of course.
I cant use cb.in() because I have only attachmentFileName and doesnt have other fields for Attachment equals method proper work.
if (attachmentFilteringName != null) {
result = result.and((root, query, cb) ->
cb.equal(root.join("attachments").get("attachmentName"), attachmentFilteringName));
}
I get a List of Objects List<APIObjects> apiObjectList as an Input to my API (through HTTP-Post), I need to compare this Input-List with my List of Entity Object which i get by executing repository.findAll() with Spring-boot-data-JPA framework
Currently i loop the List<DatabaseObject> and then find if there is a match. Below is my code
public Boolean findIfAllAPIobjectsExist (List<APIObject> apiObjects) {
List<DatabaseObject> databaseObjectsList = databaseRepository.findAll()
return apiObjects.stream().allMatch {
apiObject -> {
for (DatabaseObject dbObject : databaseObjectsList) {
if ((dbObject.getGroupId().trim().equals(dbObject.getGroupId().trim())) &&
(dbObject.getArtifactId().trim() .equals(dbObject.getArtifactId().trim())) &&
(dbObject.getVersion().trim().equals(dbObject.getVersion().trim()))) {
System.out.println("Matching ..");
return true;
}
}
return false;
}
}
}
}
But this looping seems to consume lot of time and Memory and How can it be tackled with Lambda functions ? I am pretty sure that my current methodology shown above (looping of DatabaseObject) isn't the right or professional way to tackle it
APIObject.java
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class APIObject{
private String groupId;
private String artifactId;
private String version;
}
DatabaseObject.java
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "my_table")
public class DatabaseObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Column(name = "name")
String name;
#Column(name = "group_id")
String groupId;
#Column(name = "artifact_id")
String artifactId;
#Column(name = "version")
String version;
}
I would override equals and hashCode in both of my Enties which hold the three attributes groupId, artifactId, version then sort the two List based on the three arrtibutes, and just use .equals to determine if the two Lists are equivalent or no :
databaseObjectsList.sort(
Comparator.comparing(DatabaseObject::getGroupId)
.thenComparing(DatabaseObject::getArtifactId)
.thenComparing(DatabaseObject::getVersion)
);
apiObjects.sort(Comparator.comparing(APIObject::getGroupId)
.thenComparing(APIObject::getArtifactId)
.thenComparing(APIObject::getVersion)
);
return databaseObjectsList.equals(apiObjects);
With lombok you can use :
#EqualsAndHashCode(of = {"groupId", "artifactId", "version"})
class DatabaseObject {..}
I want to convert the following json into a java object, using as much as possible annotations.
{"user":{
"id":1,
"diets":[
{"diet":{
"name":"...",
"meals":[]
}
}
]
}
}
I'm getting trouble with the collection diets. I tried to use #JsonProperty but it doesn't work properly. Is there a special annotation for map inner aggregates?
Diet.java
#JsonRootName(value = "diet")
public class Diet {
#JsonProperty(value="name")
private String name;
#JsonProperty(value="meals")
private List<Meal> meals;
private User user;
// Rest of the class omitted.
}
User.java
#JsonRootName(value = "user")
public class User {
#JsonProperty("id")
private long id;
#JsonProperty("diets")
private List<Diet> diets = new ArrayList<Diet>();
// Rest of the class omitted.
}
Thanks!
The diets object in your json is not a List. Its a List of key-value pair with key "diet" and value a diet object. So you have three options here.
One is to create a wrapper object say DietWrapper and use List of diet wrapper in User like
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
private List<DietWrapper> diets;
//Getter & Setters
}
class DietWrapper {
#JsonProperty(value = "diet")
Diet diet;
}
Second option is to keep diest as simple list of maps like List>
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
private List<Map<String, Diet>> diets;
//Getter & Setters
}
Third option is to use a custom deserializer which would ignore your diet class.
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
#JsonDeserialize(using = DietDeserializer.class)
private List<Diet> diets;
//Getter & Setters
}
class DietDeserializer extends JsonDeserializer<List<Diet>> {
#Override
public List<Diet> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonParser);
List<Diet> diets = mapper.convertValue(node.findValues("diet"), new TypeReference<List<Diet>>() {});
return diets;
}
}