JPA 存储库:当使用 findAll() 返回实体时,与@Entity 关联的对象列表为空

JPA repository: list of objects associated to @Entity is empty when Entity is returned using findAll()

我正在为使用休眠、JPArepository 和 mysql 数据库的 Web 服务编写测试。那里一切正常,数据被预加载到数据库中并且可以正确检索(当未使用测试时),但是测试失败。

测试使用内存数据库中的H2。它妥协了:

a) 将 MyEntity 保存到数据库

  1. 正在创建 MyObjects,并将它们保存到 List

  2. 正在使用关联列表创建 MyEntity

  3. 将 MyEntity 保存到存储库

b) 检查是否可以检索 MyEntity

c) 检索时检查MyEntity的关联列表是否存在

除最后一步外,一切正常。在调试时,我发现实际上当使用 findOne() 或 findAll() 从存储库中检索时,它 returns MyEntity 和一个空的 MyObjects 列表。

我认为问题出在这些调用上,因为 save() 返回的 MyEntity 对象仍然包含 MyObjects 列表。

MyObjects 通过@ManyToOne 关系关联到 MyEntity,并且 FetchType 是 LAZY,但是我添加了一个查询以便能够获取 EAGER(如此处 ) and that doesn't work either - but just to be safe I also tried changing FetchType to EAGER and that still doesn't work. (having looked at Java JPA FetchType.EAGER does not work and Hibernate one-to-many mapping eager fetch not working and Hibernate many to one eager fetching not working) 我阅读了 findOne 和 findAll 但根据这个 - When use getOne and findOne methods Spring Data JPA - 应该没问题。

我花了很长时间寻找答案,尽管我可能遗漏了一些东西,而且我也是休眠和一般编码的新手...非常感谢教育帮助!

这是我的代码: 首先是MyObjects的实体:

package com.mypackage.representation;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "myobject")
public class MyObject implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "my_entity_id")
    @JsonIgnore
    private MyEntity myEntityId;
    private BigDecimal someValue;

    protected MyObject(){

    }

    public MyObject (BigDecimal someValue){
        this.someValue = someValue;
    }

    // Getters and setters omitted for brevity
}

接下来,MyEntity:

package com.mypackage.representation;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class MyEntity implements Serializable {

    @Id
    @JsonProperty("some_id")
    private int someId;
    @OneToMany(mappedBy = "myEntityId", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JsonProperty("myObject")
    private List<MyObject> myObjects;

    protected MyEntity() {

    }

    public MyEntity(int someId, List<MyObject> myObjects) {
        this.someId = someId;
        this.myObjects = myObjects;=
    }
    // Getters and setters omitted for brevity
}

测试代码:

package com.mypackage;

import com.mypackage.repository.MyEntityRepository;
import com.mypackage.representation.MyObject;
import com.mypackage.representation.MyEntity;
import org.hibernate.Session;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyApplicationTests {

    @Rule
    public final SessionFactoryRule sf = new SessionFactoryRule();

    @Autowired
    MyEntityRepository myEntityRepository;

    @Test
    public void returnsMyEntityForSomeId() {
        Session session = sf.getSession();

        int someId = 0;
        List<MyObject> myObjects =createMyObjects();
        int numberOfMyObjectsIn = myObjects.size();
        MyEntity myEntity = createMyEntity(myObjects, someId);

        MyEntity saved = myEntityRepository.save(myEntity);

        sf.commit();

        // Test that entities are loaded into database

        List<MyEntity> allMyEntities = myEntityRepository.findAll();
        assertNotNull(allMyEntities);

        // Test that MyEntity for given someId is returned, with myObjects as assigned -- code for fetch function is below

        MyEntity thisEntity = myEntityRepository.findByIdAndFetchMyObjectsEagerly(someId);
        assertNotNull(thisEntity);
        int numberOfMyObjectsOut = (thisEntity.getMyObjects()).size();
        assertNotNull(thisEntity.getMyObjects());
        // This following test fails:
        assertEquals(numberOfMyObjectsIn, numberOfMyObjectsOut);
    }

    private List<MyObjects> createMyObjects() {
        MyObject myObjectOne = new MyObject(BigDecimal.valueOf(40));
        MyObject myObjectTwo = new MyObject(BigDecimal.valueOf(50));
        MyObject myObjectThree = new MyObject(BigDecimal.valueOf(60));

        List<MyObjects> myObjects = new ArrayList<>();
        myObjects.add(myObjectOne);
        myObjects.add(myObjectTwo);
        myObjects.add(myObjectThree);

        return myObjects;
    }

    private MyEntity createMyEntity(List<MyObject> myObjects, int someId) {
        return new MyEntity(someId, myObjects);
    }
}

这是获取函数的代码:

package com.mypackage.repository;

import com.mypackage.representation.MyEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {

    @Query("SELECT a FROM MyEntity a LEFT JOIN FETCH a.myObjects WHERE a.someId = (:someId)")
    MyEntity findByIdAndFetchMyObjectsEagerly(@Param("someId") Integer someId);
}

最后,这是我的 SessionFactoryRule:

package com.mypackage;

import com.mypackage.representation.*;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;

import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.service.ServiceRegistry;


public class SessionFactoryRule implements MethodRule {
    private SessionFactory sessionFactory;
    private Transaction hibernateTransaction;
    private Session session;

    @Override
    public Statement apply(final Statement statement, FrameworkMethod method,
                           Object test) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                sessionFactory = createSessionFactory();
                createSession();
                beginTransaction();
                try {
                    statement.evaluate();
                } finally {
                    shutdown();
                }
            }
        };
    }

    private void shutdown() {
        try {
            try {
                try {
                    hibernateTransaction.rollback();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                session.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            sessionFactory.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private SessionFactory createSessionFactory() {
        Configuration configuration = new Configuration();
        configuration.addAnnotatedClass(MyEntity.class)
                .addAnnotatedClass(MyObject.class);
        configuration.setProperty("hibernate.dialect",
                "org.hibernate.dialect.H2Dialect");
        configuration.setProperty("hibernate.connection.driver_class",
                "org.h2.Driver");
        configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem:./db");
        configuration.setProperty("hibernate.hbm2ddl.auto", "create-update");
        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        System.out.println("Hibernate serviceRegistry created");

        SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        return sessionFactory;
    }

    public Session createSession() {
        session = sessionFactory.openSession();
        return session;
    }

    public void commit() {
        hibernateTransaction.commit();
    }

    public void beginTransaction() {
        hibernateTransaction = session.beginTransaction();
    }

    public Session getSession() {
        return session;
    }
}

MyEntity.myObjects 是关联的 inverse 端,仅将 MyObject 的实例放入列表中 not建立MyEntityMyObject之间的关联。您绝对需要将 MyObject.myEntityId 设置为适当的值,因为这是关联的 owning 方。

作为旁注,如果您希望能够保存从前端收到的 MyEntity 以及稍后的子列表,请考虑使用 @JsonBackreference 而不是 @JsonIgnoreMyObject.myEntityId 上。这样,将不再需要手动设置 MyObject.myEntityId

请记住,除非您将提取类型更改为 EAGER,否则您可能不会在调试器中看到 MyObject 的填充列表。您可能需要显式调用 MyEntity.getMyObjects() 才能实际加载列表。