使用Spring Data JpaRepositories的LazyInitializationException

Mic*_*lec 5 java spring hibernate jpa spring-data

我有以下实体:

用户:

@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
}
Run Code Online (Sandbox Code Playgroud)

新闻来源:

@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;
}
Run Code Online (Sandbox Code Playgroud)

UsersRepository和NewsSourcesRepository是Spring Data JPA中的简单JpaRepositories.他们的配置如下:

@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;
    }

}
Run Code Online (Sandbox Code Playgroud)

我的测试在第15行抛出了一个LazyInitializationException.消息是:

未能懒惰地初始化角色集合:news.entities.users.User.newsSources,无法初始化代理 - 没有会话

    @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));
    }
Run Code Online (Sandbox Code Playgroud)

如果我注释我的测试因为没有抛出@Transactional异常,但我不确定这是解决这个问题的正确方法.

pre*_*mar 10

默认情况下,ManyToMany注释的获取类型是惰性的

FetchType fetch() default LAZY;
Run Code Online (Sandbox Code Playgroud)

在您的情况下,将会延迟提取User类中的newsSources.

假设您为了简单起见而直接不使用Transactional Annotation.对于任何数据库操作,都需要事务.当您使用spring数据jpa存储库时,Transactional注释将应用于所有jpa存储库方法.这些方法调用的事务在调用方法时开始,在方法执行完成时结束.除非存在同一数据库的外部事务,否则最后一个语句成立.

考虑以下几行,

User savedUser = usersRepository.findOne(EMAIL);
NewsSource newsSource = savedUser.getNewsSources().get(0);
Run Code Online (Sandbox Code Playgroud)

事务在usersRepository.findOne(EMAIL)本身中开始和结束.现在"User savedUser"对象具有将被懒惰加载的newsSources.因此,当您调用savedUser.getNewsSources()时,它会尝试使用持久性会话延迟加载.由于事务上下文已经关闭,因此没有活动的关联会话.

现在,如果将Transactional注释添加到使用Test annotation注释的方法,则事务从此处开始,现在当调用savedUser.getNewsSources()时,将使用此相同的事务.现在当你执行savedUser.getNewsSources()时,会有一个关联的会话,因此可以正常工作.

将事务注释放在测试方法上没有错.由于映射是惰性的,因此您必须在某处放置事务注释.在这里,当您在测试方法中直接调用jpa存储库方法并对延迟引用对象执行操作时,您必须在带注释的测试方法上使用事务注释.

类似的问题: LazyInitializationException:懒得初始化一组角色,无法初始化代理 - 没有Session


归档时间:

查看次数:

4019 次

最近记录:

10 年,5 月 前