简体   繁体   中英

Issues with setting up JPA entities (Spring Boot)

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.

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:

  1. No need to change the names of fields unless you need to change the names of fields.
  2. No need to initialize 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.
  3. You don't need to declare something LAZY when it is LAZY by default.
  4. When it is 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.
  5. Therefore, when you attempt to use the LAZY read-only field without having explicitly written a query for it you will get the dreaded LazyInitializationException .
  6. The 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.
  7. 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.
  8. Yes, it's true that you can play around with @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.
  9. Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM