How to lazy fetch with spring data repository? - spring

Database Tables
post
tag
ref_post_tag
post and tag has a Many-to-Many relationship
Entities
Post
#Entity
#Table(name = "post")
public class Post implements Serializable{
private static final long serialVersionUID = 1783734013146305964L;
public enum Status {
DRAFT, REMOVED, LIVE;
}
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private String id;
#Column(name = "title")
private String title;
#Column(name = "create_time")
private LocalDateTime createTime;
#Column(name = "update_time")
private LocalDateTime updateTime;
#Column(name = "content")
private String content;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private Status status;
#ManyToMany
#JoinTable(
name = "ref_post_tag",
joinColumns = #JoinColumn(name="post_id",referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name="tag_id", referencedColumnName = "id"))
private List<Tag> tagList;
...
}
Tag
#Entity
#Table(name="tag")
public class Tag implements Serializable{
private static final long serialVersionUID = -7015657012681544984L;
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "tagList")
private List<Post> postList;
public Integer getId() {
return id;
}
...
}
Tag Repo
public interface TagRepo extends CrudRepository<Tag, Integer>{
}
service implementation
#Service
public class TagServiceImpl implements TagService{
#Autowired
private TagRepo tagRepo;
#Override
public void addTag(Tag tag) {
tagRepo.save(tag);
}
#Override
public Tag getTag(Integer id) {
Tag tag = tagRepo.findOne(id);
return tag;
}
#Override
public List<Tag> findAllTags() {
return CollectionUtil.toArrayList(tagRepo.findAll());
}
}
sample test (Updated)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestContextConfiguration.class)
#Transactional
public abstract class ServiceTest {
}
public class TagServiceTest extends ServiceTest{
#Autowired
private TagService tagService;
#Autowired
private TagRepo tagRepo;
#Test
#Transactional
public void addTag() throws Exception {
Tag tag = new Tag();
tag.setName("new tag");
tag.setDescription("this is a new tag");
tagService.addTag(tag);
Tag tagCreated = tagRepo.findOne(tag.getId());
assertNotNull(tagCreated);
assertEquals(tagCreated.getName(), tag.getName());
}
#Test
public void getTag() throws Exception {
Tag tag = tagService.getTag(1); // tag "java" has an ID of "1"
assertNotNull(tag);
assertEquals(tag.getName(), "java");
assertEquals(143,tag.getPostList().size()); // 143 posts under tag "java"
}
}
Question
The sample test case passes. It means that the postList in fetched Tag is also eagerly fetched and filled.
Is Spring data repository's methods eagerly fetching by default?
If yes, what is the best way to change this to lazy fetching?

Related

Spring JPA - How can I make JpaRepository queries using an #Embedded property?

I'm trying to make a existsBy query using a property that comes from an embedded class, but I'm receiving "No property 'cpf' found for type 'Patient'".
The class Patient uses the Person class as embedded.
Person.java
#Embeddable
#Data
public class Person {
#Column(nullable = false, length = 11)
private String cpf;
#Column(name = "full_name", nullable = false, length = 60)
private String fullName;
#Column(nullable = false)
private String birthdate;
#Column(name = "email", nullable = true, length = 30)
private String emailAddress;
#Column(name = "cellphone_number", nullable = true, length = 11)
private String cellphoneNumber;
}
Patient.java
#Data
#Entity
#Table(name = "tb_patient")
public class Patient implements Serializable {
#Serial
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "patient_id")
private UUID id;
#Column
private LocalDateTime registrationDate;
#Embedded
private Person Person;
}
PatientController.java (part of)
#PostMapping
public ResponseEntity<Object> savePatient(#RequestBody Person person) {
if(patientService.existsByCpf(person.getCpf())) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("CONFLICT: CPF number is already in use!");
}
var patientModel = new Patient();
BeanUtils.copyProperties(person, patientModel);
patientModel.setRegistrationDate(LocalDateTime.now(ZoneId.of("UTC")));
return ResponseEntity.status(HttpStatus.CREATED).body(patientService.save(patientModel));
}
PatientService.java (part of)
#Service
public class PatientService {
final PatientRepository patientRepository;
public PatientService(PatientRepository patientRepository) {
this.patientRepository = patientRepository;
}
public boolean existsByCpf(String cpf) {
return patientRepository.existsByCpf((cpf));
}
PatientRepository.java
#Repository
public interface PatientRepository extends JpaRepository<Patient, UUID> {
boolean existsByCpf(String cpf);
}
How can I pass the #Embedded properties to the #Repository?
You can try separate by _ embedded filed name and it's filed.
#Repository
public interface PatientRepository extends JpaRepository<Patient, UUID> {
boolean existsByPerson_Cpf(String cpf);
}

How Can I mapping DTOs using mapstruct?

I am tring to mapping entity datas to DTOs using mapstruct.
And with these sources, I could map id,title datas.
But the problem is.... I can not map userName using these sources.
How can I resolve this problem??
#Entity // DB와의 연결을 위하여
#Data // getter setter
public class Board {
#Id // id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min=2, max=30)
private String title;
#Length(min=20)
#Lob
#Column(columnDefinition="TEXT", nullable = false)
private String content;
#ManyToOne
#JoinColumn(name="userId", referencedColumnName = "id")
private User user;
}
#Builder
#AllArgsConstructor
#Data
public class BoardListDto {
private Long id;
private String title;
private String userName;
}
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name.value")
List<BoardListDto> toDtos(List<Board> board);
}
public interface EntityMapper <D, E> {
E toEntity(D dto);
D toDto(E entity);
// Entity업데이트 시 null이 아닌 값만 업데이트 하도록 함.
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(D dto, #MappingTarget E entity);
List<D> toDtos(List<E> entity);
}
no need to implement toDtos method for this. This code should be enough and Mapstruct will handle the rest alone.
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name")
BoardListDto toDto(Board board);
}

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

