【问题标题】:JPA repository: list of objects associated to @Entity is empty when Entity is returned using findAll()JPA 存储库:使用 findAll() 返回 Entity 时,与 @Entity 关联的对象列表为空
【发布时间】:2018-06-19 16:18:45
【问题描述】:

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

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

a) 将 MyEntity 保存到数据库中

  1. 创建 MyObjects,并将它们保存到列表中

  2. 使用关联列表创建 MyEntity

  3. 将 MyEntity 保存到存储库

b) 检查是否可以检索 MyEntity

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

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

我认为问题在于这些调用,因为 save() 返回的 MyEntity 对象仍然具有 MyObjects 列表。

MyObjects 通过@ManyToOne 关系与 MyEntity 相关联,并且 FetchType 是 LAZY,但是我添加了一个能够获取 EAGER 的查询(如这里 JPA: Join Fetch results to NULL on empty many side),这也不起作用 - 但只是为了安全我也尝试将 FetchType 更改为 EAGER,但这仍然不起作用。 (看过Java JPA FetchType.EAGER does not workHibernate one-to-many mapping eager fetch not workingHibernate 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
}

接下来,我的实体:

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;
    }
}

【问题讨论】:

    标签: java hibernate jpa spring-boot many-to-one


    【解决方案1】:

    MyEntity.myObjects 是关联的inverse 侧,仅将MyObject 的实例放入列表并 建立MyEntity 和@987654324 之间的关联@。您绝对需要将MyObject.myEntityId 设置为适当的值,因为这是关联的拥有 方面。

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

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

    【讨论】:

    • 这很有意义,谢谢,手动设置效果很好。但是,您的第二个解决方案似乎更优雅,但简单地将 JsonIgnore 标记更改为 JsonBackreference 并不能单独解决问题。在创建 MyEntity 之后,我还需要以某种方式更新 MyObjects 吗?
    • @JsonBackreference 将使Jackson 用父对象填充MyObject.myEntityId当 Spring 从 JSON 反序列化对象时,然后将它们传递给您的休息控制器方法。您不会在测试中看到这一点,因为该测试不涉及 JSON 反序列化。我只是假设您打算将它与 REST 一起使用,因此提示。
    • 太棒了。再次感谢,超级有用的解释。
    猜你喜欢
    • 2021-09-03
    • 2019-08-16
    • 2021-06-27
    • 1970-01-01
    • 2018-03-21
    • 2021-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多