0

I am trying to populate my database, and during this, I create a associations between employees and teams. I'm trying to access the team members on a team via a hibernate OneToMany reference and doing lazy loading. This works great in my app, except when I call it right after the entities are created.

I'm fairly certain this is a hibernate transaction or caching issue, as all of the rows exist in the database, but I'm struggling to understand what the issue is. I have tried converting all of the saves in the populateTeamsAndEmployees function to saveAndFlush, but that did not help.

How can I make team.getTeamMembers() work every time it is called?

Note: This only happens when I call the seedData() function via RequestMapping, but it doesn't happen when I call seedData() with a @Scheduled annotation


Function with Problem

    void seedData() {
       populateTeamsAndEmployees()
       otherBusinessLogic()
    }
    
    public void otherBusinessLogic() {
       List<Team> teams = teamRepository.findAll();
    
       teams.foreach(team -> {
            /*
            works great every time, returns all team members
            */
            List<Employee> teamMembers = employeeRepository.findEmployeeByTeamId(team.getId()); 


            /*
            this returns null when ran immediately after populateTeamsAndEmployees(), 
            but returns the same as the line above all other times
            */
            List<Employee> thisDoesntWork = team.getTeamMembers();
       });
    
    }

    public void populateTeamsAndEmployees() {
        List<EmployeeFromOtherSystem> employeesToConvert = otherSystemRepo.findAll();
        employeesToConvert.foreach(otherSysEmployee -> {
            employeeRepository.save(otherSysEmployee.toEmployee());
        });
    }

Entities:

class Team {
    @OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
    private List<Employee> teamMembers;
}

class Employee {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
Community
  • 1
  • 1
  • Since you have declared the `FetchType` as Lazy, the related children will not be retrieved from the DB during a simple query. - you can change it to `@OneToMany(mappedBy = "team", fetch = FetchType.EAGER)` - however, there might be performance issues if your entity relationships are overly complex or if the number of 'fetch' invocations is high – blurfus Mar 09 '20 at 18:34
  • I've tried changing `FetchType` to eager as well and that doesn't fix it. – Zach Romano Mar 09 '20 at 18:37
  • did you adjust it on the `Employee` side as well ? (just checking, just in case) – blurfus Mar 09 '20 at 18:39
  • Yeah, I changed both to `FetchType.Eager`. I'm assuming this has something to do with it being called immediately after the data being created in the database, because `team.getTeamMembers()` works perfectly any other time. – Zach Romano Mar 09 '20 at 18:43
  • If that's the case, you might have to commit first and then fetch them (although I am not sure, I have not looked at the code closely enough, I'll admit) – blurfus Mar 09 '20 at 18:44
  • is your `populateTeamsAndEmployees()` annotated as a transaction? – blurfus Mar 09 '20 at 18:50
  • It's weird because the data is in the database (must be since the top line is working), but the relationship isn't working.I've also tried adding @Transactional on `populateTeamsAndEmployees()` with no luck – Zach Romano Mar 09 '20 at 18:51
  • Well, yes, because at the end of the method there might be a *soft commit* cached somewhere... you could also try inserting a `.flush()` between the two method invocations to get them more in sync. - or `.refresh()` - https://stackoverflow.com/a/47414189/600486 – blurfus Mar 09 '20 at 18:53
  • I've tried calling `employeeRepository.flush()` and `teamRepository.flush()` at the end of `populateTeamsAndEmployees()` but once again no luck. I've never had this much trouble with hibernate. – Zach Romano Mar 09 '20 at 19:07
  • I think you need to call it on the `entityManager` - in between the `populate...()` and `otherBusiness...()` - did you annotate the `populate` method as `@Transactional` ? – blurfus Mar 09 '20 at 19:14
  • I've annotated the `populate` function, with `@Transactional` and no luck there. I'm not totally sure how to directly access the `entityManager`. – Zach Romano Mar 09 '20 at 19:32
  • I am out of ideas at the moment... if I have time later I'll try to repro – blurfus Mar 09 '20 at 20:03
  • Sincerely appreciate the help. I've noticed that hibernate is not even showing that it executed a query to find the team members on when run immediately after `populateTeamsAndEmployees` – Zach Romano Mar 09 '20 at 20:17
  • it might be retrieving it from cache – blurfus Mar 09 '20 at 20:19
  • It seems like it. If I run `populateTeamsAndEmployees` alone, then run `otherBusinessLogic` alone it works fine. It only fails when I run them both in the `seedData` function. Is there a way to disable this caching in with Spring? – Zach Romano Mar 09 '20 at 20:51
  • It's also only happening when I call `seedData()` via a request mapping in a controller, and it isn't happening when I call `seedData()` via a `@Scheduled` annotation. Very strange. – Zach Romano Mar 09 '20 at 21:18
  • What does `populateTeamsAndEmployees` look like? `@Transactional` annotations won't work as you might expect. See https://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method – Jens Schauder Mar 10 '20 at 07:18
  • I've added a basic idea of what `populateTeamsAndEmployees` looks like. – Zach Romano Mar 10 '20 at 17:00

1 Answers1

0

So the problem was that JPA was caching the entity right after I created it.

I was able to fix it by calling em.clear()

See below for how to access EntityManager with a JpaRepository:
https://dzone.com/articles/accessing-the-entitymanager-from-spring-data-jpa

And see below for why to use .clear()
EntityManager refresh