Spring hibernate ignore json object - spring

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.

Related

Spring Controller Returns Object Incompletely

There are three classes (Course, Lesson, User).
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "usr")
#Data
public class User extends RepresentationModel<User> implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String username;
private String password;
#ElementCollection(targetClass = ERole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
private Set<ERole> roles;
}
#Data
#Entity
#NoArgsConstructor
public class Lesson extends RepresentationModel<Lesson> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String startTime;
private String endTime;
private String dayOfWeek;
#ManyToOne
private User teacher;
}
#EqualsAndHashCode(callSuper = true)
#Data
#Entity
public class Course extends RepresentationModel<Course> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date startDate;
private Date endDate;
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> teachers;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> students;
private String description;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Lesson> lessons;
}
And also RestController (CoursesController). When accessing the server at /courses, I get the correct server response with all fields
.
#RestController
#RequestMapping("/courses")
public class CoursesController {
private final CourseService courseService;
private final UserService userService;
private final LessonService lessonService;
#Autowired
public CoursesController(CourseService courseService, UserService userService, LessonService lessonService) {
this.courseService = courseService;
this.userService = userService;
this.lessonService = lessonService;
}
#GetMapping
#Operation(
summary = "getAllCourses",
description = "Returns all available courses"
)
public ResponseEntity<Page<Course>> getAllCourses(#PageableDefault(sort = "id", size = 5) Pageable pageable) {
try {
Page<Course> coursePage = courseService.findAll(pageable);
for (Course course : coursePage.getContent())
course.add(linkTo(methodOn(CoursesController.class).getCourse(course.getId().toString())).withSelfRel());
return ResponseEntity.ok(courseService.findAll(pageable));
}
catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/{course-id}")
#Operation(
summary = "getCourse",
description = "Returns course by ID"
)
public ResponseEntity<Course> getCourse(#PathVariable ("course-id") String courseId) {
try {
Course course = courseService.getCourseById(courseId);
course.add(linkTo(methodOn(CoursesController.class).getCourse(courseId)).withSelfRel());
return ResponseEntity.ok(course);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
Why, when requesting a course by ID (GET /courses/{id}), does Spring return an incomplete object (despite the fact that I manually added several teachers, students and lessons)?
I need to get all the fields of my object.
My CourseRepository below.
#Repository
#Transactional
public interface CourseRepository extends JpaRepository<Course, Long> {
}
My CourseService below.
#Service
public class CourseService {
private final CourseRepository courseRepository;
private final LessonRepository lessonRepository;
private final UserRepository userRepository;
#Autowired
public CourseService(CourseRepository courseRepository, LessonRepository lessonRepository, UserRepository userRepository) {
this.courseRepository = courseRepository;
this.lessonRepository = lessonRepository;
this.userRepository = userRepository;
}
public Page<Course> findAll(Pageable pageable) {
return courseRepository.findAll(pageable);
}
public Course createCourse(CourseDto courseDto) {
Course course = new Course(courseDto.getStartDate(), courseDto.getEndDate(), courseDto.getName(), courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Optional<Course> getCourseById(String id) {
return courseRepository.findById(Long.parseLong(id));
}
public Course updateCourse(CourseDto courseDto, String id) {
Course course = courseRepository.findById(Long.parseLong(id)).get();
course.setStartDate(courseDto.getStartDate());
course.setEndDate(courseDto.getEndDate());
course.setName(courseDto.getName());
course.setDescription(courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Page<Lesson> getLessonsByCourse(String courseId, Pageable pageable) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
return new PageImpl<>(new ArrayList<>(course.getLessons()), pageable, course.getLessons().size());
}
public Course addLesson(String courseId, LessonDto lessonDto) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
Lesson lesson = new Lesson();
lesson.setStartTime(lessonDto.getStartTime());
lesson.setEndTime(lessonDto.getFinishTime());
lesson.setDayOfWeek(lessonDto.getDayOfWeek());
lesson.setTeacher(userRepository.getUserById(lessonDto.getTeacherId()));
lessonRepository.saveAndFlush(lesson);
System.out.println(lesson);
course.getLessons().add(lesson);
return courseRepository.saveAndFlush(course);
}
public void deleteCourse(String id) {
courseRepository.deleteById(Long.parseLong(id));
}
}
Which I would (or might) expect as well. I would links to be generated for those additional relationshps (at least normally with Spring Data RESt handling this is what would happen). I wonder what happens if you ditch the RepresentationModel from your JPA model and just expose Course then. As stated you don't really want your JPA and HATEOAS stuff to be intertwined. You want to have a specialized projection/dto to expose. WHy does it work for your findAll. well you aren't adding links to it (although you think it does but your findAll executes twice!).
Removed RepresentationModel from User class.
Thx to #M.Deinum

Hibernate: How to display data from multiple table

I am new in spring/hibernate technologies, I have tried to find an information about it, but failed, so if you can help I will be so thankful!
I need to display a JSON response in browser of multiple tables, one of the table has primary key for another one.
My entities:
#Entity
#Table
#ToString
public class Book {
#Id
#GeneratedValue(strategy = AUTO)
#JsonView(Views.IdName.class)
private Long book_id;
#JsonView(Views.IdName.class)
private String name;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="author_id")
#JsonView(Views.IdName.class)
private Author author;
// ....get/set methods
Another one:
#Entity
#Table
#ToString
public class Page {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "book_id")
private Book book;
// ...get/set methods
My controllers:
#RestController
#RequestMapping("books")
public class BookController {
private final BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
#GetMapping
#JsonView(Views.IdName.class)
public List<Book> getAll() {
return bookRepo.findAll();
}
#GetMapping("{id}")
public Book getOne(#PathVariable("id") Book book) {
return book;
}
}
Another one:
#RestController
#RequestMapping("authors")
public class AuthorController {
private final AuthorRepo authorRepo;
#Autowired
public AuthorController(AuthorRepo authorRepo) {
this.authorRepo = authorRepo;
}
#GetMapping
public List<Author> getAll() {
return authorRepo.findAll();
}
#GetMapping("{id}")
public Optional<Author> getOne(#PathVariable("id") Long id) {
return authorRepo.findById(id);
}
}
And also repo for interaction with DB (they are the similar):
public interface AuthorRepo extends JpaRepository<Author, Long> {
}
So when I make a request for get all books, I take the following JSON:
enter image description here
Bit I want different result, something like:
[
{
"book_id" : 1,
"name": "name 1 book",
"author" :
{
"author_id" : 1,
"name": "some_name"
}
}
]
Also, when I tried to make a request for /authors/1, I will get the following response (something like recursion) :
enter image description here
So any help how can I handle with it? Thank you!
You can use a #NoRepositoryBean
like in this example:
#NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {
#Query("select new com.example.YourObjectWithConstructor(e.attribute, sub.sub_attribute) from entity e inner join e.subtable sub where e.attribute = ?1")
List<YourObjectWithConstructor> findAllByAttribute(String attribute);
}
My example may not be 100% correct, I did not check the syntax. Feel free to explore it
Check this also:
JPQL Create new Object In Select Statement - avoid or embrace?

Why do not I get the id when mapping from DTO to entity with mapstruct?

I have an API with spring boot and I use mapstruct and I just want to update the Person entity. For this, having PersonDto update Person.
What I have so far:
Mapper:
#Mapper
public interface PersonMapper {
PersonDto toPersonDto(Person person);
Person toPerson(PersonDto personDto);
Person updatePersonFromDto(PersonDto persoonDto, #MappingTarget
Person document);
}
In service layer:
Find Person:
public PersonDto updatePerson(Long personId) {
PersonDto personDto = personService.findById(personId)
.orElseThrow(() -> new PersonNotFoundException(id));
personDto.set(...) //set others properties
Person person = personMapper.toPerson(personDto);
person = personMapper.updatePersonFromDto(personDto, person);
personRepository.save(person);
return personMapper.toPersonDto(person);
}`
My question, is there a strategy or a better way of update an entity from a DTO?
Edit:
I was able to solve a part, now I do not lose the id, but I still create a new object instead of updating it. The id is in AbstractPersistableEntity.
#Entity
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Person extends AbstractPersistableEntity<ID> implements Serializable {
#Column
private String name;
#Column
private String lastName;
#Column
private Integer age;
}
public class PersonDto {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;
private String name;
private String lastName;
private Integer age;
}
Maybe it is not the most optimal or correct solution but it works. I made the following modifications to the mapper:
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
Person updatePersonFromDto(PersonDto persoonDto, #MappingTarget
Person document);
#ObjectFactory
default Person updatePerson(PersonDto personDto, Person person){
if (personDto != null){
Long id = person.getId();
Person resultPerson = updatePersonFromDto(personDto, person);
resultPerson.setId(id);
return resultPerson;
}
return null;
}
Then from the service just call:
person = personMapper.updatePerson(personDto, person);

How can save order detail associated with the user and How can I return order data associated with the user details based the url parameters?

I have created the User and Order entities as bellow. What I want to achieve is that if http://localhost:8080/users/username? is given I want to return only the user detail based on username provided. if http://localhost:8080/users/username?detail=true, I want to return user detail and order details for the username provided. How can I achieve this?
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String gender;
private String lastLoggedIn;
#OneToMany
List<Order> listOfOrder;
//constructors
//getter and setter
}
Order.java
#Entity
public class Order
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private float amount;
private String createdAt;
private String deliveredDate;
//constructors
//getter and setter
}
Controller.java
//CREATE CUSTOMER
#RequestMapping(method = POST, value = "/create")
public ResponseEntity createCustomerDetails(#RequestParam String userName, String password, String firstName,
String lastName, String gender) {
String lastLogged = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = new User(userName, password, firstName, lastName, gender, lastLogged);
userRepository.save(user);
return ResponseEntity.status(OK).body(user.getId() + " User were successfully saved");
}
//CREATE ORDER
#RequestMapping(method = POST, value = "/order/{userName}")
public ResponseEntity createOrder(#PathVariable ("userName") String userName, #RequestParam float amount)
{
String createdAt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
String deliveredDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = orderService.findUser(userName);
if (!user.equals(null))
{
Order order = new Order(amount,createdAt,deliveredDate);
user.getListOfOrder().add(order);
return ResponseEntity.status(OK).body("order details were saved under "+user.getUserName() + " "+user.getFirstName());
}
return ResponseEntity.status(NOT_FOUND).body(null + " was not found");
}
//GET THE USER DETAILS
#RequestMapping(method = GET, value = "/users/{userName}")
public ResponseEntity getUserDetail(#PathVariable("userName") String userName,
#RequestParam(defaultValue ="none", required = false) String detail) {
if (!detail.equals("none")){
return .....;
}else {
return ........;
}
}
UserRepository
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
If you're ok with doing the serialization manually, you can employ JsonView to determine what gets serialized.
https://www.baeldung.com/jackson-json-view-annotation
User.java
import com.fasterxml.jackson.annotation.JsonView;
public class User {
#JsonView(Views.Lite.class)
private String name;
#JsonView(Views.Full.class)
private List<Order> orders;
}
Views.java
public class Views {
public static class Lite {}
public static class Full extends Lite {}
}
UserController.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private ObjectMapper mapper;
#GetMapping("/user/{username}")
public ResponseEntity<String> getUserDetail(#PathVariable String username, #RequestParam(required = false) String detail) throws JsonProcessingException {
User user = userRepository.findByUserName(username);
Class viewClass = Views.Lite.class;
if (!StringUtils.isEmpty(detail)) {
viewClass = Views.Full.class;
}
return ResponseEntity.status(HttpStatus.OK)
.body(mapper.writerWithView(viewClass).writeValueAsString(user));
}
}

Why the record is posted twice in the database?

Can you tell me, why the record is posted twice in the database. I think. this happens because I use save() method. But shouldn't I save the master-entity and dependent-entity separately?
Controller method:
#RequestMapping(value = "/addComment/{topicId}", method = RequestMethod.POST)
public String saveComment(#PathVariable int topicId, #ModelAttribute("newComment")Comment comment, BindingResult result, Model model){
Topic commentedTopic = topicService.findTopicByID(topicId);
commentedTopic.addComment(comment);
// TODO: Add a validator here
if (!comment.isValid() ){
return "//";
}
// Go to the "Show topic" page
commentService.saveComment(comment);
return "redirect:../details/" + topicService.saveTopic(commentedTopic);
}
Services:
#Service
#Transactional
public class CommentService {
#Autowired
private CommentRepository commentRepository;
public int saveComment(Comment comment){
return commentRepository.save(comment).getId();
}
}
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
}
Model:
#Entity
#Table(name = "T_TOPIC")
public class Topic {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
#Enumerated(EnumType.STRING)
private Tag topicTag;
private String name;
private String text;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
}
#Entity
#Table(name = "T_COMMENT")
public class Comment
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="TOPIC_ID")
private Topic topic;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
private String text;
private Date creationDate;
}
In this concrete case, you do not need to save the master and the client.
Saving the master or the client would be enough (with this concrete mapping)
But I think the main problem is that you do not have a good equals method in your Comment so your ORM Provider think that there are two different comments, and therefore store them twice.

Resources