JPA - Spring boot -#OneToMany persistence works fine but i get a strange object when returning Json object - spring

I have two entities ( Category | product ) with #OneToMany bidirectional relationship.
#Entity
public class Category {
public Category() {
// TODO Auto-generated constructor stub
}
public Category(String name,String description) {
this.name=name;
this.description=description;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long cid;
private String name;
private String description;
#OneToMany(mappedBy="category",cascade=CascadeType.ALL, orphanRemoval=true)
private Set<Product> products;
/..getters and setter.../
}
#Entity
public class Product {
public Product() {
// TODO Auto-generated constructor stub
}
public Product(long price, String description, String name) {
// TODO Auto-generated constructor stub
this.name=name;
this.description=description;
this.price=price;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long pid;
private long price;
private String name;
private String description;
#ManyToOne
private Category category;
/..getters and setters../
}
in my controller I have a function /categoris that add a new category with one product, it works great and in my database I've got a foreign category id
But when i try to retrieve all the categories with responseBody i got a strange object exactely in category ( i want have in product, category : the category id instead of the object it's self )
public #ResponseBody Category create() {
Category c=new Category("LIGA","messi feghouli cristiano");
Product p=new Product(200,"jahd besaf","Real Madrid");
if(c.getProducts()!=null){
c.addProducts(p);
}else{
Set<Product> products=new HashSet<Product>();
products.add(p);
c.setProducts(products);
}
p.setCategory(c);
cDao.save(c); pDao.save(p);
return c;
}
#RequestMapping(value="/categories",method = RequestMethod.GET)
public #ResponseBody List<Category> categories() {
return cDao.findAll();
}
this is the strage object that i got :
{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":[{"price":200,"name":"Real Madrid","description":"jahd besaf","category":{"cid":1,"name":"LIGA","description":"messi feghouli cristiano","products":

That's exactly as it should be.
If you wish to avoid a circular reference, use the #JsonBackReference annotation. This prevents Jackson (assuming you're using Jackson) from going into an infinite loop and blowing your stack.
If you want the ID instead of the entity details, then create getProductID & getCategoryID methods and annotate the entity accessor with #JsonIgnore.

Related

Spring hibernate ignore json object

I need to remove cart object from json, but only in one controller method and that is:
#GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
User
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotBlank(message = "Name cannot be empty")
private String name;
#OneToOne
private Cart cart;
}
Cart
#Entity
public class Cart {
#Id
private String id = UUID.randomUUID().toString();
#OneToMany
private List<CartItem> cartItems = new ArrayList<>();
#OneToOne
#JsonIgnore
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
}
I have done it with simple solution so i loop trough all users, and set their cart to null,and then anotated user entity with #JsonInclude(JsonInclude.Include.NON_NULL)
But i dont think this is propper solution, so im searching for some better solution..
How am i able to do this?
Thanks...
You can create DTO (data transfer object) class like this:
#Data
public class UsersDto {
private Integer id;
private String name;
public UsersDto(User user) {
this.id = user.id;
this.name= user.name;
}
}
and than create List<UsersDto>
#GetMapping("/users")
public List<UsersDto> getUsers() {
List<User> users = userRepository.findAll();
return users
.stream()
.map(o -> new UsersDto(o))
.collect(Collectors.toList());
}
You should use Data Projection.
In your use case, you can use an interface projection:
public interface CartlessUser {
Integer getId();
String getName();
}
And In your repository:
public interface UserRepository extends JpaRepository<User, Integer> {
List<CartlessUser> findAllBy();
}
The interface projection will help generate the sql query for only selecting the id, name fields. This will save you from fetching the Cart data when you're just going to throw it away anyways.

bidirectional relationship returning empty set on OneToMany and works only on ManyToOne

i have 2 entities, Category and Feature, each Category has one or many features.
when creating a category along with its features, fetching the new category returns an empty set on the features attribute.
#PostMapping("/sub")
#PreAuthorize("hasRole('ROLE_admin')")
public HttpEntity<CategoryDTO> createSubCategory(#Valid #RequestBody CreateCategory createCategory)
{
Category category = categoryService.create(createCategory,mainCategoryService.one(createCategory.getMainCategory_id()));
featureService.bulkCreate(createCategory.getFeatures(),category);
return ResponseEntity.status(HttpStatus.CREATED).body(modelMapper.map(category,CategoryDTO.class));
}
this is the data that i'm sending:
{"name":"SUB","mainCategory_id":1,"features":{"F1":"SLIDER","F2":"CHECKBOX"}}
and this is the data returned by the controller:
{"id":2,"name":"SUB","mainCategory":{"id":1,"name":"CATEGORY"},"features":[]}
as you can see, the features are empty.
This is the test to create a category with its features:
#Test
public void testIfAdminCanCreateSubCategory_expect201AndMainCategoryIdEqualsTheAssociatedOne() throws Exception {
String category = "{\"name\" : \"CATEGORY\"}";
String sub = "{\"name\":\"SUB\",\"mainCategory_id\":1,\"features\":{\"F1\":\"SLIDER\",\"F2\":\"CHECKBOX\"}}";
mockMvc().with(keycloakAuthenticationToken().authorities("ROLE_admin")).perform(post("/categories").content(category).contentType("application/json"))
.andDo(print())
.andDo(r -> mockMvc().with(keycloakAuthenticationToken().authorities("ROLE_admin"))
.post(sub,"/categories/sub")
.andExpect(status().isCreated())
.andExpect(jsonPath("$.mainCategory.id").value(1))
.andDo(print())
// .andExpect(jsonPath("$.features[0]").value("SLIDER"))
);
//featureService.all().forEach( f -> System.out.println(f.getCategory().getId()));
}
when decommenting the last line, it prints the category id ( which is 2 as shown in the returned data ), meaning that the ManyToOne is working, but not the OneToMany.
My models:
#Entity
#EntityListeners( AuditingEntityListener.class )
#Data
public class Category {
#Id #GeneratedValue(strategy=GenerationType.AUTO) private Long id;
....
#OneToMany(cascade = CascadeType.ALL, mappedBy = "category" , fetch = FetchType.LAZY)
Set<Feature> features = new HashSet<>();
}
#Entity
#Data
public class Feature {
#Id #GeneratedValue private Long id;
private String name;
private FeatureType type;
#ManyToOne Category category;
}
the create method in categoryService:
#Override
public Category create(CreateCategory createCategory, MainCategory mainCategory) {
Category category = new Category();
category.setName(createCategory.getName().toUpperCase());
category.setMainCategory(mainCategory);
return categoryRepository.save(category);
}
the bulkCreate method in the featureService:
#Override
public Feature create(String feature, FeatureType type, Category category) {
Feature f = new Feature();
f.setName(feature);
f.setType(type);
f.setCategory(category);
return featureRepository.save(f);
}
#Override
public void bulkCreate(Map<String, FeatureType> features, Category category) {
features.forEach( (name,type) -> create(name,type,category));
}
myDTOs:
#Data
public class CategoryDTO {
private Long id;
private String name;
private MainCategoryDTO mainCategory;
private Set<FeatureDTO> features;
}
#Data
public class FeatureDTO {
private Long id;
private String name;
private FeatureType type;
}
#Data
public class MainCategoryDTO {
private Long id;
private String name;
}
EDIT 1 :
i've added a method on my categoryService that sets the category features.
#Override
public Category addFeatures(Category category, List<Feature> features) {
category.setFeatures(features);
return categoryRepository.save(category);
}
and on my controller, i added the commented line so i can associate features to the category
#PostMapping("/sub")
#PreAuthorize("hasRole('ROLE_admin')")
public HttpEntity<CategoryDTO> createSubCategory(#Valid #RequestBody CreateCategory createCategory)
{
Category category = categoryService.create(createCategory,mainCategoryService.one(createCategory.getMainCategory_id()));
#category = categoryService.addFeatures(category,featureService.bulkCreate(createCategory.getFeatures(),category));
return ResponseEntity.status(HttpStatus.CREATED).body(modelMapper.map(category,CategoryDTO.class));
}

I don't know why the double values are displayed in postman. Is the my code correct?

This is my Book class:
#Entity
#Table(name="book")
public class Book {
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#ManyToOne(targetEntity=Category.class,cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn(name="CategoryId")
public Category category;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(length=10)
private int book_id;
#Column(length=128)
private String title;
#Column(length=64)
private String author;
#Column(length=200)
private String description;
#Column(length=10)
private int ISBN;
#Column(length=10)
private float price;
private Date published_Date;
#Lob
#Column
#Basic(fetch = FetchType.LAZY)
private byte[] icon;
//getter and setter
}
This is my Category class:
#Entity
#Table(name="category1")
public class Category {
#Id
#Column(length=12)
#GeneratedValue(strategy=GenerationType.AUTO)
public int CategoryId;
#Column(length=50)
public String CategoryName;
//#JsonBackReference
#OneToMany(mappedBy="category")
private List<Book> books = new ArrayList<Book>();
//getter and setter
}
The relationship between them is one to many.
This is my Category Service class
#Service
#Transactional
public class AdminServiceImpl implements AdminService {
#Autowired
private CategoryDao dao;
#Autowired
private BookDao dao1;
#Override
public List<Category> getAllCategory(){
return dao.findAll();
}
}
My Controller class
#RestController
#RequestMapping("/bookstore")
public class CategoryController {
#Autowired
private AdminService service;
#GetMapping("/GetAllCategory")
private ResponseEntity<List<Category>> getAllCategory() {
List<Category> catlist = service.getAllCategory();
return new ResponseEntity<List<Category>>(catlist, new HttpHeaders(), HttpStatus.OK);
}
}
My category table already has data.When i try to display them it is showing double values.
Displaying values using Postman
The Category table in the Database: Database table
Jackson's ObjectMapper uses the Java bean pattern and it expects the following
public class Foo {
public Object bar;
public Object getBar() {...}
public void setBar(Object bar) {...}
}
The getters and setters start with get and set, respectively, followed by the corresponding field name with its first letter capitalized.
Change
CategoryId to categoryId (first letter lowercase)
and
CategoryName to categoryName

How to send model property, the property is the model too in spring

I have two models.
#Entity
class Product {
#Id
private String id;
private String name;
#ManyToOne(optional = false)
#JoinColumn(name = "category_id", referencedColumnName = "id")
#NotNull(groups = {CREATE.class, UPDATE.class})
private Category category;
...
}
#Entity
class Category {
#Id
private String id;
private String name;
...
}
#RestController
#RequestMapping(path = "/product")
class ProductController {
#RequestMapping(method = RequestMethod.POST)
public void create(#ModelAttribute Product product) {
...
}
}
I want send request to ProductController:
http POST http://localhost:8080/product name=='Product 1' category=1
The param category is id of Category into db, but spring does not understand it.
Is it possible to do this?
Well, your entitiy classes are ok, but it's really weird to see parameters in the POST request especially in so sort as you have it placed here.
Here is my sample that is working properly
public class Product {
private String id;
private String name;
private Category category;
******
}
public class Category {
private String id;
private String name;
*******
}
#RestController
#RequestMapping(path = "/product")
public class ProductController {
#RequestMapping(method = RequestMethod.POST)
public void create(#ModelAttribute Product product) {
Product prd1 = product;
prd1.getId();
}
}
And just in case here is an appConfig:
#Configuration
#EnableWebMvc
public class AppConfig {
}
That is all. Now your contorller is expecting to get a message that is a Product instance.
Let's go onward. It's pretty weird to see parameters in the POST query. I've had some test and they are ok - just pass the data as a request body! Whatever you cose. For instance let's modify controller as it shown below:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void create(#ModelAttribute Product product) {
Product prd1 = product;
prd1.getId();
}
}
And now you have to send a POST message with a body that contains a Product data in a JSON format, i.e
{ "id": 1 }
and it works for all other formats that are supported by spring

Spring 3 mvc #Valid annotation doesn't work with List<Entity> property

I want to update an entity, which has a one-to-many List collection of other entity. When the handler method gets called, the validation doesn't seem to run on the collection. I've read the documentation, and searched stackoverflow, but did not find anything useful.
Model:
#Entity
public class Employee {
#Id
#GeneratedValue
private int employeeId;
#NotEmpty
private String name;
#Min(value=18)
private int age;
#OneToMany(mappedBy="parent",cascade=CascadeType.ALL)
private List<Child> children;
//getters,setters
}
#Entity
public class Child {
#Id
#GeneratedValue
private int childId;
#Column(nullable=false)
#NotNull
#Size(min=1,message="Child's name must not be empty")
private String childName;
#Max(value=18)
private Integer age;
#ManyToOne
#JoinColumn(name="employeeId")
private Employee parent;
//getters,setters
}
In the controller:
#RequestMapping(value = { "/edit/{id}" }, method = RequestMethod.POST)
private String update(#PathVariable int id, ModelMap model, #Valid Employee employee, BindingResult result) {
if (result.hasErrors()) {
return "employee/edit";
}
employeeDao.merge(employee);
return "redirect:../list";
}
The validation works for the simple properties of the Employee bean, but not for the elements in the children list.
How can this be fixed?
Seems like you should decorate your children list with #Valid annotation, as described here.
It should look something like this:
#OneToMany(mappedBy="parent",cascade=CascadeType.ALL)
#Valid
private List<Child> children;

Resources