Spring Data JPA ManyToOne Bidirectional - java

Problem: it works till i try to add Student obejcts to Database, but the tables are being created correctly .
I can't simplify the post any further. But it's mainly code that doesn't require a lot of reading, it's a simple spring data repository service model. I posted it all due to the fact idk what am i doing wrong. Problem is in the JPA mapping.
I got the example from over here http://www.java2s.com/Tutorial/Java/0355__JPA/OneToManyBidirectional.htm
MDOELS
#Entity
public class Department {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy="department")
private Collection<Student> students;
public Department() {
}
public Department(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String deptName) {
this.name = deptName;
}
public Collection<Student> getStudents() {
return students;
}
public void setStudent(Collection<Student> students) {
this.students = students;
}
public String toString() {
return "Department id: " + getId() +
", name: " + getName();
}
}
#Entity
public class Student {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
#ManyToOne (cascade=CascadeType.ALL)
private Department department;
public Student() {
}
public Student(String name, Department department) {
this.name = name;
this.department = department;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String toString() {
return "\n\nID:" + id + "\nName:" + name + "\n\n" + department;
}
}
REPOSITORIES
#Repository
public interface DepartmentRepository extends JpaRepository<Department, Integer> {
Department findByName(String name);
}
#Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
Student findByName(String name);
}
SERVICES
#Service
public class StudentService {
private final StudentRepository studentRepository;
#Autowired
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public void addToDatabase(Student student) {
this.studentRepository.saveAndFlush(student);
}
public Student getStudentByName(String name) {
return studentRepository.findByName(name);
}
}
#Service
public class DepartmentService {
private final DepartmentRepository departmentRepository;
#Autowired
public DepartmentService(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
public void addToDataBase(List<Department> department) {
this.departmentRepository.save(department);
department.forEach(this.departmentRepository::saveAndFlush);
}
public Department getDepartmentByName(String name){
return this.departmentRepository.findByName(name);
}
}
My main method
#Component
public class Terminal implements CommandLineRunner {
private final StudentService studentService;
private final DepartmentService departmentService;
#Autowired
public Terminal(StudentService studentService, DepartmentService departmentService) {
this.studentService = studentService;
this.departmentService = departmentService;
}
#Override
public void run(String... strings) throws Exception {
Department department = new Department("dep1");
Department department1 = new Department("dep2");
Department department2 = new Department("dep3");
Department department3 = new Department("dep4");
List<Department> departments = new ArrayList<>(Arrays.asList(department, department1, department2, department3));
this.departmentService.addToDataBase(departments);
//
Student student = new Student("pesho", department);
Student student11 = new Student("gosho", department1);
this.studentService.addToDatabase(student11);
this.studentService.addToDatabase(student);
student = new Student("sasho", department2);
this.studentService.addToDatabase(student);
// System.out.println(this.studentService.getStudentByName("gosho").getDepartment1());
// System.out.println("CHECKING ONE TO ONE BIDIRECTIONAL: " + this.departmentService.getDepartmentByName("dep1").getStudent());
}
}
So here when i try to add students in the students table it gives an error
The error is the fallowing
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: app.models.Department

you added cascade= CascadeType.ALL for Department in Student class and save departments separete. this.departmentService.addToDataBase(departments);
fix : dont call
departmentService.addToDataBase(departments);
or remove CascadeType.ALL from Student

Well I can't understand you problem completely but here's what I would like to add. Cascading for add operation is not implemented or it's incomplete. Hope it helps.

Related

How to update a single field using jpa repository using #patch

I am developing a simple application by spring boot. I need to restrict the user to be able to only update the name, not all the filed that related to user data but unfortunately, my code has a problem that if someone sends a data in Json format and changes the age or any other field it will be updated but as I told I need the user to be able to change the only name not any other field. I have to mention I am using JPA repository and spring data
my controller
#RestController
#RequestMapping("/student")
public class StudentController {
#Autowired
StudentRepository repository;
// method i user to only update the name field
#PatchMapping("/pattt/{id}")
public ResponseEntity partialUpdateName(
#RequestBody Student partialUpdate,
#PathVariable("id") String id
){
Student.save(partialUpdate, id);
return ResponseEntity.ok(repository.save(partialUpdate));
};
}
JPA repository
#Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {}
Student class
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue
private int id;
private String name;
private int age;
private String emailAddress;
public Student() { }
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public Student(int id, String name, int age, String emailAddress) {
this.id = id;
this.name = name;
this.age = age;
this.emailAddress = emailAddress;
}
public static void save(Student partialUpdate, String id) {
partialUpdate.setName(id);
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getEmailAddress() {
return emailAddress;
}
}
The best solution for the future is to add a DTO layer to your application and use it to map to your object. See example below.
public class StudentDto {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Then you can map this to your model by Mapstruct:
#Mapper
public abstract class StudentMapper {
public static final StudentMapper INSTANCE =
Mappers.getMapper(StudentMapper.class);
#Mapping
Student studentDtoToStudent(StudentDto studentDto);
}
Mapstruct dependencies:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
</dependency>
You will be able to hide your internal structure from outside world.
In your conroller:
public ResponseEntity partialUpdateName(
#RequestBody StudentDto partialUpdate,
#PathVariable("id") String id)
{
Student student =
StudentMapper.INSTANCE.studentDtoToStudent(partialUpdate);
}
The last line will give you a safe student model which you can then save
Quick solution
In your controller:
public ResponseEntity partialUpdateName(
#RequestBody Student partialUpdate,
#PathVariable("id") String id)
{
Optional<Student> optionalStudent = repository.findById(id);
if(optionalStudent.isPresent() && partialUpdate!=null) {
Student current=optional.get();
current.setName(partialUpdate.getName());
return ResponseEntity.ok(repository.save(current));
}
/* return an error */
}

Spring Boot Hibernate CRUD REST API 404 Error

I am simply trying to create a Spring boot Hibernate CRUD REST API through this code:
EmployeController.java
#RestController
#RequestMapping("/api")
public class EmployeController {
#Autowired
private EmployeService employeService;
#GetMapping("/employe")
public List<Employe> get(){
return employeService.get();
}
}
Employe.java
#Entity
#Table(name="employe")
public class Employe {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column
private int id;
#Column
private String name;
#Column
private String gender;
#Column
private String department;
#Column
private Date dob;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
#Override
public String toString() {
return "Employe [id=" + id + ", name=" + name + ", gender=" + gender + ", department=" + department + ", dob="
+ dob + "]";
}
}
EmployeService.java
public interface EmployeService {
List<Employe> get();
Employe get(int id);
void save(Employe employe);
void delete(int id);
}
EmployeServiceImplement.java
#Service
public class EmployeServiceImplement implements EmployeService {
#Autowired
private EmployeDAO employeDAO;
#Transactional
#Override
public List<Employe> get() {
return employeDAO.get();
}
}
EmployeDAO.java
public interface EmployeDAO {
List<Employe> get();
Employe get(int id);
void save(Employe employe);
void delete(int id);
}
EmployeDAOImplement.java
#Repository
public class EmployeDAOImplement implements EmployeDAO {
#Autowired
private EntityManager entityManager;
#Override
public List<Employe> get() {
Session currentSession = entityManager.unwrap(Session.class);
Query<Employe> query = currentSession.createQuery("from Employe", Employe.class);
List<Employe>list = query.getResultList();
return list;
}
}
I have write all the configuration related to MySQl database into the application.properties and when i run this project as Spring Boot App and go to the Postman and tried like this
and i a unable to understan why it always throws 404 error every time , can anyone tell me what i am missing in this code.
Try with this GET request, it may help you:
http://localhost:8080/api
I checked your code.
where is #RestController for your Controller file and where is #RequestMapping For your method in Controller class?
maybe you should write something like this according to your need.
tell me if you need more help.
#RestController
#RequestMapping("/api")
public class EmployeController {
#RequestMapping(value = "/employ")
public void employ() {
}
}
Instead of this -
#Override
public List get()
Use this -
#RequestMapping(value = "/Employe", method = RequestMethod.GET)
public List get()

#MapKeyColumn in hibernate default value error

I am trying to implement the below example for #MapKeyColumn. There is a OnetoMany relationship between the Company and Persons. However I am getting the below error when I try to persist the Person instance since that is the owner of the relationship:
Caused by: java.sql.SQLException: Field 'name_emp' doesn't have a default value
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:975)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1114)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1062)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1383)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1047)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
... 20 more
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
int id;
String name;
#ManyToOne(cascade=CascadeType.PERSIST)
//#JoinColumn(name="company_id")
Company company;
public Person()
{}
public Person(String name) {
super();
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Company getcompany() {
return company;
}
public void setcompany(Company c) {
this.company = c;
}
}
Company Entity
#Entity
public class Company {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
int id;
String name;
#OneToMany(cascade=CascadeType.PERSIST,mappedBy="company")
#MapKeyColumn(name="name_emp")
Map<String,Person> persons= new HashMap<>();
public Company()
{}
public Company(String name) {
super();
this.name = name;
}
public Map<String,Person> getPersons() {
return persons;
}
public void setPersons(Map<String,Person> persons) {
this.persons = persons;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
MainTest
Person p1 = new Person("jack");
Company c= new Company("ABCORP");
p1.setcompany(c);
session.persist(p1);
The problem might occur because the relationship is not set in the company instance.
Could you try to add the person to the company ether by calling c.getPersons().add(p1.getName(), p1) or by changing the setcompany(...) method to
public void setcompany(Company c) {
c.getPersons().add(this.getName(), this)
this.company = c;
}

Why is my Spring data JPA's query not fetching distinct rows?

I have the following entity
public class Attendance extends AbstractEntity<Long>{
/**
*
*/
private static final long serialVersionUID = -1409043060497488674L;
#ManyToOne
private Student student;
#ManyToOne
private Staff staff;
#ManyToOne
private Teacher teacher;
private UserType userType;
private Timestamp checkInTime;
private String cardNumber;
#ManyToOne
private School school;
private AttendanceStatus status;
private AttendanceSource source;
#ManyToOne
private User markedBy;
#Transient
private long dummyId;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Staff getStaff() {
return staff;
}
public void setStaff(Staff staff) {
this.staff = staff;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public UserType getUserType() {
return userType;
}
public void setUserType(UserType userType) {
this.userType = userType;
}
public Timestamp getCheckInTime() {
return checkInTime;
}
public void setCheckInTime(Timestamp checkInTime) {
this.checkInTime = checkInTime;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public AttendanceStatus getStatus() {
return status;
}
public void setStatus(AttendanceStatus status) {
this.status = status;
}
public User getMarkedBy() {
return markedBy;
}
public void setMarkedBy(User markedBy) {
this.markedBy = markedBy;
}
public AttendanceSource getSource() {
return source;
}
public void setSource(AttendanceSource source) {
this.source = source;
}
public long getDummyId() {
return dummyId;
}
public void setDummyId(long dummyId) {
this.dummyId = dummyId;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
}
I am trying to fetch distinct rows of this tables using the following query:
#Query("Select distinct a.cardNumber, a.student, a.checkInTime, a.status, a.source, a.userType from Attendance a where a.school.schoolUrl=?1 and a.userType=?2 and a.status=?3 and a.checkInTime>=?4 order by a.checkInTime desc")
List<Object[]> getTodaysStudentAttendance(String schoolUrl,UserType userType,AttendanceStatus status,Timestamp timestamp);
However no matter what I do I just cant fetch the distinct rows. I have the following data in my data base
The query is returning all these three rows. What exactly am I missing? Please help.

Why the same id is incremented when I save an object in the repository?

Whenever I call save() method the same ID is shared between three different entities and I don't know why ?
#Entity
public class Department {
#Id
#GeneratedValue
private Long departmentId;
private String name;
public Department(Long departmentId) {
this.departmentId = departmentId;
}
public Department() {
}
public Department(String name) {
this.name = name;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Entity
public class Location {
#Id
#GeneratedValue
private Long locationId;
private String name;
public Location(Long locationId) {
this.locationId = locationId;
}
public Location() {
}
public Location(String name) {
this.name = name;
}
public Long getLocationId() {
return locationId;
}
public void setLocationId(Long locationId) {
this.locationId = locationId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And this is my Controller:
#RestController
public class SettingsController {
#Autowired
private LocationRepository locationRepository;
#Autowired
private DepartmentRepository departmentRepository;
#Autowired
private RoleRepository roleRepository;
#RequestMapping(value = "/api/locations", method = RequestMethod.POST)
public ResponseEntity addLocation(#RequestBody DataForm dataForm) {
if (dataForm == null) {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
locationRepository.save(new Location(dataForm.getName()));
return new ResponseEntity(HttpStatus.CREATED);
}
#RequestMapping(value = "/api/roles", method = RequestMethod.POST)
public ResponseEntity addRole(#RequestBody DataForm dataForm) {
if (dataForm == null) {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
roleRepository.save(new Role(dataForm.getName()));
return new ResponseEntity(HttpStatus.CREATED);
}
#RequestMapping(value = "/api/departments", method = RequestMethod.POST)
public ResponseEntity addDepartment(#RequestBody DataForm dataForm) {
if (dataForm == null) {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
departmentRepository.save(new Department(dataForm.getName()));
return new ResponseEntity(HttpStatus.CREATED);
}
}
This should happen only if the id would be static, but It's not. If I create two new Location() objects, when I will create a new Department() the Id of the department will be 3. Why ?
Since you didn't specify the strategy for #GeneratedValue, I guess that Hibernate uses the same sequence for all your entities.
You can set something like
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="department_seq_gen")
#SequenceGenerator(name="department_seq_gen", sequenceName="DEPARTMENT_SEQ")
on Department entity, and something similar on Location entity (just use location_seq_gen and LOCATION_SEQ).

Categories

Resources