I have following entities:
User:
@Entity
public class User {
@Id
@Column(nullable = false)
private String email = "";
@Column(nullable = false)
private String nickname = "";
@Column(nullable = false)
private String password = "";
@ManyToMany(cascade = CascadeType.ALL)
private List<NewsSource> newsSources;
// getters and setters
}
News Source:
@Entity
public class NewsSource {
@Id
@Column(nullable = false)
private URL url;
private LocalDateTime updateTime;
@OneToMany(cascade = CascadeType.ALL)
private List<News> newses;
@ManyToMany(cascade = CascadeType.ALL)
private List<User> users;
}
UsersRepository and NewsSourcesRepository are simple JpaRepositories from Spring Data JPA. Their configuration is as follow:
@Configuration
@EnableTransactionManagement
@PropertySource("classpath:database_config.properties")
@EnableJpaRepositories(basePackages = {"news.repositories" })
public class RepositoriesConfiguration {
@Bean(destroyMethod = "close")
DataSource dataSource(Environment env) {
HikariConfig dataSourceConfig = new HikariConfig();
dataSourceConfig.setDriverClassName(env.getRequiredProperty("db.driver"));
dataSourceConfig.setJdbcUrl(env.getRequiredProperty("db.url"));
dataSourceConfig.setUsername(env.getRequiredProperty("db.username"));
dataSourceConfig.setPassword(env.getRequiredProperty("db.password"));
return new HikariDataSource(dataSourceConfig);
}
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan("pl.mielecmichal.news.entities");
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect"));
jpaProperties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.ejb.naming_strategy", env.getRequiredProperty("hibernate.ejb.naming_strategy"));
jpaProperties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql"));
jpaProperties.put("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql"));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
@Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
My test throws an LazyInitializationException on line 15. The message is:
failed to lazily initialize a collection of role: news.entities.users.User.newsSources, could not initialize proxy - no Session
@Test
public void cascadeRelationsShouldBeRetrieved() throws MalformedURLException {
NewsSource source = new NewsSource();
source.setUrl(new URL(SOME_URL));
newsSourcesRepository.save(source);
newsSourcesRepository.flush();
User user = new User();
user.setEmail(EMAIL);
List<NewsSource> sources = new ArrayList<>();
sources.add(source);
user.setNewsSources(sources);
usersRepository.save(user);
usersRepository.flush();
User savedUser = usersRepository.findOne(EMAIL);
NewsSource newsSource = savedUser.getNewsSources().get(0);
assertThat("News source should be saved", newsSource.getUrl(), is(SOME_URL));
NewsSource savedSource = newsSourcesRepository.findOne(newsSource.getUrl());
assertThat("New user should be saved in M2M relation", savedSource.getUsers(), Matchers.contains(user));
}
If I annotate my test as @Transactional exception is not thrown, but I'm not sure is this a proper way to solve this.
You cant actually use both of them in the same application. For backwards compatibility. We are expanding our application and want to start using Spring Data JPA, but still keep the old hibernate implementation. Its better you develop your new application as a separate microservice and use spring data jpa ..
The right way to fix a LazyInitializationException is to fetch all required associations within your service layer. The best option for that is to load the entity with all required associations in one query.
In this part we will integrate Spring Data Jpa in Spring MVC and modify our previous Hibernate based service classes and re-write them with Spring Data JPA. To get existing source code, click here. Firstly, we need to add the dependency of Spring Data JPA in our existing project.
Crud Repository doesn't provide methods for implementing pagination and sorting. JpaRepository ties your repositories to the JPA persistence technology so it should be avoided. We should use CrudRepository or PagingAndSortingRepository depending on whether you need sorting and paging or not.
ManyToMany annotation by default it's fetch type is lazy
FetchType fetch() default LAZY;
In your case, newsSources in User class will be lazily fetched.
Let's assume that you are directly not using Transactional Annotation for the sake of simplicity. For any database operation a transaction is required. As you are using spring data jpa repositories, Transactional annotation is applied to all jpa repository methods. The transactions for these method calls begin when the method is called and ends when the method execution is finished. The last statement holds unless there is an outer transaction of the same database.
Consider following lines,
User savedUser = usersRepository.findOne(EMAIL);
NewsSource newsSource = savedUser.getNewsSources().get(0);
Transaction starts and ends in the usersRepository.findOne(EMAIL) itself. Now "User savedUser" object has newsSources which will be loaded lazily. So when you call savedUser.getNewsSources(), it tries to load lazily using the persistence session. As the transaction context was already closed there is no active associated session.
Now if you add Transactional annotation to the method annotated with Test annotation, transaction begins here itself and now when savedUser.getNewsSources() is called, this same transaction will be used. And now when you do savedUser.getNewsSources(), there is an associated session with it and therefore will work properly.
There is nothing wrong in putting transactional annotation on test method. As the mapping is lazy, somewhere you have to put transactional annotation. Here as you are directly calling jpa repository method within test method and performing operation on lazy reference object, you definitely have to use transactional annotation on the annotated test method.
Similar question: LazyInitializationException: failed to lazily initialize a collection of roles, could not initialize proxy - no Session
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With