I am using Hibernate 5.3.10 as my ORM in the Spring boot based project. Suppose that we have the following entities:
#Entity
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
#EmbeddedId
private EmbId id;
public Id getId() { return id; }
public void setId(Id id) { this.id = id; }
}
#Entity
#Table(name = "child")
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "id"),
#PrimaryKeyJoinColumn(name = "date_time")
})
public class Child {
#EmbeddedId
private EmbId id;
private String name;
public Id getId() { return this.id; }
public void setId(Id id) { this.id = id; }
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
}
and the embeddable key as follow:
#Embeddable
public class EmbId {
#Column(name = "id")
private Long id;
#Column(name = "date_time")
private Date dateTime;
public Long getId() { return this.id; }
public void setId(Long id) { this.id = id; }
public Date getDateTime() { return this.dateTime; }
public void setDateTime(Date dateTime) { this.dateTime = dateTime; }
}
When I want to polymorphic query on Child entity, the JPA faces with ORA-00932 Inconsistent datatypes: expected TIMESTAMP got NUMBER and the following sql have seen in the console:
select child0_.id as date_time1_25_0, child0_.date_time as id2_25_0, child0_1_.name as name3_3_0 from my_schema.child child0_ inner join my_schema.parent child0_1_ on child0_.id = child0_1_.date_time and child0_.date_time = child0_1_.id
It seems that the equality of IDs is displaced. What happened and what should I do to resolve that?
Thanks in advance.
Fortunately, I found that adding referencedColumnName attribute to #PrimaryKeyJoinColumn could be guide hibernate to use the IDs in the right place.
So, the Child class should be as follow:
#Entity
#Table(name = "child")
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "id"),
#PrimaryKeyJoinColumn(name = "date_time")
})
public class Child {
#EmbeddedId
private EmbId id;
private String name;
public Id getId() { return this.id; }
public void setId(Id id) { this.id = id; }
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
}
Related
I can't delete the association in the courses_student table of course and student when trying to delete a course, even if I want to cascade delete it does not work for me since there is a foreign key in courses_student, I don't know what the problem is.
I have also tried to remove the association in the courses_student table doing a update.but nothing happened.
DAO
#Override
public boolean deleteCourse(int id) {
Session currentSession = entityManager.unwrap(Session.class);
Courses course = currentSession.load(Courses.class, id);
for(Student student : course.getEstudiantes()) {
course.removeStudent(student);
}
currentSession.delete(course);
if(course.getId() == null)
return true;
else
return false;
}
Courses entity
#Entity
#Table(name = "courses")
public class Courses {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column
private String nombre;
#Column
private String descripcion;
#ManyToMany(mappedBy = "courses")
private Set<Student> Estudiantes = new HashSet<Student>();
public Courses() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public Set<Student> getEstudiantes() {
return Estudiantes;
}
public void setEstudiantes(Set<Student> estudiantes) {
Estudiantes = estudiantes;
}
public void removeStudent(Student student) {
this.Estudiantes.remove(student);
student.getCourses().remove(this);
}
}
Student entity
#Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column
private String nombre;
#Column
private String apellido;
#Column
private String dni;
#ManyToMany(fetch=FetchType.LAZY,
cascade= {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JoinTable(
name="courses_students",
joinColumns=#JoinColumn(name="id_student"),
inverseJoinColumns=#JoinColumn(name="id_course")
)
private Set<Courses> courses = new HashSet<Courses>();
public Student() {
}
public Student(String nombre, String apellido, String dni) {
this.nombre = nombre;
this.apellido = apellido;
this.dni = dni;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
public String getDni() {
return dni;
}
public void setDni(String dni) {
this.dni = dni;
}
public Set<Courses> getCourses() {
return courses;
}
public void setCourses(Set<Courses> courses) {
this.courses = courses;
}
}
EDIT:
apparently it works for me, trying to update since owner side.
#Override
public boolean deleteCourse(int id) {
Session currentSession = entityManager.unwrap(Session.class);
Courses course = currentSession.load(Courses.class, id);
for(Student student : course.getEstudiantes()) {
student.removeCourse(course);
}
currentSession.update(course);
if(course.getId() == null)
return true;
else
return false;
}
It seems to me that you are missing a cascade configuration of your #ManyToMany annotation on Courses which is actually the one you are updating / deleting. Try the following:
#ManyToMany(mappedBy = "courses", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Student> Estudiantes = new HashSet<Student>();
Also, given that you have a bi-directional relationship, you should also remove the Course from each Student courses property.
I faced with problem of null value in PK.
Here's an entity:
#Entity
#Table(name="space")
public class Space implements Serializable {
#Id
#GeneratedValue
#Column(nullable = false, unique = true)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserAccount user;
private String name;
private String description;
private Date createdTime;
private Date modifiedTime;
#OneToMany(mappedBy="space")
private Set<SpaceAccess> spaceAccesses = new HashSet<>();
public Set<SpaceAccess> getSpaceAccesses() {
return spaceAccesses;
}
public void setSpaceAccesses(Set<SpaceAccess> spaceAccesses) {
this.spaceAccesses = spaceAccesses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Space() {}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public UserAccount getUser() {
return user;
}
public void setUser(UserAccount user) {
this.user = user;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}
I wrote strategy to generate PK properly but I always get Null in id field when I create new instance of the Space:
Space space = new Space();
Here's content of the object:
What i should do to generate id of instance properly using hibernate/spring mechanisms?
application.properties:
spring.datasource.url="some_url"
spring.datasource.username=name
spring.datasource.password=password
spring.jpa.generate-ddl=true
P.S. I use spring-boot-starter-data-jpa with version: 2.3.4.RELEASE.
Use:
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
I am using the debugger in IntelliJ and right before the point of returning the result, the array is perfectly fine, as you can see here
But for some reason, the response in the browser looks like this
I don't understand why the fields are invisible.
This is what my 2 models look like:
Municipality:
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
}
Prediction
#Entity
public class Prediction {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
LocalDateTime tsPredictionMade;
LocalDateTime tsPredictionFor;
float pm10;
float pm25;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
Municipality municipality;
}
And this is my controller:
#RestController
#RequestMapping("/predict")
public class PredictionController {
private MunicipalityService municipalityService;
private PredictionService predictionService;
#Autowired
public PredictionController(MunicipalityService municipalityService, PredictionService predictionService) {
this.municipalityService = municipalityService;
this.predictionService = predictionService;
}
#GetMapping
public List<Municipality> getPredictions(){
List<Municipality> result = municipalityService.getPredictions();
return result;
}
#GetMapping("/{municipality}")
public List<Prediction> getPredictionsForMunicipality(#PathVariable("municipality") String name){
List<Prediction> result = predictionService.getPredictions(name);
return result;
}
}
The rest of the app (service and persistence layer) is pretty standard.
What is the reason for this?
You will need the getters and setters for your models. The Jackson library needs it for accessing its fields when converting the models into JSON, differently from JPA when converting the resultSet into models. Here is the code:
Prediction
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getTsPredictionMade() {
return tsPredictionMade;
}
public void setTsPredictionMade(LocalDateTime tsPredictionMade) {
this.tsPredictionMade = tsPredictionMade;
}
public LocalDateTime getTsPredictionFor() {
return tsPredictionFor;
}
public void setTsPredictionFor(LocalDateTime tsPredictionFor) {
this.tsPredictionFor = tsPredictionFor;
}
public float getPm10() {
return pm10;
}
public void setPm10(float pm10) {
this.pm10 = pm10;
}
public float getPm25() {
return pm25;
}
public void setPm25(float pm25) {
this.pm25 = pm25;
}
public Municipality getMunicipality() {
return municipality;
}
public void setMunicipality(Municipality municipality) {
this.municipality = municipality;
}
}
Municipality
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
You need getters and setter for each field that you want to expose.
You can use #Data from lombok project to avoid boilerplate code.
https://projectlombok.org/
i am try to create a one to many bidirectional mapping using spring boot and spring data jpa please look the below entity
Employer Entity
#Entity
public class Employer
{
private Long id;
private String employerName;
private List<Employee> employees;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployerName()
{
return employerName;
}
public void setEmployerName(String employerName)
{
this.employerName = employerName;
}
#OneToMany(cascade=CascadeType.ALL, mappedBy="employer")
public List<Employee> getEmployees()
{
return employees;
}
public void setEmployees(List<Employee> employees)
{
this.employees = employees;
}
}
Employee Entity
#Entity
public class Employee
{
private Long id;
private String employeeName;
private Employer employer;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployeeName()
{
return employeeName;
}
public void setEmployeeName(String employeeName)
{
this.employeeName = employeeName;
}
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
public Employer getEmployer()
{
return employer;
}
public void setEmployer(Employer employer)
{
this.employer = employer;
}
}
Employer Repo
public interface EmployerServices extends JpaRepository<Employer, Long> {
}
Employee Repo
public interface EmployeeServices extends JpaRepository<Employee, Long> {
}
REST Controller is
#RestController
public class Controller {
#Autowired EmployeeServices employeeServices;
#Autowired EmployerServices employerServices;
#GetMapping("/getempr")
public Object getempr(){
return employerServices.findOne(1L);
}
}
now the problem begin start see my out put
its look like a infighting loop and my server throwing error getOutputStream() has already been called for this response.
I used #JsonBackReference & #JsonManagedReference
annotation but the problem is its working like one to many
{
"id":1,
"employerName":"employer",
"employees":[
{"id":1,"employeeName":"emp1"},
{"id":2,"employeeName":"emp2"}
]
}
if I am trying to get in the concern of many to one like all employee with employer. the output is
[
{
"id":1,
"employeeName":"emp1"
},
{
"id":2,
"employeeName":"emp2"}
]
its not showing me the employer details.
please suggets me guys what i am doing wrong. thanks in advance!!
Instead of using #JsonBackReferenceand #JsonManagedReference try to use annotation #JsonIgnoreProperties:
#JsonIgnoreProperties("employer")
private List<Employee> employees;
#JsonIgnoreProperties("employees")
private Employer employer;
It prevents Jackson from rendering a specified properties of associated objects.
with the JSON its a problem with bi-directional mapping. Use the below properties.
#JsonIgnoreProperties("employer")
#JsonIgnoreProperties("employees")
please keep fetching type as eager.
hope this will work.
You can solve your issue with two modification with annotations.
Employer.class
#Entity
public class Employer {
private Long id;
private String employerName;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "employer",
orphanRemoval = true)
private List<Employee> employees;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployerName() {
return employerName;
}
public void setEmployerName(String employerName) {
this.employerName = employerName;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
Employee.class
#Entity
public class Employee {
private Long id;
private String employeeName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employer_id")
private Employer employer;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public Employer getEmployer() {
return employer;
}
public void setEmployer(Employer employer) {
this.employer = employer;
}
}
For more information please visit this link.
Change your getEmployer Method like this:
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public Employer getEmployer()
{
return employer;
}
use
#JsonProperty(access = Access.WRITE_ONLY)
private List<Employee> employees;
So that it will ignore employees while printing to JSON in the response (and thus prevents the looping), but will still consider the JSON data (employee list) you pass in the request body so that it is available for persistence.
I have a simple Model in Java called Member with fields - ID (Primary Key), Name (String), Position (String)
I want to expose an POST endpoint to update fields of a member. This method can accept payload like this
{ "id":1,"name":"Prateek"}
or
{ "id":1,"position":"Head of HR"}
and based on the payload received, I update only that particular field. How can I achieve that with JPARepository?
My repository interface is basic -
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository("memberRepository")
public interface MemberRepository extends JpaRepository<Member, Integer>{
}
My Member model -
#Entity
#Table(name="members")
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="member_id")
private Integer id;
#Column(name="member_name")
#NotNull
private String name;
#Column(name="member_joining_date")
#NotNull
private Date joiningDate = new Date();
#Enumerated(EnumType.STRING)
#Column(name="member_type",columnDefinition="varchar(255) default 'ORDINARY_MEMBER'")
private MemberType memberType = MemberType.ORDINARY_MEMBER;
public Member(Integer id, String name, Date joiningDate) {
super();
this.id = id;
this.name = name;
this.joiningDate = joiningDate;
this.memberType = MemberType.ORDINARY_MEMBER;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getJoiningDate() {
return joiningDate;
}
public void setJoiningDate(Date joiningDate) {
this.joiningDate = joiningDate;
}
public MemberType getMemberType() {
return memberType;
}
public void setMemberType(MemberType memberType) {
this.memberType = memberType;
}
public Member(String name) {
this.memberType = MemberType.ORDINARY_MEMBER;
this.joiningDate = new Date();
this.name = name;
}
public Member() {
}
}
Something like this should do the trick
public class MemberService {
#Autowired
MemberRepository memberRepository;
public Member updateMember(Member memberFromRest) {
Member memberFromDb = memberRepository.findById(memberFromRest.getid());
//check if memberFromRest has name or position and update that to memberFromDb
memberRepository.save(memberFromDb);
}
}