I have a some records in the table having parent child relations, screenshot below:
How do I write a JPA Entity to retrieve those records with respect to Parent-Child relation. Your help is appreciated.
The code that did not help me well is as below:
#Entity
#Table(name = PlatformConstant.TABLE_MENU)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "url", nullable = false)
private String url;
#Column(name = "description")
private String description;
#Column(name = "qr_code")
private Blob qrCode;
#JsonManagedReference
#OneToMany(mappedBy = "parent")
private Set<Menu> children;
#ManyToOne
#JsonBackReference
private Menu parent;
}
My code above has the following wrong output:
Using the JpaRepository find all, and applying the answer from #lucid, the new output is as below:
the code:
#Autowired
private MenuService menuService;
#CrossOrigin
#GetMapping("/all")
#ResponseBody
public List<Menu> getMenus() {
return (List<Menu>) menuService.findAll().stream()
.filter (menu-> Objects.isNull(menu.getParent()).collect(Collectors.toList()));
}
the output:
Thank you.
Jackson provides these annotations to control the parent-child relationships.
#JsonBackReference: skips property during the serialization process
#JsonManagedReference: forward reference and serialized annotated property
In your case, you don't want parent object to be serialized inside your child reference, you can annotate it with #JsonBackReference
#JsonManagedReference
#OneToMany(mappedBy = "parent")
private Set<Menu> children;
#ManyToOne
#JsonBackReference
private Menu parent;
Now, to remove child objects from response, we can filter that
Like this
menuService.findAll().stream()
.filter(menu-> Objects.isNull(menu.getParent()))
.collect(Collectors.toList());
Related
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 am trying to solve JPA problem. I have 2 main entities - CameraItem and Chain (which represents ordered list of cameras)
Now there have to be 2 #ManyToMany relationships between CameraItem and Chain.
Each CameraItem has at least one parent Chain. As one CameraItem can belong to different Chains, and each Chain can have multiple CameraItems this is the first simple direct #ManyToMany relationship.
Chains can be connected with each other via CameraItem. In other words, CameraItem is holding the connection between Chains. But this is not simple #ManyToMany relationship, because we also need information about direction of the Chains connection. So it is #ManyToMany relationship with new Entity as Baeldung describes here https://www.baeldung.com/jpa-many-to-many. Entity ConnectionPoint is holding the information about the direction as a String.
I paste the classes here:
CHAIN CLASS:
#Entity
#Table(name = "chain")
public class Chain {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotBlank(message = "Chain name is mandatory")
private String name;
#Column(name = "PLANT_NAME")
private String plantName;
private String description;
private String status;
private Boolean hasPlant;
#CreationTimestamp
#Column(name = "creation_time")
private LocalDateTime creationTime;
#OneToMany(mappedBy = "camera_item")
private List<CameraItem> cameraItems = new ArrayList<>();
#OneToMany(mappedBy = "chain")
Set<ConnectionPoint> connectionPoints;
CAMERA ITEM CLASS:
#Entity
#Table(name = "camera_item")
public class CameraItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn
private Camera camera;
private String name;
private Integer positionInChain;
#ManyToMany(mappedBy = "cameraItems", fetch = FetchType.LAZY)
private List<Chain> parentChainIds;
#OneToMany(mappedBy = "cameraItem")
Set<ConnectionPoint> connectionPoints;
CONNECTION POINT CLASS:
#Entity
#Table(name = "connection_point")
public class ConnectionPoint {
#Id
private Long id;
#Column(name = "direction")
private String direction;
#ManyToOne
#JoinColumn(name = "chain")
private Chain chain;
#ManyToOne
#JoinColumn(name = "camera_item")
private CameraItem cameraItem;
When I run the application I get this error:
org.hibernate.AnnotationException: mappedBy reference an unknown
target entity property:
no.trafsys.videodashboard.model.entity.CameraItem.camera_item in
no.trafsys.videodashboard.model.entity.Chain.cameraItems
Does somebody know where the problem can be?
I use #OneToMany annotations in Chain and CameraItem entities and #ManyToOne in ConnectionPoint like Baeldung in his tutorial.
Thank you in advance for any help
I don't think there is issue in ConnectionPoint. I think the issue is that:
In Chain class,
#OneToMany(mappedBy = "camera_item") // One-to-Many defined here
private List<CameraItem> cameraItems = new ArrayList<>();
while in CameraItem class, corresponding property is defined as follow:
#ManyToMany(mappedBy = "cameraItems", fetch = FetchType.LAZY) // Many-To-Many
private List<Chain> parentChainIds;
Try changing the mapping type to #ManyToMany in Chain class as well. It might work.
PS: I am not entirely sure of this, but this feels like the issue[incorrect mapping type]. Wanted to add this as a comment, but due to space issues, adding it as an answer.
#Entity
#Table(name = "chain")
public class Chain {
//..
#OneToMany(mappedBy = "camera_item")
private List<CameraItem> cameraItems = new ArrayList<>();
//..
}
mappedBy parameter can only be in one side of the relation. I suspect camera_item is database table column name. So your cameraItems needs #JoinTable(name = "camera_item"... annotation
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
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.