【问题标题】:LazyInitializationException with Spring Data JpaRepositories带有 Spring Data Jpa 存储库的 LazyInitializationException
【发布时间】:2016-03-26 19:43:05
【问题描述】:

我有以下实体:

用户:

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

新闻来源:

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

}

我的测试在第 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));
    }

如果我将我的测试注释为未引发 @Transactional 异常,但我不确定这是解决此问题的正确方法。

【问题讨论】:

  • 你试过在查询中使用 join fetch 吗?
  • 我不写任何查询 spring data 做这个工作。
  • 没关系,但您可以在存储库界面中使用@Query 注释编写自己的查询。

标签: java spring hibernate jpa spring-data


【解决方案1】:

ManyToMany 注解默认它的获取类型是惰性的

FetchType fetch() default LAZY;

在您的情况下,User 类中的 newsSources 将被延迟获取。

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

考虑以下几行,

User savedUser = usersRepository.findOne(EMAIL);
NewsSource newsSource = savedUser.getNewsSources().get(0);

事务在 usersRepository.findOne(EMAIL) 本身中开始和结束。现在“用户已保存用户”对象具有将延迟加载的新闻源。因此,当您调用 savedUser.getNewsSources() 时,它会尝试使用持久会话延迟加载。由于事务上下文已经关闭,因此没有活动的关联会话。

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

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

类似的问题: LazyInitializationException: failed to lazily initialize a collection of roles, could not initialize proxy - no Session

【讨论】:

  • 你的回答很有帮助。如果我定义 interface:public interface UserRepository extends JpaRepository { User findByUserName(String userName); ....} 并使用 userRepository.findByUserName("aa").getUserName() 在测试中可以,你能解释一下为什么吗?
  • 用户名字段不需要延迟加载,因为它在同一个表中。而作为一对多或多对多可以延迟加载,因为它涉及其他表和连接。这是你要求的吗?
  • 看看这是否有帮助.. stackoverflow.com/questions/33218762/… 我没有使用 getOne... 一个原因可能是检查记录的存在而不是获取详细信息。通常对 id 进行索引,但要获取必须从存储表信息的位置读取的详细信息.. 尝试按照这些思路思考.. 也可以查看此链接 stackoverflow.com/questions/1607532/…
  • "当您使用 spring data jpa 存储库时,事务注释应用于所有 jpa 存储库方法。这些方法调用的事务在调用方法时开始并在方法执行完成时结束” 因此,您是说,如果我有一个用 @Transactional 注释的 Service 类方法,并且该方法调用 Spring JPA 存储库,那么 JPA 存储库事务将被扩展到Service 类方法?因此,我是否应该Service 方法类中获得LazyIniitalizationException,因为我使用了@Transactional
猜你喜欢
  • 1970-01-01
  • 2017-03-16
  • 1970-01-01
  • 2014-05-22
  • 1970-01-01
  • 2018-08-06
  • 2016-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多