Search code examples
javahibernatejpaspring-data-jpaspring-data

Is there a way to retrieve an entity with list property but load the list with the last few entities?


Let's say I have the following entities in my application:

@Data
@Entity
public class SomeEntity {
    @Id
    private Long id;

    @OneToMany
    private List<AnotherEntity> anotherEntities = new ArrayList<>();
    
    @Version
    private Long version;

}

@Data
@Entity
public class AnotherEntity {
    @Id
    private Long id;

    @Column
    private String someField;
    
    @Column
    private Long version;
  
}

Question 1:

I want to load a SomeEntity with id = 1 for example, but I only want to load the anotherEntities partially, for example I only want the last 10 versions of it, what is the easiest and most straight forward way of doing it (with Hibernate/Spring Data JPA) with one request?

Question 2:

I want to update the previously mentioned object and add a new AnotherEntity to the list, but the save(T t) method of JpaRepository saves the whole object and I lose the ones that weren't loaded. How can I save the object so that the version will be updated by Spring Data (Optimistic Locking) and the SomeEntity won't lose previous data?


Update 1:

I am using Postgresql for database.


Solution

  • You have different options depending on your exact constraints.

    You can use the @Where annotation:

    @Data
    @Entity
    public class SomeEntity {
        @Id
        private Long id;
    
        @OneToMany
        @Where(clause = "version < 10")
        private List<AnotherEntity> anotherEntities = new ArrayList<>();
        
        @Version
        private Long version;
    
    }
    

    You can use a filter:

    @Data
    @Entity
    public class SomeEntity {
        @Id
        private Long id;
    
        @OneToMany
        @Filter(
            name="latestVersions",
            condition="version < :version"
       )
       private List<AnotherEntity> anotherEntities = new ArrayList<>();
       
    }
    

    You can enable filters before running a query with the session:

    entityManager
        .unwrap(Session.class)
        .enableFilter("latestVersions")
        .setParameter("version", 10);
    
    List<Account> accounts = entityManager.createQuery(
        "from SomeEntity se where se.id = 1", SomeEntity.class)
    .getResultList();
    

    You can map the association as bidirectional (or as a many-to-one)

    @Data
    @Entity
    public class SomeEntity {
        @Id
        private Long id;
    
        @OneToMany(mappedBy = "someEntity")
        private List<AnotherEntity> anotherEntities = new ArrayList<>();
        
        @Version
        private Long version;
    
    }
    
    @Data
    @Entity
    public class AnotherEntity {
        @Id
        private Long id;
    
        @Column
        private String someField;
        
        @Column
        private Long version;
     
    
       @ManyToOne
       private SomeEntity someEntity;
    }
    

    Now you can get the list of entities using an HQL query:

    from AnotherEntity ae where ae.someEntity.id = 1 and ae.version < 10
    

    When you want to create a new AnotherEntity, you can get SomeEntity from any element in the result list, or you can use EntityManager#getReference (and avoid running the query):

    AnotherEntity ae = new AnotherEntity(...);
    ae.setSomeEntity(em.getReference(SomeEntity.class, 1));
    em.persist(ae);
    

    The association is lazy, so Hibernate is not going to load the whole collection (unless you need to access it).