3

I am running into org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: myapp.myapp.models.Contact.messages, could not initialize proxy - no Session. I have looked into these similar questions Hibernate: LazyInitializationException: failed to lazily initialize a collection of role. Could not initialize proxy - no Session and How to solve the “failed to lazily initialize a collection of role” Hibernate exception but none of them help my situation. I had spring auto config my datasource to where I didn't have this issue but I added another datasource connection and then created a config file for each data source to which now everything works as normal, as it did before but I keep getting this error thrown. I don't know what to do. Any help is appreciated.

My DB info in my properties file looked like this before I added the other DB

##############DBs##################
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database=default

#Myapp DB
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myapp?        verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.username=myusername
spring.datasource.password=mypassword

Everything worked with no issues.

This is how everything is set up now.

Properties file

##############DBs##################
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database=default

#Myapp DB
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myapp?        verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.username=myusername
spring.datasource.password=mypassword

#Other DB
spring.seconddatasource.driverClassName = com.mysql.jdbc.Driver
spring.seconddatasource.url = jdbc:mysql://localhost:3306/other
spring.seconddatasource.username=myusername
spring.seconddatasource.password=mypassword
###################################

Contact Entity:

@Entity
@Table(name = "contact")
public class Contact {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "contact")
    private List<Messages> messages;

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public List<Messages> getMessages() {
        return this.messages == null ? null : new ArrayList<>(this.messages);
    }

    public void setMessages(List<Messages> messages) {
        this.messages = messages;
    }

    public void addMessage(Messages message) {
        this.messages.add(message); // this is where the error is being thrown
    }
}

Message Entity:

@Entity
@Table(name = "message")
public class Contact {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @ManyToOne
    @JoinColumn(name = "contactId", nullable = false)
    private Contact contact;

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Contact getContact() {
         return this.contact;
    }

    public void setContact(Contact contact) {
         this.contact = contact;
    }
}

New MyAppConfigClass(As soon as this was put in with other, the error started happening):

@ComponentScan
@Configuration
@EnableJpaRepositories(
    basePackages = { "myapp.myapp" },
    entityManagerFactoryRef = "myappEntityManagerFactory",
    transactionManagerRef = "myappTransactionManager")
@EnableTransactionManagement
public class MyAppDBConfiguration {

    @Autowired private ApplicationContext applicationContext;

    @Bean(name = "myappExceptionTranslator")
    public HibernateExceptionTranslator personnelHibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean(name = "myappTransactionManager")
    public PlatformTransactionManager personnelTransactionManager() {
        return new JpaTransactionManager(personnelEntityManagerFactory().getObject());
    }

    @Bean(name = "myappEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean personnelEntityManagerFactory() {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("myapp.myapp");
        factory.setDataSource(myappDataSource());
        factory.afterPropertiesSet();

        return factory;

    }

    @Primary
    @Bean(name = "myappDataConfig")
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties myappProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "myappData", destroyMethod = "")
    public DataSource myappDataSource() {
        DataSourceProperties properties = myappProperties();
        if (null != properties.getJndiName()) {
            JndiDataSourceLookup lookup = new    JndiDataSourceLookup();
            DataSource source = lookup.getDataSource(properties.getJndiName());
            excludeMBeanIfNecessary(source, "myappData");
            return source;
        } else {
            return properties.initializeDataSourceBuilder().build();
        }
    }

    private void excludeMBeanIfNecessary(Object candidate, String beanName) {
        try {
            MBeanExporter mbeanExporter = this.applicationContext.getBean(MBeanExporter.class);
            if (JmxUtils.isMBean(candidate.getClass())) {
                mbeanExporter.addExcludedBean(beanName);
            }
        } catch (NoSuchBeanDefinitionException ex) {
            // No exporter. Exclusion is unnecessary
        }
    }
}

This is the OtherConfigClass(almost exactly the same):

@ComponentScan
@Configuration
@EnableJpaRepositories(
    basePackages = { "myapp.other" },
    entityManagerFactoryRef = "otherEntityManagerFactory",
    transactionManagerRef = "otherTransactionManager")
@EnableTransactionManagement
public class OtherDBConfiguration {

    @Autowired private ApplicationContext applicationContext;

    @Bean(name = "otherExceptionTranslator")
    public HibernateExceptionTranslator personnelHibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean(name = "otherTransactionManager")
    public PlatformTransactionManager personnelTransactionManager() {
        return new JpaTransactionManager(personnelEntityManagerFactory().getObject());
    }

    @Bean(name = "otherEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean personnelEntityManagerFactory() {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("myapp.other");
        factory.setDataSource(otherDataSource());
        factory.afterPropertiesSet();

        return factory;

    }