How to get specific inheritant entity using generic dao and service layer

Working on my spring mvc project I face following issu:
I have UnitAppUser, VStanAppUser and RjuAppUser entity classes which extend User entity. User entity stores some general informations. Rest of inheritant entities stores the references to the particular entities (UnitAppUser has a field Unit type, VStanAppUser has a VStan type so on).
Here is my parent User entity
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="app_user")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6628717324563396999L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#Column(name="SSO_ID", unique=true, nullable=false)
private String ssoId;
#NotEmpty
#Column(name="PASSWORD", nullable=false)
private String password;
#NotEmpty
#Column(name="FIRST_NAME", nullable=false)
private String firstName;
#NotEmpty
#Column(name="LAST_NAME", nullable=false)
private String lastName;
#NotEmpty
#Column(name="EMAIL", nullable=false)
private String email;
}
Here's my child classes:
#Entity
#Table(name = "unit_app_user")
public class UnitAppUser extends User implements Serializable {
#JoinColumn(name = "UNITWORK", referencedColumnName = "ID")
#ManyToOne(optional = false)
private UnitDepart unitdepart;
public UnitDepart getWorkat() {
return unitdepart;
}
public void setWorkat(UnitDepart unitdepart) {
this.unitdepart = unitdepart;
}
}
#Entity
#Table(name="rju_app_user")
public class RjuAppUser extends User implements Serializable{
#JoinColumn(name = "workrju", referencedColumnName = "id")
#ManyToOne(optional = false)
private Rju rju;
public Rju getWorkat() {
return rju;
}
public void setWorkat(Rju rju) {
this.rju = rju;
}
}
and finally my VStan entity:
#Entity
#Table(name="vstan_app_user")
public class VstanAppUser extends User implements Serializable{
#JoinColumn(name = "WORKSTATION", referencedColumnName = "kod")
#ManyToOne(optional = false)
private Vstan vstan;
public Vstan getWorkat() {
return vstan;
}
public void setWorkat(Vstan vstan) {
this.vstan = vstan;
}
}
How to write generic dao and service to get specific entity?
As a result I should have something like this
userService.findBySSOId("somessoId").getWorkat() //should return UnitDepart, Rju or VStan

JPA repository null pointer exception for many to one mapping with composite primary key

Post class
one to many mapping
Composite primary key using id
I am getting null pointer exception when I make get request for getting comments
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Size(max = 100)
#Column(unique = true)
private String title;
#NotNull
#Size(max = 250)
private String description;
#NotNull
#Lob
private String content;
#NotNull
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "posted_at")
private Date postedAt = new Date();
#NotNull
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_updated_at")
private Date lastUpdatedAt = new Date();
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "post")
private Set<Comment> comments = new HashSet<>();
public Post() {
}
public Post(String title, String description, String content) {
this.title = title;
this.description = description;
this.content = content;
}
//getters and setters
}
Comment class
many to one mapping with composite primary keys using #Idclass
#Entity
#IdClass(CommentId.class)
#Table(name = "comments")
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Lob
private String text;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "post_id", nullable = false)
private Post post;
public Comment() {
}
public Comment(String text) {
this.text = text;
}
//getters and setters
}
Id class
CommentId
public class CommentId implements Serializable {
private static final long serialVersionUID = 1L;
private Post post;
private Long id;
public CommentId(Post post, Long id) {
super();
this.post = post;
this.id = id;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result+ ((post == null) ? 0 : post.hashCode());
result = prime * result ;
return result;
}
public boolean equals(Object object) {
if (object instanceof CommentId) {
CommentId pk = (CommentId)object;
return id.equals(pk.id) && post == pk.post;
} else {
return false;
}
}
//getters and setters
}
repositories
PostRepository
CommentRepository
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
}
#Repository
public interface CommentRepository extends JpaRepository<Comment, Long>
{
}
Controller class get request and I am using mysql database
#RestController
#RequestMapping("/demo")
public class Controller {
#Autowired
PostRepository ps;
CommentRepository cs;
#GetMapping("/post")
public List<Post> getAll(){
return ps.findAll();
}
#GetMapping("/comment")
public List<Comment> getAllcom(){
return cs.findAll();
}
}

Resources