1

I have a problem with loading a collection of related entites with @OneToMany and @ManyToOne mapping with no cascadeType set.

FYI, we use Lombok.

Base entity - a parent class of all our entities:

@SuperBuilder
@Getter
@Setter
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

  @Id
  @GeneratedValue
  @Column(columnDefinition = "uuid", updatable = false)
  private UUID id;

  @Version @Builder.Default private long version = 1;
}

An entity on "one" side:

@SuperBuilder
@Getter
@Setter
@ToString(includeFieldNames = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Entity
@Table(name = "companies")
public class Company extends BaseEntity {

  @ToString.Exclude
  @OneToMany(mappedBy = "company")
  private List<OperatingFacility> operatingFacilities;

}

An entity on "many" side:

@SuperBuilder
@Getter
@Setter
@ToString(includeFieldNames = true)
@NoArgsConstructor
@Entity
@Table(name = "operating_facilities")
public class OperatingFacility extends BaseEntity {

  @ToString.Exclude
  @ManyToOne()
  @JoinColumn(name = "company_id")
  private Company company;

}

Here is my test case:

    Company company = companyRepository.save(Company.builder().build());

    OperatingFacility operatingFacility =
        operatingFacilityRepository.save(
            OperatingFacility.builder()
                .company(company)
                .build());

    final List<OperatingFacility> gotFoundByCompanyId =
        operatingFacilityRepository.findByCompanyId(company.getId());

    // SUCCESS At this point a link looks OK
    Assertions.assertEquals(1, gotFoundByCompanyId.size());

    Optional<Company> companyOptional = companyRepository.findById(company.getId());
    Assertions.assertTrue(companyOptional.isPresent());
    Assertions.assertNotNull(companyOptional.get().getOperatingFacilities());
    
    // FAIL When gathering operating facilities from Company, then there are 0
    Assertions.assertEquals(1, companyOptional.get().getOperatingFacilities().size());

When I search for OperatingFacilities by company id, then it works just fine. But when I want to get them using a fetched Company then a collection is empty (not null).

I've checked similar questions e.g. Can someone explain mappedBy in JPA and Hibernate? , but I do thing exactly as other working solutions.

One point what is different is there is no cascadeType set in Company. I want to handle OperatingFacility items using a dedicated OperatingFacilityRepository.

zolv
  • 1,720
  • 2
  • 19
  • 36
  • The cascadeType is not the issue, that much I can tell. I would guess that it has something to do with caching and test not actually committing the data to the database. Once you commit the inserts to the database and query afterwards, it should work fine. You can test it by manually creating entities in the database and then searching by ID in the test. This should then properly return the values you expect – XtremeBaumer Jun 28 '22 at 12:53

1 Answers1

0

This is a bidirectional relationship that is controlled by the OperatingFacility.company reference - it is common for users to only set this one side and think JPA will automatically fix the other side for you; this is not the case. If you add it to one side, you must maintain both sides to keep the model in synch with the database as they are POJOs that may be cached. If you don't add operatingFacility to the company's list yourself, it will only get there if you force a refresh/reload of the entity from the database using em.refresh(). A read in a cleared context (em.clear()) might also work if there isn't a second level cache.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • Even when I add `em.clear();` after `operatingFacilityRepository.save(...); it doesn't help. Still returns 0. I tried also with `em.refresh(companyOpt.get());` - same effect. – zolv Jun 30 '22 at 07:47
  • Also, when I add an `operatingFacility` to the collection in `company`: `company.getOperatingFacilities().add(operatingFacility);` it throws `UnsupportedOperationException` on List's add operation. – zolv Jun 30 '22 at 07:50
  • First, when you call save, does it issue SQL statements to the database before you call clear? Flush first, then clear - but your 'entity' will always return 0. You have to re-read it from the database after a clear call. As for refresh, again, make sure you've flushed the EntityManager or those changes will not exist in the database. Be sure to turn on SQL logging as it will show you what is happening and when. As for adding to a List; that pretty much breaks the contract on List, and you are the one building the company with your builder - what are you putting in there? – Chris Jul 04 '22 at 14:14