简体   繁体   中英

Designing hierarchy of parent child relationships with spring data jpa

I am trying to build a to-do log keeper. I am using java spring-boot with data-jpa which is built on hibernate. I want a user to have several projects the user works on. Every project then has several tasks associated with it and the user tracks how much time was spent per a task by completing short atomic units of work ( log entries ).

So far I ended up building the most naive implementation of this system. It looked like several levels of one to many hierarchy: user->projects->tasks->entries. The current db implementation is based on a schema like this

图式

Code for entity classes (getters setters constructors and some annotations are omitted for brevity):

@MappedSuperclass
public abstract class AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
}

@Entity
public class User extends AbstractEntity {
    @Column
    private String name;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Project> projects;
}

@Entity
public class Project extends AbstractEntity {
    @Column
    private String name;

    @OneToMany(mappedBy = "project", fetch = FetchType.LAZY)
    private List<Task> tasks;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

@Entity
public class Task extends AbstractEntity {
    @Column
    private String name;

    @OneToMany(mappedBy = "task", fetch = FetchType.LAZY)
    private List<Entry> entries;

    @ManyToOne
    @JoinColumn(name = "project_id")
    private Project project;
}

@Entity
public class Entry extends AbstractEntity {   
    @Column
    private Integer duration;

    @Column
    private LocalDateTime finish;

    @ManyToOne
    @JoinColumn(name = "task_id")
    private Task task;
}

I want to be able to provide functionality for a user to view all the log entries in a user specified time frame . I added jpa repository like this:

public interface EntryRepository extends JpaRepository<Entry, Integer> {

    @Query("SELECT e FROM Entry e WHERE (e.task.project.user.id=:user_id) AND " +
           "(e.finish BETWEEN :from AND :to)")
    List<Entry> getAllForUserInDateRange(@Param("from") LocalDateTime from, 
                                         @Param("to") LocalDateTime to, 
                                         @Param("user_id") int userId);
}

1) Is it correct to say that this query is inefficient? I was thinking performing a fetch like this from a database is inefficient because the query cannot take advantage of indexes. Since there is no foreign key user_id in the Entry table every row is being looked up and the chain entry->task->project->user is being followed. I end up with linear complexity instead of logarithmic.

2) What is a better way to solve the problem? Is it ok to store the foreign key to the user in the Entry table? If I will want to fetch entries from the database for a particular project or a task, then I will have to add foreign keys to these relationships as well. Is that ok?

You should check real SQL which is being executed. Set org.hibernate.SQL log level to DEBUG and you'll see the statements.

I think for your query you will actuall get three inner joins between four tables. You say the query cannot take advantage of indexes. It absolutely can. Create following indexes:

  • USER (ID)
  • PROJECT (USED_ID, ID)
  • TASK (PROJECT_ID, ID)
  • ENTRY(TASK_ID, ID)

See Contactenated Indexes from Use the Index, Luke .

With these indexes your joins across four tables will likely use indexes. I won't put my hand in fire for this, but it should work. Check the query plan.

You are right that the chain ENTRY -> TASK -> PROJECT -> USER will be followed, but it should be quite faset with indixes

Your database schema is pretty normalized, which results in three joins across four tables. You could denormalize this schema by bringing, say, user_id to the ENTRY . This may improve query performance, but honestly I doubt this will bring much. You may want to run real-world benchmark before actually switching to this solution.

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