    @Bean(name = "otherDataConfig")
    @ConfigurationProperties("spring.seconddatasource")
    public DataSourceProperties otherProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "otherData", destroyMethod = "")
    public DataSource textappotherDataSource() {
        DataSourceProperties properties = myappProperties();
        if (null != properties.getJndiName()) {
            JndiDataSourceLookup lookup = new    JndiDataSourceLookup();
            DataSource source = lookup.getDataSource(properties.getJndiName());
            excludeMBeanIfNecessary(source, "otherData");
            return source;
        } else {
            return properties.initializeDataSourceBuilder().build();
        }
    }

    private void excludeMBeanIfNecessary(Object candidate, String beanName) {
        try {
            MBeanExporter mbeanExporter = this.applicationContext.getBean(MBeanExporter.class);
            if (JmxUtils.isMBean(candidate.getClass())) {
                mbeanExporter.addExcludedBean(beanName);
            }
        } catch (NoSuchBeanDefinitionException ex) {
            // No exporter. Exclusion is unnecessary
        }
    }
}

This is the Application class:

@EnableAutoConfiguration
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
    }
}

So I'm assuming I'm missing something with the new config files that was done apart of the AutoConfig. That was the only change I made and it started throwing the error. Like I said above, the data is being saved into the database correctly but that error is still being thrown.

I don't know why this would be the case, an explanation would be very helpful.

Update:

ContactRepository:

@Repository
public interface ContactRepository extends JpaRepository<Contact, Long> {

}

MessagesRepository:

@Repository
public interface MessagesRepository extends JpaRepository<Messages, Long> {

}

ServiceClass:

@Service
public void serviceClass(long id) {
    Contact contact = contactRepository.findOne(id);
    Messages msg = new Messages();
    msg.setContact(contact);

    // do some work here

    Messages savedMessage = messagesRepository.save(msg);
    contact.addMessage(savedMessage);
    contactRepository.save(contact);
C. Smith
  • 172
  • 1
  • 4
  • 16
  • You are missing the default no-arg constructor on your JPA entities, so I would assume that's a typo. Can you also share what you are doing to retrieve the entities? – usually on a "service" class + "repository". – x80486 Dec 28 '18 at 21:32
  • @x80486 My repositories are just extended JpaRepositories. I posted the repositories and the service class. If you need any more information then let me know. – C. Smith Dec 28 '18 at 23:18
  • OK, what I answered is still current with all of that. Do you have an `@Transactional` at the service level? – x80486 Dec 29 '18 at 01:35

4 Answers4

7

Setting enable_lazy_load_no_trans=true in application.properties file solved in my case without changing fetch type from lazy to eager.

3

There are a few ways to initialize lazy associations in Hibernate, but bottom line, you should be able to solve that by (1) using a FETCH JOIN (if using the Criteria API or a custom, native or not, query), or (2) using a named/dynamic Entity Graph.

Try something like this in Contact:

@Entity
@Table(name = "contact")
@NamedEntityGraph(name = "graph.Contact.messages",
    attributeNodes = @NamedAttributeNode("messages"))
public class Contact { ... }

If you have access (and time) to use the EntityManager all of that is straightforward. I remember Spring Data JPA had some issue with something in particular with Entity Graphs, but I think for your case should work out of the box.

You can also try a FETCH JOIN like: SELECT c FROM Contact AS c JOIN FETCH c.messages m WHERE c.id = :id" – might need to tweak that, I just wrote it "on the fly".

NOTE: Additionally, since you are using Spring, make sure you have your "service" class(es) annotated with @Transactional since lazy-loading only works within the (same) transaction context.

Please, do not turn your FetchType.LAZY into FetchType.EAGER – unless you are going to lose your job ;)

At the same time, calling a method on the mapped relation is a hack and will take you to the so called N + 1 problem.

Community
  • 1
  • 1
x80486
  • 6,627
  • 5
  • 52
  • 111
3

You can lazy-load only within transaction context. Use @Transactional annotation on your service class:

@Service
@Transactional
public class Service {
public void serviceClass(long id) {
 Contact contact = contactRepository.findOne(id);
 Messages msg = new Messages();
 msg.setContact(contact);

 // do some work here

 Messages savedMessage = messagesRepository.save(msg);
 contact.addMessage(savedMessage);
 contactRepository.save(contact);
}
}
pkkoniec
  • 168
  • 6
2

If you added below line in your application.properties configuration file, then it should work

spring.jpa.open-in-view=false
IMParasharG
  • 1,869
  • 1
  • 15
  • 26