Im trying to save data to the database, but instead of it JPA is saving null to the database, I am usually doing it with dto, but since it s a very small project, I want to do it without it
Entity
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String department;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_COURSE_TABLE",
joinColumns = {
#JoinColumn(name="student_id",referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "couse_id",referencedColumnName = "id")
})
#JsonManagedReference
private Set<Course> courses;
}
DAO
#Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
Controller
#RestController
#RequestMapping("/api")
public class StudentCourseController {
private final StudentRepository studentRepository;
private final CourseRepository courseRepository;
public StudentCourseController(StudentRepository studentRepository,
CourseRepository courseRepository) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository;
}
#PostMapping
public Student addStudent (Student student){
return studentRepository.save(student);
}
}
and in my application.properties
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialec
t
Make sure is deserialization correct in controller method "addStudent" - If You want to pass Student entity in request body, add annotation #RequestBody to method parameter like:
#PostMapping
public Student addStudent (#RequestBody Student student){
return studentRepository.save(student);
}
If You do not do that - there is possibility to null/empty parameter, what can lead to saving nulls into db.
By the way:
Consider using DTO or Request classes to pass entity in/out your REST application - it will help you avoid circular reference in future and problems with de/serialization your entity.
Consider using ResponseEntity instead of returning object to output - method with ResponseEntity should be like:
#PostMapping
public ResponseEntity<Student> addStudent (#RequestBody Student
student){
return ResponseEntity.ok(studentRepository.save(student));
}
Related
I'm trying to lazily fetch a ManyToMany relationship (Courses - Students) from the Service and pass the result to the Controller. While i'm in the Service, no LazyInitializationException is thrown, thanks to the #Transactional annotation. However, while i'm in the Controller the LazyInitializationException is thrown (while getting Course.students), because the Session was closed. How can i resolve this issue, without eagerly fetch the Collection? That's my code: Couse Model
#Entity
#Getter
#Setter
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany
#JoinTable(name = "COURSES_STUDENTS",
joinColumns = {#JoinColumn(name = "COURSE_ID")},
inverseJoinColumns = {#JoinColumn(name = "STUDENT_ID")})
private Set<Student> students;
public Course() {
this.students = new HashSet<>();
}
Student Model
#Entity
#Getter
#Setter
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "students")
private Set<Course> courses;
public Student() {
this.courses = new HashSet<>();
}
}
Course Repository
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}
Course Service
#Service
public class CourseService {
private final CourseRepository courseRepository;
#Autowired
public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#Transactional
public ResponseEntity<List<Course>> findAll() {
return this.courseRepository.findAll().isEmpty() ? ResponseEntity.noContent().build()
: ResponseEntity.ok(this.courseRepository.findAll());
}
}
Course Controller
#Controller
#RequestMapping("/")
public class CourseController {
private final CourseService courseService;
#Autowired
public CourseController(CourseService courseService) {
this.courseService = courseService;
}
#GetMapping
public ResponseEntity<List<Course>> index() {
return this.courseService.findAll();
}
}
application.properties
spring.datasource.url=jdbc:h2:~/database;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.open-in-view=false
spring.mvc.hiddenmethod.filter.enabled=true
logging.level.org.springframework.web=DEBUG
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Thanks in advance.
So there are 2 approaches :
What is this spring.jpa.open-in-view=true property in Spring Boot?
This is bad for performance and must be avoided at all costs.
use jpql queries to join fetch lazy collections needed in DAO layer so they are available in the controller when you need them to be.
All in all, do not use transactional to keep the db session open to fetch lazy collections. Just join fetch lazy collections in db / dao layer to have the data needed for each endpoint available.
If you want have a look here for how to use join fetch How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller
I'm using Spring MVC with Spring data.
Simple example of my problem:
My dao Service class:
#Service
#AllArgsConstructor
#Transactional
public class FooService{
private FooRepository fooRepo;
public Foo save(Foo foo){
return fooRepo.save(foo);
}
}
and controller:
#Controller
#AllArgsConstructor
#Transactional //if I remove this, method add does not save a foo.
//But I don't understand why, because FooService already has #Transactional annotation
public class FooController{
private FooService fooService;
#PostMapping("/add")
public String add(#RequestParam("programName") String programName, #RequestParam("id") long id){
Foo foo = fooService.findById(id).get();
foo.setProgramName(programName);
fooService.save(foo);
return "somePage";
}
}
If I remove #Transaction annotation from controller class, method save will not update foo object.
And I don't understand why I should mark controller by #Transactional annotation if I already mark service class by this annotation?
############ UPDATE ####################
Simple detailed description:
I have Program and Education entities. One Program has many Education, Education entity has foreign key program_id.
There is a page with Program form, there are fields: program id, program theme,..., and field with a list of education id separated by commas.
I'm trying to update the education list at the program, so I add a new education id at the page form and click save. Through debugger I see, that new education has appeared in the program, but changes do not appear in the database.
#Controller
#RequestMapping("/admin/program")
#AllArgsConstructor //this is lombok, all services autowired by lombok with through constructor parameters
#Transactional//if I remove this, method add does not save a foo.
//But I don't understand why, because FooService already has #Transactional annotation
public class AdminProgramController {
private final ProgramService programService;
private final EducationService educationService;
#PostMapping("/add")
public String add(#RequestParam("themeName") String themeName, #RequestParam("orderIndex") int orderIndex,
#RequestParam(value = "educationList", defaultValue = "") String educationList,
#RequestParam(value = "practicalTestId") long practicalTestId){
saveProgram(themeName, orderIndex, educationList, practicalTestId);
return "adminProgramAdd";
private Program saveProgram(long programId, String themeName, int orderIndex, String educationList, long practicalTestId){
List<Long> longEducationList = Util.longParseEducationList(parsedEducationList); //this is list of Education id separeted by commas that I load from page form
//creating new program and set data from page form
Program program = new Program();
program.setId(programId);
program.setThemeName(themeName);
program.setOrderIndex(orderIndex);
//starting loop by education id list
longEducationList.stream()
.map(educationRepo::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(edu->{
//linking program and education
program.getEducationList().add(edu);
edu.setProgram(program);
});
//saving new program or updating by service if there is one already
Program savedProgram = programService.save(program);
//saving education with updated program
for(Education edu : savedProgram.getEducationList())
{
educationService.save(edu);
}
return savedProgram;
}
}
ProgramService:
#Service
#AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
#Transactional
public class ProgramService {
private ProgramRepo programRepo;
//other code here.....
public Program save(Program program) {
Optional<Program> programOpt = programRepo.findById(program.getId());
//checking if the program is already exist, then update it paramateres
if(programOpt.isPresent()){
Program prgm = programOpt.get();
prgm.setThemeName(program.getThemeName());
prgm.setOrderIndex(program.getOrderIndex());
prgm.setPracticalTest(program.getPracticalTest());
prgm.setEducationList(program.getEducationList());
return programRepo.save(prgm);
}
//if not exist then just save new program
else{
return programRepo.save(program);
}
}
}
Education service
#Service
#AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
#Transactional
public class EducationService {
private EducationRepo educationRepo;
//other code here....
public Education save(Education education){
return educationRepo.save(education);
}
}
Program entity:
#Entity
#ToString(exclude = {"myUserList", "educationList", "practicalTest"})
#Getter
#Setter
#NoArgsConstructor
public class Program implements Comparable<Program>{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "theme_name")
private String themeName;
#Column(name = "order_index")
private int orderIndex; //from 1 to infinity
#OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
#OrderBy("orderIndex asc")
private List<Education> educationList = new ArrayList<>();
#OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
private List<MyUser> myUserList = new ArrayList<>();
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "test_id")
private PracticalTest practicalTest;
public Program(int orderIndex, String themeName) {
this.orderIndex = orderIndex;
this.themeName = themeName;
}
public Program(long id) {
this.id = id;
}
//other code here....
}
Education entity:
#Entity
#ToString(exclude = {"program", "myUserList"})
#Getter
#Setter
#NoArgsConstructor
public class Education implements Comparable<Education>{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String link;
#Column(name = "order_index")
private int orderIndex;
private String type;
private String task;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "program_id")
private Program program;
#OneToMany(mappedBy = "education", fetch = FetchType.LAZY)
private List<MyUser> myUserList = new ArrayList<>();
public Education(String link, int orderIndex, String task, Program program) {
this.link = link;
this.orderIndex = orderIndex;
this.task = task;
this.program = program;
}
//other code here....
}
Program repo:
#Repository
public interface ProgramRepo extends CrudRepository<Program, Long> {
Optional<Program> findByPracticalTest(PracticalTest practicalTest);
Optional<Program> findByOrderIndex(int orderIndex);
List<Program> findByIdBetween(long start, long end);
}
Education repo:
#Repository
public interface EducationRepo extends CrudRepository<Education, Long> {
Optional<Education> findByProgramAndOrderIndex(Program program, int orderIndex);
#Query("select MAX(e.orderIndex) from Education e where e.program.id = ?1")
int findLastEducationIndexByProgramId(long programId);
}
I think the problem is program object created in one transaction and saved in another. That's why if I put Transactional on controller it works. There are two ways to solve the problem:
Without transactional on the controller: then I must save education object at first, because it has program id field and then save the program object.
With transactional on controller: then saving order has no matter, because saving object occurs in one transaction
I was trying to implement bi-directional relationships bettwen my entities.
Student
#Table(name = "students")
#Entity
public class Student {
#Id
// #GeneratedValue(strategy = GenerationType.AUTO)
private long album;
#NotNull
private String name;
#NotNull
private String surname;
#OneToMany(mappedBy = "student", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private List<StudentSection> studentSections;
#Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
public void addSection(Section section){
if(this.studentSections == null){
this.studentSections = new ArrayList<>();
}
StudentSection studentSectionToAdd = new StudentSection();
studentSectionToAdd.setStudent(this);
studentSectionToAdd.setSection(section);
this.studentSections.add(studentSectionToAdd); //here
section.addStudentSection(studentSectionToAdd);
}
}
the connecting entity in a ManyToMany relationship
#Table(name = "student_section")
#Entity
public class StudentSection {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Integer grade;
private Date date;
#NotNull
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "student_id")
private Student student;
#NotNull
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "section_id")
private Section section;
}
and Section
#Table(name = "sections")
#Entity
public class Section {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String name;
#NotNull
private Integer sizeOfSection;
#NotNull
private Boolean isActive;
#OneToMany(mappedBy = "section", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private List<StudentSection> studentSections;
void addStudentSection(StudentSection studentSection){
if(this.studentSections == null){
this.studentSections = new ArrayList<>();
}
this.studentSections.add(studentSection);
}
}
I ran into a problem with the Student.addSection() method. When trying to execute it I got an error on the this.studentSections.add(studentSectionToAdd); line, saying
failed to lazily initialize a collection of role: Student.studentSections, could not initialize proxy - no Session
I read about it and found out that the best way to fix this is to add the #Transactional annotation to the method, however it didnt change anything and I cant get it to work.
I also tried moving the Student.addSection() method to
StudentServiceImpl
#Service
#Primary
public class StudentServiceImpl implements StudentService {
protected StudentRepository studentRepository;
#Autowired
public StudentServiceImpl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Override
#Transactional(propagation = Propagation.REQUIRED, readOnly = true, noRollbackFor = Exception.class)
public void addSection(Student student, Section section) {
if (student.getStudentSections() == null) {
student.setStudentSections(new ArrayList<>());
}
StudentSection studentSectionToAdd = new StudentSection();
studentSectionToAdd.setStudent(student);
studentSectionToAdd.setSection(section);
student.getStudentSections().add(studentSectionToAdd);
//section.addStudentSection(studentSectionToAdd);
}
}
but I still got the error.
I am also using CrudRepository to retrive entities from the database.
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
Student findByName(String name);
}
This is where I call the method
#Component
public class DatabaseLoader implements CommandLineRunner {
private final StudentRepository studentRepository;
private final SectionRepository sectionRepository;
private final StudentSectionRepository studentSectionRepository;
private final StudentService studentService;
#Autowired
public DatabaseLoader(StudentRepository studentRepository, SectionRepository sectionRepository, StudentSectionRepository studentSectionRepository,
StudentService studentService) {
this.studentRepository = studentRepository;
this.sectionRepository = sectionRepository;
this.studentSectionRepository = studentSectionRepository;
this.studentService = studentService;
}
#Override
public void run(String... strings) throws Exception {
//Testing entities
Student student = new Student();
student.setAlbum(1L);
student.setName("student");
student.setSurname("test");
this.studentRepository.save(student);
Section section = new Section();
section.setName("section");
section.setSizeOfSection(10);
section.setIsActive(true);
this.sectionRepository.save(section);
//end
//Adding Student to a Section test
Student student1 = studentRepository.findByName("student");
//student1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
Section section1 = sectionRepository.findByName("section");
//section1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
studentService.addSection(student1, section1);
this.studentRepository.save(student1);
//end test
}
}
Also when I retrive StudentSection lists from the database here and set them im both objects before adding a new relationship it works fine, but this is not really the solution I am going for.
The problem is that every call from the run() method to studentRepository and studentService are separate sessions/transactions.
It's virtually as-if you did this:
...
beginTransaction();
this.studentRepository.save(student);
commit();
...
beginTransaction();
this.sectionRepository.save(section);
commit();
beginTransaction();
Student student1 = studentRepository.findByName("student");
commit();
beginTransaction();
Section section1 = sectionRepository.findByName("section");
commit();
// This does it's own transaction because of #Transactional
studentService.addSection(student1, section1);
beginTransaction();
this.studentRepository.save(student1);
commit();
Since transaction = session here, it means that student1 is detached, and that the lazy-loaded studentSections collection cannot be loaded on-demand outside the session, and hence the code fails.
Inserting a new student and a new section and associating them should really be one transaction, so if a later step fails, it's all rolled back.
Which basically means that you want the entire run() method to be one transaction, so in your case, it is the run() method that should be #Transactional, not the addSection() method.
Generally, in a 3-tiered approach, you would put transaction boundaries on service layer:
Presentation tier. This is #Controller classes, or the run() method for a simple command-line program.
Logic tier. This is #Service classes. This is where you put #Transactional, so each service call is an atomic transaction, i.e. it either succeeds or it fails, as far as the database updates are concerned, no half updates.
Data tier. This is #Repository and #Entity classes.
As such, you should keep the instantiation and initialization of the Student and Section objects in the run() method, but the rest of the code, incl. save(), should be moved to a single method in a #Service class.
About this
#Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
public void addSection(Section section){
#Transactional works only for spring-managed beans and Entities are not managed by spring.
You get this exception because you try load a lazy relations outside a session (because your entity is actually in detached-state).
To re-attach --> entityManager.merge(student);
But the best thing to do is to load the relation at query-time. By using EntityGraph for example -->
#EntityGraph(attributePaths="studentSections")
Student findByName(String name);
I am using OneToMany relationship in spring data Jpa and testing the api using postMan
#Entity
#Table(name = "book_category")
public class BookCategory {
ParentEntity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "bookCat_id")
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy="bookCategory")
private Set<Book> books;
public BookCategory(String name, Set<Book> books) {
this.name = name;
this.books = books;
}
// getter and setter
}
ChildEntity
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id")
private Long bookId;
private String name;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "bookCat_id")
BookCategory bookCategory;
public Book() {
}
//getter and Setter
}
ControllerClass
#RestController
#RequestMapping(value = "/bookCategory")
public class BookController {
#Autowired
BookCategoryRepository bookCategoryRepository;
#Autowired
BookRepository bookRepository;
#PostMapping("/addBookCategory")
public BookCategory savePerson(#Valid #RequestBody BookCategory bookCategory){
return bookCategoryRepository.save(bookCategory);
}
}
calling Rest Api from postman and passing json as
{
"name":"Category 1",
"books":[
{"name" : "Hello Koding 1"},
{"name":"Hello Koding 2"}
]
}
Following Query is executing by hibernate query is also correct while i am calling rest point the thing
Hibernate: insert into book_category (name) values (?)
Hibernate: insert into book (book_cat_id, name) values (?, ?)
it is not inserting book_cat_id, in book_cat_id it is passing null so null is getting store
Data stored in database
book_category Parent Table in database
book(ChildTable)
book(ChildTable)
I want to get Child Table Like
I want my table like this
The problem is that you are not setting parent in child object. Somewhere in the code you should call the
public void setBookCategory(BookCategory bookCategory) { ... }
method of the Book entity.
I'd suggest to resolve this issue by using DTOs in controller and mapping them to entities in service layer, as using peristent entities as parameters of http requests can lead to serious security vulnerabilities as explained here.
EDIT: Alternatively, even thought I would not encourage this solution would be to modify the savePerson method like this
#PostMapping("/addBookCategory")
public BookCategory savePerson(#Valid #RequestBody BookCategory bookCategory){
bookCategory.getBooks().forEach(book -> book.setBookCategory(bookCategory));
return bookCategoryRepository.save(bookCategory);
}
Given a User entity with the following attributes mapped:
#Entity
#Table(name = "user")
public class User {
//...
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "user_email")
private String email;
#Column(name = "user_password")
private String password;
#Column(name = "user_type")
#Enumerated(EnumType.STRING)
private UserType type;
#Column(name = "user_registered_date")
private Timestamp registeredDate;
#Column(name = "user_dob")
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
//...getters and setters
}
I have created a controller method that returns a user by ID.
#RestController
public class UserController {
//...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userService.findOne(id);
if (user != null) {
return new ResponseEntity<User>(user, HttpStatus.OK);
}
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
//...
}
A service in my business logic layer.
public class UserServiceBean implements UserService {
//...
public User findOne(Long id) {
User user = userRepository.findOne(id);
return user;
}
//...
}
And a repository in my data layer.
public interface UserRepository extends JpaRepository<User, Long> {
}
This works fine, it returns everything about the user, but I use this in several different parts of my application, and have cases when I only want specific fields of the user.
I am learning spring-boot to create web services, and was wondering: Given the current implementation, is there a way of picking the attributes I want to publish in a web service?
If not, what should I change in my implementation to be able to do this?
Thanks.
Firstly, I agree on using DTOs, but if it just a dummy PoC, you can use #JsonIgnore (jackson annotation) in User attributes to avoid serializing them, for example:
#Entity
#Table(name = "user")
public class User {
//...
#Column(name = "user_password")
#JsonIgnore
private String password;
But you can see there, since you are not using DTOs, you would be mixing JPA and Jackson annotations (awful!)
More info about jackson: https://github.com/FasterXML/jackson-annotations