I found weird Hibernate behavior that I can not understand.
Let's say I have class A (was inspired with this question JPA: How to have one-to-many relation of the same Entity type)
#Entity
public class A {
#Id
private String id;
private String name;
#ManyToOne
#JoinColumn(name = "PARENT")
private A parent;
#OneToMany(mappedBy = "parent",cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private Set<A> children;
// Getters, Setters, etc...
}
Also, say we have Spring JPA Repository
public interface ARepository extends JpaRepository<A, Long> {}
And test class where magic happens
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:spring/applicationContext-test.xml" })
public class ATest {
#Autowired
private ARepository aRepository;
#Test
public void testA() {
A parent = new A();
parent.setName("I am Parent: 121_1001_21");
parent.setId("121_1001_21");
A son = new A();
son.setName("I am Son: 121_1001_31");
son.setId("121_1001_31");
son.setParent(parent);
A daughter = new A();
daughter.setName("I am Daughter: 121_1001_32");
daughter.setId("121_1001_32");
daughter.setParent(son);
// daughter.setParent(parent);// yes, I'm intentionally creates wrong hierarchy
parent.setChildren(new HashSet<>(Arrays.asList(daughter, son)));// order of elements in set matters!
son.setChildren(new HashSet<>(Arrays.asList(daughter)));
aRepository.save(parent);
}
}
So the hierarchy is following:
Parent (121_1001_21)
Son (121_1001_31)
Daughter (121_1001_32)
Daughter (121_1001_32)
But this test fails on saving entity with
javax.persistence.EntityNotFoundException:
Unable to find com.orga.pcom.om.core.entity.productorder.A with id 121_1001_31
After hours of debugging I found that Hibernates tries to load linked entities and load it in this way:
Parent (121_1001_21) 1st load
Son (121_1001_31) 3rd load (this entity loading is fail!)
Daughter (121_1001_32) 2nd load
Daughter (121_1001_32) 2nd load
and fails! So, the questions are:
Why Hibernate loads something while it saves something? :)
What is the best way to fix this issue?
Related
I'm just trying to understand how certain parts of Spring Jpa/Hibernate work. As the title says orphanRemoval only seems to work if entities are flushed to database between adding and removing a child entity from a collection and I'm wondering why.
I've got a parent class with a #OneToMany association with a child class
#Entity
class Parent {
#Id
#GeneratedValue
#Getter
private Long id;
#OneToMany(mappedBy = parent, cascade=ALL, orphanRemoval=true)
#Getter
private Set<Child> children;
public Parent(){
children = new HashSet<Child>();
}
public Child addChild(Child child){
child.setParent(this);
children.add(child);
return child;
}
public void removeChild(Child child){
child.setParent(null);
children.remove(child);
}
}
#Entity
class Child {
#Id
#GeneratedValue
#Getter
private Long id;
#ManyToOne
#Setter
private Parent parent;
}
I was testing to get the child to delete when removed from the parent like so (using an #Autowired JPARepository<Parent,Long> and #JpaTest annotation)
#ExtendWith(SpringExtension.class)
#DataJpaTest
class PersistTest {
#Autowired ParentRepository repo; // JpaRepository<Parent, Long>
#Autowired EntityManager em;
#Test
public void whenChildRemoved_thenChildDeleted(){
Parent parent = new Parent();
Child child = parent.addChild(new Child());
repo.save(parent);
em.flush(); // test fails if removed
parent.removeChild(child);
repo.saveAndFlush(parent);
assertThat(repo.findById(parent.getId()).get().getChildren()).isEmpty();
assertThat(em.find(Child.class, child.getId()).isNull();
}
}
If the entity manager is not flushed between adding the child to the parent and removing it then both assertions fail and when looking at the generated sql there is no DELETE statement made. If the EM is flushed then a DELETE is made and the tests pass.
Basically just wondering if anyone could explain why this is the case and if putting the two operations in separate #Transactional methods would have the same effect.
You expect some transactions being finished or created, but in reality they didn't. That is why you see this behavior.
#DataJpaTest places on every method separate transaction (which will be rolled back anyway by default BTW);
That is why you can use JpaRepository -- it does not create the transaction itself (in the opposite to CrudRepository), but there is underlying one;
If JpaRepository used #Transactional(REQUIRED_NEW), you'd may remove flush;
Answer to your last question. If you put those operations in the separate #Transactional methods, it will work exactly in the same way, because there is underlying transaction in the test created by the #DataJpaTest -- hibernate usually flushes at the very end of the method. You'd have to use #Transactional(REQUIRED_NEW).
I have an SpringBoot application in which I have defined an entity as given below
#Entity
public class Organisation {
#Id
#GeneratedValue
#JsonIgnore
private Long id;
private String entityId;
#OneToMany(mappedBy = "parent")
#Where(clause = "active_ind=true")
#JsonIgnore
private Set<Organisation> activeSubOrgs = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parentId")
private Organisation parent;
public Set<Organisation> getActiveSubOrgs() {
return activeSubOrgs;
}
In my service class I have a function to fetch the children
public Set<Organisation> getChildrenForEntity(String entityId) {
Organisation parent = organisationRepository.findByEntityIdAndActiveInd(entityId, true);
return parent.getActiveSubOrgs();
}
This works fine and gets the children when called from rest controller, but when I use the same function to test in junit, it always returns empty. In my sql trace log I see that query is not being triggred when getActiveSubOrgs() is being called. My junit test is as given below
#SpringBootTest
#RunWith(SpringRunner.class)
#Transactional
public class OrgServiceTest {
#Autowired
private OrganisationService organisationService;
#Before
public void setup() {
Organisation company = new Organisation("c", true);
company = organisationRepository.save(company);
Organisation circle = new Organisation("circle1", true);
circle.setParent(company);
circle = organisationRepository.save(circle);
Organisation div1 = new Organisation("div1", true);
div1.setParent(circle);
div1 = organisationRepository.save(div1);
}
#Test
public void getChildrenForEntitySuccessTest() {
Set<Organisation> children = organisationService.getChildrenForEntity("c");
System.out.println(children.iterator().next().getEntityId());
assertEquals("circle1", children.iterator().next().getEntityId());
}
The children Set in the test is empty when it should actually have circle1. I have tried calling Hibernate.initialize() on children, but that did not work either.
The issue is that bidirectional relationships have to be updated on both sides, i.e. the parent and the children have to know about each other. In your function setup(), you just define the parent of the child. Therefore, each child knows about its parent. However, the parent does not know about its children.
For bidirectional relationships, a good way of handling this is to define a function for one class to set/add a property and automatically update the other one. In case of OneToMany relationships, this can be handled nicely with add(entity) functions.
public void addActiveSubOrg(Organisation activeSubOrg) {
this.activeSubOrgs.add(activeSubOrgs);
activeSubOrg.setParent(activeSubOrg);
}
I already looked at previous questions but all the solutions still doesn't work on my project.
I have a CUBA Platform project that uses spring core 5.2.3. CUBA uses the ORM implementation based on the EclipseLink framework.
I have 1 MainClass Entity, and children, SubClass Entity.
MainClass Definition
//annotations here
public class MainClass{
#Composition
#OnDelete(DeletePolicy.CASCADE)
#OneToMany(mappedBy = "mainClass", cascade = CascadeType.ALL, orphanRemoval = true)
protected List<SubClass> subClass;
public Category getCategory() {
return category;
}
}
//SubClass entity
//annotations here
public class SubClass{
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "MAINCLASS_ID")
protected MainClass mainClass;
}
The problem with this setup is that it only saves the MainClass Entity but not the SubClass Entity.
Service Class
#Service("MainService")
public class ServiceClass{
#Inject
private Persistence persistence;
#Transactional
public void saveOrUpdateMain(MainClass mainClass){
MainClass qMainClass = (MainClass) entityManager.createQuery("select
b from main_Class b where b.extID = ?1")
.setParameter(1, extID).getSingleResult();
//assume mainClass is not null, set the primary key of qMainClass to mainClass
mainClass.setId(qMainClass.getId());
entityManager.merge(mainClass);
}
}
I have read this 2 links but still did not solve my issue.
Why merging is not cascaded on a one to many relationship
JPA does not insert new childs from one to many relationship when we use merge
In CUBA, DataManager is a preferred option to work with data. It automatically resolves cascade operations and does not require explicit transaction definition. Please try to implement this logic using DataManager first.
I am creating a spring boot application and use it's build in JpaRepository interface to store my entities. I have the following two entities (removed getters and setters for readability):
Profile entity
#Entity
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "profileOne", orphanRemoval = true)
private List<Match> matchOnes;
#OneToMany(mappedBy = "profileTwo", orphanRemoval = true)
private List<Match> matchTwos;
}
Match entity
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames = { "profileOne_id", "profileTwo_id" })
})
public class Match {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn(name = "profileOne_id")
private Profile profileOne;
#ManyToOne
#JoinColumn(name = "profileTwo_id")
private Profile profileTwo;
}
To understand the JpaRepository behavior I wrote the following unit test.
#RunWith(SpringRunner.class)
#DataJpaTest
public class IProfileDaoTest {
#Autowired
private IProfileDao profileDao; //This class extends JpaRepository<Profile, long>
#Autowired
private IMatchDao matchDao; //This class extends JpaRepository<Match, long>
#Test
public void saveProfileTest() throws Exception {
#Test
public void profileMatchRelationTest() throws Exception {
//Test if matches stored by the IMatchDao are retrievable from the IProfileDao
Profile profileOne = new Profile("Bob"),
profileTwo = new Profile("Alex");
profileDao.saveAndFlush(profileOne);
profileDao.saveAndFlush(profileTwo);
matchDao.saveAndFlush(new Match(profileOne, profileTwo));
profileOne = profileDao.getOne(profileOne.getId());
Assert.assertEquals("Match not retrievable by profile.", 1, profileOne.getMatchOnes().size());
}
}
Now I expected the matches to have appeared in the profile entity but they do not. I also tried adding CascadeType.ALL to the #ManyToOne annotation in the match entity and adding FetchType.EAGER to the #OneToMany annotation in the profile entity.
Is it possible to get the matches saved with the matchDao by requesting a profile in the profileDao? Or should I find the matches with a profile with a separate function?
Spring Data repositories don't write to the database immediately for performance (and probably other) reasons. In tests, if you need to test query methods you need to use the TestEntityManager provided by #DataJpaTest (it's just the entity manager that the repositories use anyway in the background, but with a few convenience methods for testing).
Update 1:
The matches aren't added to the profile. To make sure the relationship is bidirectional the matches should have the profiles but the profiles should also have the matches.
Try this:
#RunWith(SpringRunner.class)
#DataJpaTest
public class IProfileDaoTest {
#Autowired
private IProfileDao profileDao; //This class extends JpaRepository<Profile, long>
#Autowired
private IMatchDao matchDao; //This class extends JpaRepository<Match, long>
#Autowired
private TestEntityManager testEntityManager;
#Test
public void saveProfileTest() throws Exception {
#Test
public void profileMatchRelationTest() throws Exception {
//Test if matches stored by the IMatchDao are retrievable from the IProfileDao
Profile profileOne = new Profile("Bob"),
profileTwo = new Profile("Alex");
//Persist the profiles so they exist when they are added to the match
entityManager.persistAndFlush(profileOne);
entityManager.persistAndFlush(profileTwo);
//Create and persist the match with two profiles
Match yourMatch = entityManager.persistFlushFind(new Match(profileOne, profileTwo));
//Add the match to both profiles and persist them again.
profileOne.matchOnes.add(yourMatch);
entityManager.persistAndFlush(profileOne);
profileTwo.matchTwos.add(yourMatch);
entityManager.persistAndFlush(profileTwo);
profileOne = profileDao.getOne(profileOne.getId());
Assert.assertEquals("Match not retrievable by profile.", 1, profileOne.getMatchOnes().size());
}
}
Everything in your test happens in the same JPA session. Such a session guarantees that every entity is included only once. So when you execute
profileOne = profileDao.getOne(profileOne.getId());
you are getting the exact instance back you created 5 lines above.
Hibernate nor any other JPA implementation will change anything in the entity for loading.
If you want to actually reload an entity you'll have to either evict it first from the entity manager or use a fresh Session/EntityManager.
For more details see chapter 3 of the JPA specification.
I try to build a bidirectional relationship. I am using Spring Boot 1.5.4.RELEASE with Spring Boot JPA to generate my repositories. I try to save two entities which are associated to each other, but it isnt working. I commented the test-statements which fails.
My Entites:
Driver:
#Entity
#ToString
#EqualsAndHashCode
public class Driver {
public static final String COLUMN_CAR = "car";
#Id
#GeneratedValue
private long id;
#Getter
#Setter
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = COLUMN_CAR)
private Car car;
}
Car:
#Entity
#ToString
#EqualsAndHashCode
public class Car {
#Id
#GeneratedValue
private long id;
#Getter
#Setter
#OneToOne(mappedBy = Driver.COLUMN_CAR)
private Driver driver;
}
I used Spring JPA to generate repositories.
DriverRepository:
#Repository
public interface DriverRepository extends CrudRepository<Driver, Long> { }
CarRepository:
#Repository
public interface CarRepository extends CrudRepository<Car, Long> { }
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class StackoverflowTest {
#Autowired
private DriverRepository driverRepository;
#Autowired
private CarRepository carRepository;
#Test
public void test1() {
Driver driver = driverRepository.save(new Driver());
Car car = carRepository.save(new Car());
driver.setCar(car);
driverRepository.save(driver);
/* Success, so the driver got the car */
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
/* Fails, so the car doesnt got the driver */
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
}
#Test
public void test2() {
Driver driver = driverRepository.save(new Driver());
Car car = carRepository.save(new Car());
car.setDriver(driver);
carRepository.save(car);
/* Success, so the car got the driver */
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
/* Fails, so the driver doesnt got the car */
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
}
}
In both tests the last statement fails. Any ideas? Thanks in Advice.
Several mistakes in what you posted.
First:
#OneToOne(mappedBy = Driver.COLUMN_CAR)
mappedBy expects the name of the Java field/property on the other side of the association. Not the name of the database column. It works here because both happen to have the same name.
Second:
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
That fails simply because you're doing everything in a single transaction, and you failed to properly initialize the two sides of the association. So car.driver is just as you initialized it: null.
Third:
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
You made the same mistake as before, but worse. Here, you only initialized one side of the association, but you initialized the inverse side of the association (the one which has the mappedBy attribute). So the association won't even be saved in the database, as it would have been in your previous snippet.