Reputation: 2115
I am working on a small test project that will persist data to a simple database. I am running into confusion and issues with setting up my data entities and could use some help.
I am developing this as a Spring Boot Java project and am using JPA for the persistence capabilities. I am not very familiar with JPA.
Consider the following mock schema:
Task:
<simple data fields omitted>
collection of TaskNote items
collection of StateChangeHistory items
reference to a "parent" Task item (can be null)
collection of Task "child" items (may be empty)
TaskNote:
<simple data fields omitted>
StateChangeHistory:
<simple data fields omitted>
I am not sure of the correct way to annotate and structure these classes to facilitate this arrangement. I am also not sure whether some of these relationships should be uni- or bi-directional in nature. I've seen articles about various ways to set up uni- and bi-directional relationships, but I'm still confused and not sure when a particular approach should be used.
Here is what I have at this point:
Task.java:
@Entity
public class Task extends AbstractEntity {
// Simple fields omitted.
@OneToMany(mappedBy = "taskId", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<TaskNote> notes = new LinkedList<>();
@OneToMany(mappedBy = "taskId", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<StateChangeHistory> stateHistory = new LinkedList<>();
// Have not yet attempted to implement parent and children.
}
TaskNote.java:
@Entity
public class TaskNote extends AbstractEntity {
// Simple fields omitted.
@ManyToOne
@JoinColumn(name = "taskId")
private Task task;
}
StateChangeHistory.java:
@Entity
public class StateChangeHistory extends AbstractEntity {
// Simple fields omitted.
@ManyToOne
@JoinColumn(name = "taskId")
private Task task;
}
AbstractEntity.java:
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
public Long getId() {
return id;
}
// Other items omitted.
}
When I attemp to run the application (which is still very basic at this point) just to see what happens, there is a ton of console output, but the thing that catches my eye is the following:
...
[WARN ] 2020-11-19 14:06:17.747 [restartedMain] AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.mycompany.tasks.backend.entity.TaskNote.taskId in com.mycompany.tasks.backend.entity.Task.notes
...
[ERROR] 2020-11-19 14:06:17.794 [restartedMain] SpringApplication - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.mycompany.tasks.backend.entity.TaskNote.taskId in com.mycompany.tasks.backend.entity.Task.notes
...
No idea what this all means, but it's related to my entity classes. I'm also wondering if I'm missing something in my POM file or if there is some kind of configuration that's missing. As I said, I'm not familiar with the JPA or data persistence in Java in general.
Upvotes: 0
Views: 1123
Reputation: 11551
The basics. So, like I was saying, there is not enough information for your problem because I am not getting the problem. However, here is a 2 minute Spring-Data-Jpa lesson. An abstract entity:
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
public Long getId() {
return id;
}
}
A Task:
@Entity
public class Task extends AbstractEntity {
@OneToMany(mappedBy = "task")
List<TaskNote> notes;
@OneToMany(mappedBy = "task")
List<StateChangeHistory> stateHistory;
}
A TaskNote
@Entity
public class TaskNote extends AbstractEntity {
@ManyToOne
Task task;
}
A StateChangeHistory
@Entity
public class StateChangeHistory extends AbstractEntity {
@ManyToOne
Task task;
}
A very important configuration for application.properties.
spring.jpa.show-sql=true
An application:
@Override
public void run(ApplicationArguments args) throws Exception {
save();
Task task = taskRepo.findById(1L).get();
System.out.println("after findById");
System.out.println(task);
System.out.println("after print task");
System.out.println(task.notes);
}
private void save() {
// save ...
Task task = taskRepo.save(new Task());
TaskNote taskNote = new TaskNote();
taskNote.task = task;
taskNoteRepo.save(taskNote);
StateChangeHistory stateChangeHistory = new StateChangeHistory();
stateChangeHistory.task = task;
stateChangeHistoryRepo.save(stateChangeHistory);
}
An output:
2020-11-19 15:06:19.114 INFO 2156 --- [ main] jpatest.JpatestApplication : Started JpatestApplication in 1.798 seconds (JVM running for 2.252)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into task (id) values (?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into task_note (task_id, id) values (?, ?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into state_change_history (task_id, id) values (?, ?)
Hibernate: select task0_.id as id1_1_0_ from task task0_ where task0_.id=?
after findById
jpatest.Task@fd53053
after print task
2020-11-19 15:06:19.171 INFO 2156 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-11-19 15:06:19.185 ERROR 2156 --- [ main] o.s.boot.SpringApplication : Application run failed
java.lang.IllegalStateException: Failed to execute ApplicationRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) [spring-boot-2.4.0.jar:2.4.0]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:785) [spring-boot-2.4.0.jar:2.4.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) [spring-boot-2.4.0.jar:2.4.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) [spring-boot-2.4.0.jar:2.4.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) [spring-boot-2.4.0.jar:2.4.0]
at jpatest.JpatestApplication.main(JpatestApplication.java:13) [classes/:na]
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: jpatest.Task.notes, could not initialize proxy - no Session
Details:
List
for @OneToMany
relations because they are really just read only query fields. The mappedBy
annotation says that the entity TaskNote
or StateChangeHistory
is the owner
of the relation and only changes made to the owner of the relationship are persisted. These entities are created and persisted explicitly as you see.LAZY
when it is LAZY
by default.LAZY
then the relation won't be read unless you explicitly tell JPA to do so and there is nothing in the default JpaRepository
interface that will do that for you.LAZY
read-only field without having explicitly written a query for it you will get the dreaded LazyInitializationException
.can be null
in reference to a "parent" Task item (can be null)
is confusing to me. It's a foreign key which cannot be null. See ACID principals.cascade = CascadeType.ALL
doesn't mean anything if it's not on the owner of the relation and so adding it has no effect on this example.@Join
annotations and make cascade
annotations do funny things, but you are much better off getting a handle on the basics before trying funny stuff.Upvotes: 1