I'm trying to send the following JSON to a REST API and persist on database, but only the Product is created, the Image it is not.
{"name":"pen",
"description":"red pen",
"images":[{"type":"jpeg"}]
}
#Controller
#POST
#Path("/product/add")
#Consumes("application/json")
public Response addProduct(Product product) {
service.createProduct(product);
}
#Service
#Autowired
private ProductDAO productDAO;
#Autowired
private ImageDAO imageDAO;
public void createProduct(Product product) {
productDAO.save(product);
}
#Product
#Entity
#Table
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer productId;
#Column(name = "NAME")
private String name;
#Column(name = "DESCRIPTION")
private String description;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="product")
private Set<Image> images;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="parent")
private Set<Product> children;
#ManyToOne
#JoinColumn(name = "PARENT_PRODUCT_ID")
private Product parent;
#Image
#Entity
#Table
public class Image implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer imageId;
#Column(name = "TYPE")
private String type;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
At the #POST method, when print the Product object received, this is what returns:
Product [productId=null, name=pen, description=red pen, images=[Image [id=null, type=jpeg, product=null]], children=null, parent=null]
The correct way is to first persist the Product, and then persist the Image or the Hibernate can automatically persist the Image when I persist the Product?
Hibernate takes care of persisting your child entities if your bidirectional mapping is correctly implemented and you have set proper relationships between your entity objects.
You have a Product entity that has a collection of Image. Product entity is the parent entity here. You can simply set proper relations between Product and Image entities and persist only Product. Hibernate will persist your parent as well as your child entities.
What you need to do
Product product = new Product();
product.setName("PRODUCT_NAME");
Set<Image> productImages = new HashSet<>();
Image productProfileImage = new Image();
productProfileImage.setType("PROFILE");
productProfileImage.setProduct(product);
//..set other fields
productImages.add(productProfileImage);
Image productCoverImage = new Image();
productCoverImage.setType("COVER");
productCoverImage.setProduct(product);
//..set other fields
productImages.add(productCoverImage);
product.setImages(productImages);
productRepository.save(product); //Persist only your product entity and the mapped child entities will be persisted
Check out this similar answer.
PS: I have not tested the code but this should work.
Related
I have a Shipment Entity class where there are two ShipmentAddress, one for the delivery address and the other, the destination address:
#Entity
#Table(name = "shipment")
public class Shipment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private AppUser user;
#OneToOne(cascade = CascadeType.ALL)
private ShipmentAddress fromAddress;
#OneToOne(cascade = CascadeType.ALL)
private ShipmentAddress toAddress;
}
I have a ShipmentAddress Entity class where it looks like this:
#Entity
public class ShipmentAddress {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String street1;
private String street2;
#OneToOne(mappedBy = "fromAddress")
private Shipment shipment;
#OneToOne(mappedBy = "toAddress")
private Shipment shipment;
}
I think we can agree that it is redunant to have two properties to define a Shipment inside ShipmentAddress, because they both point to the same Shipment. In ShipmentAddress class, I want to be able to have one Shipment in the ShipmentAddress Entity. JPA does not support multiple #OneToOne annotations for the same entity class. How can I accomplish this?
In my Spring Boot app, I use Hibernate and applied the necessary relations to the following entities properly.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false, length=50)
private String title;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}
#Entity
public class RecipeIngredient {
#EmbeddedId
private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("recipeId")
#JoinColumn(name = "recipe_id", referencedColumnName = "id")
private Recipe recipe;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("ingredientId")
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")
private Ingredient ingredient;
}
#Entity
public class Ingredient
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique=true, nullable=false, length=50)
#EqualsAndHashCode.Include
private String name;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}
Now I am trying to retrieve data by merging related entities. For example, when retrieving a Recipe, I also need to retrieve all Ingredients belonging to this Recipe.
As far as I know, I can use Projection and maybe it is better to only use Hibernate features and retrieve related table data via Java Stream. I have no idea how should I retrieve data via Hibernate.
Suppose that I just need an Optional<Recipe> that has List<Ingredient>. Then, I probably need a DTO class something like that:
#Data
public class ResponseDTO {
private Long id;
private String title;
List<RecipeIngredient> ingredients;
// getter, setter, constructor
}
So, how should I populate this DTO with the requested Recipe and corresponding Ingredient data (getting Ingredient names besides id values) using Java Stream?
Or if you suggest Projection way, I tried it but the data is multiplied by the ingredient count belonging to the searched recipe.
Update:
#Getter
#Setter
#NoArgsConstructor
public class ResponseDTO {
private Long id;
private String title;
List<IngredientDTO> ingredientDTOList;
public ResponseDTO(Recipe recipe) {
this.id = recipe.getId();
this.title = recipe.getTitle();
this.ingredientDTOList = recipe.getRecipeIngredients().stream()
.map(ri -> new IngredientDTO(ri.getIngredient().getName()))
.toList();
}
}
#Getter
#Setter
public class IngredientDTO {
private Long id;
private String name;
public IngredientDTO(String name) {
this.name = name;
}
}
First, in the ResponseDTO you will need you change the type of ingredients from List<RecipeIngredient> to List<Ingredient>.
To manually perform the mapping, you should use (to map from a suppose Recipe recipe to a RespondeDTO response):
ResponseDTO recipeToResponseDTO(Recipe recipe) {
ResponseDTO response = new ResponseDTO();
response.setId(recipe.getId());
response.setTitle(recipe.getTitle());
response.setIngredients(recipe.recipeIngredients.stream()
.map(RecipeIngredient::getIngredient()
.collect(Collectors.toList());
return response;
}
On the other hand, to model a n-n relation, I encourage you to use the approach proposed by E-Riz in the comment.
I am trying to save a JPA entity which has ManytoMany Relationship (Consumer and Product table) and OnetoOne relation with ConsumerDetailstable.Below are my entities
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity
public class Consumer {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
#JsonManagedReference
#OnToMany(mappedBy = "consumer")
private Set<ConsumerProduct> consumerProducts;
#OneToOne
private CustomerDetails consumerDetails;
}
#Entity
public class Product {
#Id
#GeneratedValue
private Long productId;
private String productCode;
#OneToMany(mappedBy = "product")
private Set<ConsumerProduct> consumerProducts;
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity(the join table)
public class ConsumerProduct {
#EmbeddedId
ConsumerProductKey id;
#JsonBackReference
#ManyToOne
#MapsId("id")
#JoinColumn(name = "id")
private Consumer consumer;
#ManyToOne
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product product;
}
#Embeddable (forgein keys combined as embeded id)
public class ConsumerProductKey implements Serializable {
#Column(name="id")
private Long id;
#Column(name = "product_id")
private Long productId;
}
#Enitity (one to one relation table)
public class CustomerDetails {
#Id
#GeneratedValue
private Long consumerDtlId;
#OneToOne
private Consumer consumer;
private String city;
private String state;
private String country;
}
To save the entity am have just extended JPARepository and called save method
public class ConsumerRepository<Consumer> Implements JPARepository<Consumer, Long> {
#Override
public Consumer S save(Consumer entity) {
return save(entity);
};
}
I get java.lang.StackOverFlowError at save method.
Anything wrong with my Mappings ?
Question: Since this will be save operation and since Consumer Id is yet to be generated how do I assign to below Entities
ConsumerProduct.ConsumerProductKey (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
CustomerDetails (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
EDIT: I have updated the entity with JsonManagedReference and JsonBackedReference but still i have am facing stackoverflow error
It is due to Consumer trying to access ConsumerProduct and ConsumerProduct trying to access consumer entity and end up with StackOverflow error.
You should use #JsonManagedReference and #JsonBackReference annotation in consumer and ConsumerProduct respectivly.
I have one table called image and another table called duplicate. There are two OneToMany relations are associated.
I am not quite sure whether below implementation is the right approach for that. Moreover whether we require that second private List<DuplicateEntity> duplicateEntities2; ?
ImageEntity:
#Entity
#Table(name = "image")
public class ImageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "filename")
private String fileName;
#OneToMany(mappedBy = "imageEntity1")
private List<DuplicateEntity> duplicateEntities1;
#OneToMany(mappedBy = "imageEntity2")
private List<DuplicateEntity> duplicateEntities2;
}
DuplicateEntity:
#Entity
#Table(name = "duplicate")
public class DuplicateEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "image_a_id")
private ImageEntity imageEntity1;
#ManyToOne
#JoinColumn(name = "image_b_id")
private ImageEntity imageEntity2;
}
I have a Controller from rest service that I call a Hibernate method to get the result, but I really don't know why the children components didn't come. When I call this method using Junit, It works.
This is the Code:
{
#Entity
public class Product implements Serializable {
private static final long serialVersionUID = -6131311050358241535L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
private String description;
#OneToMany(mappedBy = "product")
private List<Image> images = new ArrayList<Image>();
}
{
#Entity
public class Image implements Serializable {
private static final long serialVersionUID = 2128787860415180858L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#JoinColumn(name = "product_id")
#ManyToOne
private Product product;
private ImageType type;
}
{
#PersistenceContext
private EntityManager entityManager;
public List<Product> findAllWithParentProductsAndImage() {
String hpql = "select distinct p from Product p left join fetch p.images";
List<Product> resultList = entityManager.createQuery(hpql,
Product.class).getResultList();
return resultList;
}
}
By default #OneToMany will load lazily.
You should use #OneToMany( mappedBy = "product", fetch=FetchType.Eager ) to do Eager fetch
You can definitely use
#OneToMany(mappedBy = "product", fetch=FetchType.Eager)
However this has a downside. You will always be fetching children even if you only want the Parent and its few properties.
Use JOIN FETCH within your #Query if you are using JpaRepositories.
Check out the following related questions
How to properly express JPQL "join fetch" with "where" clause as JPA 2 CriteriaQuery?
http://www.objectdb.com/java/jpa/query/jpql/from#LEFT_OUTER_INNER_JOIN_FETCH_
https://stackoverflow.com/a/29667050/3094731