2

I am working through my first Spring boot application using Hibernate/JPA. I have created two @Entities with a @ManyToMany mapping: Team and Game.

Currently, the database has a table holding teams and I'd like to insert a game which references two teams.

The issue is that i am getting the error:

`org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataLoader': Invocation of init method failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.data.entities.FootballTeam.homeGames, could not initialize proxy - no Session
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:838) ~[spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537) ~[spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at org.springframework.boot.SpringApplication.doRun(SpringApplication.java:347) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:295) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1112) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1101) [spring-boot-1.3.0.RELEASE.jar:1.3.0.RELEASE]
    at com.example.FootballPoolSpringSpringBoot.main(FootballPoolSpringSpringBoot.java:11) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-1.3.0.RELEASE.jar:1.3.0.RELEASE]
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.data.entities.FootballTeam.homeGames, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:576) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:215) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:555) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.write(AbstractPersistentCollection.java:400) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
    at org.hibernate.collection.internal.PersistentBag.add(PersistentBag.java:314) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
    at com.example.service.DataLoader.loadData(DataLoader.java:106) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:354) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:305) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    ... 23 common frames omitted`. 

What am I doing wrong?

@Entity
public class FootballTeam {

private FootballTeam(){/**/}

public FootballTeam(String teamName) {
    this.teamName = teamName;
}



@Id
@GeneratedValue
private Long teamId;

@Column(name = "team_name", unique = true)
private String teamName;

@Column(name = "short_name", unique = true)
private String teamShortName;

private String teamLeague;
private String teamDivision;

@Embedded
private Location teamLocation;

@ManyToMany(cascade = CascadeType.ALL, mappedBy = "favoriteTeams")
private List<Users> fans = new ArrayList<>();

@OneToMany(mappedBy = "homeTeam")
List<FootballGame> homeGames = new ArrayList<>();

@OneToMany(mappedBy = "visitingTeam")
List<FootballGame> awayGames = new ArrayList<>();

/* getters and setters omitted for brevity */
}

Game.java

@Entity
@Table(name = "football_game",
    uniqueConstraints= @UniqueConstraint(
            columnNames={"played_on", "home_team", "visiting_team"}))
public class FootballGame {

private FootballGame(){/**/}

public FootballGame(Date playedOn, FootballTeam homeTeam, FootballTeam visitingTeam) {
    this.homeTeam = homeTeam;
    this.playedOn = playedOn;
    this.visitingTeam = visitingTeam;
}

@Id
@GeneratedValue
private Long gameId;

@Column(name = "played_on", nullable = false)
private Date playedOn;

@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name = "home_team", nullable = false)
private FootballTeam homeTeam;

@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name = "visiting_team", nullable = false)
private FootballTeam visitingTeam;

private int homeTeamScore;
private int visitingTeamScore;

@OneToOne(mappedBy = "footballGame")
private FootballPool footballPool;

/* getters and setters omitted for brevity */
}

DataLoader.class

  @Service
public class DataLoader {

    private FootballTeamRepository teamRepository;
    private FootballGameRepository gameRepository;

    @Autowired
    public DataLoader(FootballGameRepository gameRepository,
                      FootballTeamRepository teamRepository) {
        this.gameRepository = gameRepository;
        this.teamRepository = teamRepository;
}

@PostConstruct
private void loadData(){
    FootballTeam f4 = new FootballTeam("lil Shin-Kickers");
    f4.setTeamLocation(new Location("California"));
    teamRepository.save(f4);

    FootballTeam f1 = new FootballTeam("Tumble Tots");
    f1.setTeamLocation(new Location("Colorado"));
    teamRepository.save(f1);

    FootballTeam homeTeam = teamRepository.findTeamByTeamNameIgnoreCase("lil Shin-Kickers");

    System.out.println(homeTeam.getTeamName());
    FootballTeam visitingTeam = teamRepository.findTeamByTeamNameIgnoreCase("Tumble Tots");

    FootballGame footballGame = new FootballGame(new Date(), homeTeam, visitingTeam);
    footballGame.setHomeTeamScore(7);
    footballGame.setVisitingTeamScore(21);

    gameRepository.save(footballGame);

}
StillLearningToCode
  • 2,271
  • 4
  • 27
  • 46

3 Answers3

2

This is pointing to your error:

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.data.entities.FootballTeam.homeGames, could not initialize proxy - no Session

@OneToMany and @ManyToMany (and with Hibernate I believe @ManyToOne & @OneToOne) are lazily instantiated. That means it won't load the Object or list of Objects into memory from the database until needed. In your case you are getting the exception because when you referenced the FootballTeam class the session was already closed or not in scope.

If the mappings will not be to large you can specify the fetching strategy on the mapping as:

@OneToMany(mappedBy = "homeTeam", fetch = FetchType.EAGER)

This will load the instance(s) into memory right away. Note you will probably need to this with all your mappings.

UDPATE: The below option won't work for @PostContruct method. See David Lizárraga comment below. Will work for more general cases such as making a call from a Controller to a Service Class and annotating the Service method with @Transactional

If loading all into memory right away is not an option, you will have to look into ensuring your Session is still in scope when referencing the mapped Objects. For your current case, this can be done by annotating your method with the @Transactional annotation. In general you need to ensure that the methods accessing your lazily loaded Objects are done within a @Transaction method.

DavidR
  • 6,622
  • 13
  • 56
  • 70
  • Using the eager fetching worked, but when I used @Transactional on loadData instead, it still gave me the error. Is there something else I need to do? – StillLearningToCode Dec 23 '15 at 07:21
  • 2
    `@Transactional` will not work in a `@PostConstruct` method. You will have to manage the transaction manually. See http://stackoverflow.com/a/18790494/3132058 – David Lizárraga Dec 23 '15 at 11:20
1

In a JPA many to many relationship, if cascade type has been set at CascadeType.PERSIST (or CascadeType.ALL, which includes CascadeType.PERSIST), then while saving the parent and updating it with references of the child, it will try to save the child again.

Since you're using Hibernate, try:

@ManyToOne(cascade = CascadeType.MERGE)
Jim Archer
  • 1,337
  • 5
  • 30
  • 43
  • I made that change and the game has been inserted into the table, now i am getting an error: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.data.entities.FootballTeam.homeGames. do i need to do i need to do a save on both entities? – StillLearningToCode Dec 22 '15 at 22:46
  • Well update the question with the entire error. Also, depending upon the exact the issue, the @Version answer may be what you need. – Jim Archer Dec 22 '15 at 22:47
1

So if you are getting "detached entity error", which means Your child objects does not have any flag to be tracked. So anytime you do save it will consider child as new object. So just put "version" field on entity with annotation @version and version field to related table. Also when saving new child make sure you set parent on the child.

smile
  • 498
  • 7
  • 18
  • To clarify, you are referring to the FootballGame class as the parent and Team as the child? Then to set the parent in the child class i would add the FootballGame to the corresponding member List before calling save on the footballGame object? – StillLearningToCode Dec 22 '15 at 23:05