bekimarton
bekimarton

Reputation: 13

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

I am writing a test for a webservice that uses hibernate, JPArepository and a mysql database. Everything works well there, data is preloaded into the database and can be retrieved correctly (when the test is not used), however the test fails.

The test uses a H2 in memory database. It compromises of:

a) Saving a MyEntity to the database

  1. Creating MyObjects, and saving them into List

  2. Creating MyEntity with the associated List

  3. Saving MyEntity to the repository

b) Checking whether MyEntity can be retrieved

c) Checking whether MyEntity's associated List is there when retrieving

All works fine, apart from the last step. On debugging, I have found that actually when retrieved from the repository using findOne() or findAll(), it returns MyEntity with an empty list of MyObjects.

I think the issue is with these calls, since the returned MyEntity object of save() still has the list of MyObjects.

MyObjects is associated to MyEntity with a @ManyToOne relationship, and FetchType is LAZY, however I have added a query to be able to fetch EAGER (as here JPA: Join Fetch results to NULL on empty many side) 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) I read up on findOne and findAll but according to this - When use getOne and findOne methods Spring Data JPA - it should be fine.

I have spent a long time looking for answers although it is possible that I have missed something, and I'm also a newbie to hibernate and coding in general... Educational help would be highly appreciated!

Here is my code: First, the entity that is 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
}

Next, 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
}

The test's code:

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

Here's the code for the fetch function:

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

And finally, here's my 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;
    }
}

Upvotes: 1

Views: 4180

Answers (1)

crizzis
crizzis

Reputation: 10716

MyEntity.myObjects is the inverse side of the association, merely putting instances of MyObject in the list does not establish the association between MyEntity and MyObject. You absolutely need to set MyObject.myEntityId to an appropriate value since that's the owning side of the association.

As a side note, if you want to be able to save MyEntity received from the front-end together with a list of children later on, consider using @JsonBackreference instead of @JsonIgnore on MyObject.myEntityId. This way, setting MyObject.myEntityId manually will no longer be required.

Keep in mind that unless you change the fetch type to EAGER, you might not see a populated list of MyObject in the debugger. You might need to call MyEntity.getMyObjects() explicitly to actually load the list.

Upvotes: 1

Related Questions