I am trying to do do JPA/Hibernate mappings to map two tables, but am getting this error. any help would be greatly appreciated!!
Restaurants.java
#Entity
#Table(name="RESTAURANTS")
public class Restaurants{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy="restaurant")
private LinkedList<Menus> menus = new LinkedList<Menus>();
/* constructors **/
public Restaurants(){
this.dateJoined = new Date();
};
/* getters and setters **/
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy = "increment")
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public LinkedList<Menus> getMenus() {return menus;}
public void setMenus(LinkedList<Menus> menus) {this.menus = menus;}
}
Menus.java
#Entity
#Table(name = "MENUS")
public class Menus {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private Long restaurantID;
#OneToMany
#JoinColumn(name="restaurant")
private Restaurants restaurant;
/* constructors */
public Menus(){}
/* getters and setters */
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy = "increment")
#Column(nullable = false)
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public Long getRestaurantID() {return restaurantID;}
public void setRestaurantID(Long restaurantID) {this.restaurantID = restaurantID;}
public void setRestaurant(Restaurants restaurant) {this.restaurant = restaurant;}
public Restaurants getRestaurant() {return restaurant;}
}
With this error
Exception in thread "main" org.hibernate.MappingException: Could not
determine type for: bb.entities.Restaurants, at table: MENUS, for
columns: [org.hibernate.mapping.Column(restaurant)] at
org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:306) at
org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:290) at
org.hibernate.mapping.Property.isValid(Property.java:217) at
org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:464)
at org.hibernate.mapping.RootClass.validate(RootClass.java:235) at
org.hibernate.cfg.Configuration.validate(Configuration.java:1362) at
org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1865)
at bb.TestMain.setUp(TestMain.java:26) at
bb.TestMain.main(TestMain.java:59)
Thanks.
It appears to be a misconception in the use of the #OneToMany annotation. The #OneToMany annotation is used to represent the 1-side in a 1:M relationship, and the inverse #ManyToOne relationship is used to represent the M-side. Therefore, a #OneToMany annotation should be defined on a collection-type in an entity and not on a normal reference type.
You should therefore:
use a #OneToOne association if that is the nature of the relationship between the entities.
or, decide which entity represents the 1-side in the 1:M relationship. Going by the use of the LinkedList class in Restaurants, I would consider the Restaurants class to be the 1-side, and use the #OneToMany annotation in the Restaurants class, while using the inverse #ManyToOne relationship in the Menus class. The refined code would be:
Restaurants.java
...
#OneToMany(mappedBy="restaurant")
private List<Menus> menus = new LinkedList<Menus>();
Menus.java
#ManyToOne
#JoinColumn(name="restaurant")
private Restaurants restaurant;
Note the change in the declaration of the menus member variable from LinkedList<Menus> to List<Menus>. Apparently, in this case, it is wiser to declare any collection with the interface-type of the collection, instead of the concrete collection class. The rationale is that the underlying JPA provider will use it's own concrete collection types at runtime, for the purpose of proxying the collection values. Hibernate for instance, will use a PeristentList at runtime, to represent the List in a managed entity, and not a LinkedList as created by the entity. If you use the concrete type, Hibernate might fail in mapping the column, or might fail in retrieving the associated records from the database; I'm not sure about the specifics of the runtime behavior, except that I know of the eventual failure.
Related
I'm trying to implement entity auditing in my Java Spring Boot project using spring-data-envers. All the entities are being created as they should, but I've come up against a brick wall when executing the query.
parentRepository.findRevisions(id).stream().map(Parent::getEntity).collect(Collectors.toList());
During this select the repository is supposed to fetch info also from the child entity, instead I get unable to find <child object> with {id}.
According to my experiments categoryId is being searched in the Category_Aud table, instead of the actual table with desired data.
Code snippets:
#Data
#Entity
#Audited
#NoArgsConstructor
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Status status;
#Enumerated(EnumType.STRING)
private Type requestType;
private String fullName;
#ManyToOne
#JoinColumn(name = "child_id")
private Child child;
}
#Data
#Entity
#Audited
#NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
}
I've extended Parent with RevisionRepository
#Repository
public interface ParentRepository extends RevisionRepository<Parent, Long, Long>, JpaRepository<Parent, Long>
And annotated my SpringBootApplication entry class with:
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
I couldn't find any explanation for this so far, how can make parentRepository get what I need?
The underlying problem here is that the reference from a versioned entity isn't really properly defined. Which variant of the reference should be returned? The one at the start of the version you use as a basis, the one at the end? The one that exists right now?
There are scenarios for which each variant makes sense.
Therefor you have to query the revisions yourself and can't simply navigate to them.
I have one to many relationship. If in class Customer I write List:
private List<Orders> order;
my GetMapping will work fine.
But I want to use best practices and I write Set instead of List:
private Set<Orders> order;
In result I have error:
Could not write JSON: Infinite recursion (StackOverflowError); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError)
Why I have this error? What's wrong with Set?
My entities:
#Entity
public class Customer {
#Id
#GeneratedValue
private int id;
private String firstName;
private String lastName;
#OneToMany(cascade=ALL, mappedBy="customer", orphanRemoval=true)
private Set<Orders> order;
//private List<Orders> order;
}
#Entity
public class Orders {
#Id
#GeneratedValue
private int id;
#JsonIgnore
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;
}
And GetMapping:
#GetMapping("/customer/{id}")
public ResponseEntity get(#PathVariable Long id) {
Optional<Customer> customer = customerRepository.findById(id);
return new ResponseEntity<>(new ResponseObject(customer));
}
UPD. I see question Infinite Recursion with Jackson JSON and Hibernate JPA issue. But it's other question. I talk about difference in use List and Set. I am not interesting in #JsonIgnore and I don't ask about it (and I use it in my code). I want to understand why I have an error when I use Set and don't have error with List
I've setup JPA auditing with Spring Data JPA AuditingEntityListener and AuditorAware bean. What I want is to be able to persist auditor details even on entities with predefined identifiers.
The problem is that when JPA entity with predefined id is being persisted and flushed it's auditor details cannot be persisted:
object references an unsaved transient instance - save the transient instance before flushing: me.auditing.dao.AuditorDetails
The interesting part is that when an entity with a generated id is saved - everything's fine. In both cases the entities are new. I could not pinpoint the problem digging through hibernate code so I've created a sample project to demonstrate this (test class me.auditing.dao.AuditedEntityIntegrationTest) It has both entities with predefined and generated identifiers and should be audited.
The entities are:
#Entity
public class AuditedEntityWithPredefinedId extends AuditableEntity {
#Id
private String id;
public String getId() {
return id;
}
public AuditedEntityWithPredefinedId setId(String id) {
this.id = id;
return this;
}
}
and:
#Entity
public class AuditedEntityWithGeneratedId extends AuditableEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
public String getId() {
return id;
}
public AuditedEntityWithGeneratedId setId(String id) {
this.id = id;
return this;
}
}
where parent class is:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity implements Serializable {
private static final long serialVersionUID = -7541732975935355789L;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#CreatedBy
private AuditorDetails createdBy;
#CreatedDate
private LocalDateTime createdDate;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#LastModifiedBy
private AuditorDetails modifiedBy;
#LastModifiedDate
private LocalDateTime modifiedDate;
And the auditor getter implementation is:
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
}
Edit 2016-08-08: It seems that when a new entity with predefined id is saved, it gets two different instances of createdBy and modifiedBy AuditorDetails, which is quite logical if the entity wouldn't be actually new. So, a completely new entity with generated gets both AuditorDetails of same instance, and the one with manually set id doesn't. I tested it by saving auditor details in AuditorAware bean before returning it to AuditingHandler.
Ok, so for now the only solution I could find is to actually persist AuditorDetails before writing it to audited entities like so:
#Override
#Transactional
public AuditorDetails getCurrentAuditor() {
AuditorDetails details = new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
return auditorDetailsRepository.save(details);
}
It is not the most elegant solution, but it works for now.
This is my sample schema and I have generated jpa entities in eclipse.
I am using spring jpa repositories. I want to know if I need to create repository interface for student course table.
I am having doubt over addStudentCourse method of both student and course entity classes. List studentCourses will be always null for new entity, how can I fill student course table while registering student information in system i.e save method on studentRepository.
Student.java
#Entity
#NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long studentid;
private String studentname;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="student")
private List<StudentCourse> studentCourses;
........
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setStudent(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCours.setStudent(null);
return studentCourse;
}
Course.java
#Entity
#NamedQuery(name="Course.findAll", query="SELECT c FROM Course c")
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long courseid;
private String coursename;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="course")
private List<StudentCourse> studentCourses;
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setCourse(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCourse.setCourse(null);
return studentCourse;
}
StudentCourse.java
#Entity
#Table(name="STUDENT_COURSE")
#NamedQuery(name="StudentCourse.findAll", query="SELECT s FROM StudentCourse s")
public class StudentCourse implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private StudentCoursePK id;
private String status;
//bi-directional many-to-one association to Course
#ManyToOne
#JoinColumn(name="COURSEID")
private Course course;
//bi-directional many-to-one association to Student
#ManyToOne
#JoinColumn(name="STUDENTID")
private Student student;
...
}
StudentCoursePK.java
#Embeddable
public class StudentCoursePK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(insertable=false, updatable=false)
private long studentid;
#Column(insertable=false, updatable=false)
private long courseid;
...
}
If I understood your question correctly what you want to do is to be able to save a student from the save method in StudentRepository, and that this inserts/updates the student and also inserts/updates the join table.
Since the Student entity is not the owning side (it's mapped by "student" in StudentCourse), saving a Student will not trigger a save on StudentCourse. To do so you can add a cascade property the list for insert, update... or just for everything:
#OneToMany(mappedBy="student", cascade = CascadeType.ALL)
private List<StudentCourse> studentCourses = new ArrayList<StudentCourse>();
Then you could a method on your #Service class that looks like this:
#Transactional
public void enrollInCourse(Student student, Course course) {
StudentCourse sc = new StudentCourse();
sc.setStudent(student);
sc.setCourse(course);
sc.setStatus("Enrolled");
student.getStudentCourses().add(sc);
studentRepository.save(student);
}
This will also populate the StudentCourse table.
So there's no need for a repository, although if the cascade doesn't work as expected you could create one and save the StudentCourse entity yourself manually.
If this does not work you could try changing your mappings. For n-ary relationships or join tables with extra columns I always define the #ManytoOne relationships inside the #Embeddable class, and in the entity that represents the join table I define getters as #Transient to allow access to the mapped objects which are inside the embedded composite Id.
You can see an example here, and a blog post about this approach here.
Got GlassFish v3. I have an one-to-many entity. The problem is, that EclipseLink seems to ignore the fetch EAGER mode.
Here is my entities.
#Entity
public class Person implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Hobby> hobbies;
// getter and setter
}
A 1:n relationship
#Entity
public class Hobby
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn
private Person person;
// getter and setter
}
And the bean
#javax.ejb.Remote
public interface Testing
{
public void addTestData();
public List<Person> getTestData();
}
#javax.ejb.Stateless
public class TestingBean implements Testing
{
#javax.persistence.PersistenceContext
private EntityManager entityManager;
public void addTestData()
{
Person p = new Person();
p.setName("JOE");
entityManager.persist(p);
Hobby h1 = new Hobby();
h1.setName("h1");
h1.setPerson(p);
entityManager.persist(h1);
}
public List<Person> getTestData()
{
TypedQuery<Person> gridQuery = entityManager.createQuery("SELECT e FROM Person e", Person.class);
return gridQuery.getResultList();
}
}
EDIT Client:
InitialContext context = new InitialContext();
Testing test = (Testing)context.lookup("java:global/dst2_1/TestingBean");
test.addTestData();
for(Person p: test.getTestData()) {
System.out.println(p.getName());
for(Hobby b : p.getHobbys()) {
System.out.println(b.getName());
}
}
context.close();
Using MySQL - Storing the data works. But if I fetch the data only the person is returned - not hobbies. Coudld you tell me what is wrong in my code?
EDIT sorry have tried so many things ... The code shown as above produces:
Exception Description: An attempt was made to traverse a
relationship using indirection that had a null Session. This often
occurs when a n entity with an uninstantiated LAZY relationship is
serialized and that lazy relationship is traversed after
serialization. To avoid this issue, ins tantiate the LAZY
relationship prior to serialization.
But the Person is returned correctly. Why does it specify LAZY while I am using EAGER?
You code looks correct. I can't see any way that the EAGER could be ignored.
Are you sure you get the error with this attribute, not another one?
Also ensure you recompile and deployed your code correctly. You most like have an old version deployed.
Make the eager object Serializable