【问题标题】:Hibernate does not load one to many relationships sets even with eager fetch即使使用 Eager fetch,Hibernate 也不会加载一对多的关系集
【发布时间】:2014-03-27 12:46:09
【问题描述】:

我有以下休眠映射。

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product")
private Set<ProductLicense> productLicenses = new HashSet<ProductLicense>(0);


@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

但是当我调用 product.getProductLicences() 时,我总是得到一个空 Set,即使在事务方法中也是如此。

sessionFactory.getCurrentSession().get(Product.class, productId))
            .getProductLicenses()

以下是ProductDaoImpl类

@Repository
public class ProductDaoImpl implments ProductDao{

@Autowired
private SessionFactory sessionFactory;

@Override
public Product getProduct(Integer productId)
{

    Product result = (Product) sessionFactory.getCurrentSession().get(Product.class, productId);
    Hibernate.initialize(result.getProductLicenses());
            //sessionFactory.getCurrentSession().refresh(result);
    return result;

}
   .. other methods

}

此外,如果我调用 Hibernate.initialize(product);不执行表之间的任何连接。 指令 sessionFactory.getCurrentSession().get(Product.class, productId);不会对数据库产生任何查询(我的属性 show_sql 等于 true)。 但是如果我取消注释 sessionFactory.getCurrentSession().refresh(result);我可以看到 sql 和 set 已加载,我不明白为什么在其他情况下未加载。 但我无法理解我的映射出了什么问题。

产品类别是:

@Entity
@Table(name = "product")
public class Product {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "product_id", unique = true)
private Integer productId;

@NotNull
@Size(max = 200)
@Column(name = "name")
private String name;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product")
private Set<ProductLicense> productLicenses = new HashSet<ProductLicense>(0);


    public Set<ProductLicense> getProductLicenses()
{
    return productLicenses;
}

public void setProductLicenses(Set<ProductLicense> productLicenses)
{
    this.productLicenses = productLicenses;
}

//getters and setters
..

}

ProductLicense 类:

@Entity
@Table(name = "product_license")
public class ProductLicense {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "product_license_id", unique = true)
private Integer productLicenseId;

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
    private Product product;

@NotNull
@Column(name = "key")
private String key;

    // getters and setters ...

}

最后我的配置如下:

 @Configuration
 @EnableWebMvc
 @EnableTransactionManagement
 @ComponentScan("com.company.package")
 @PropertySource("classpath:application.properties")

public class WebAppConfig {

private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";

private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";

@Resource
private Environment env;

@Bean
public DataSource dataSource()
{
    DriverManagerDataSource dataSource = new DriverManagerDataSource();

    dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
    dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
    dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
    dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));

    return dataSource;
}

private Properties hibProperties()
{
    Properties properties = new Properties();
    properties.put(PROPERTY_NAME_HIBERNATE_DIALECT,
            env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
    properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL,
            env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
    properties.put("hibernate.cache.provider_class", "org.hibernate.cache.NoCacheProvider");
    properties.put("hibernate.cache.use_second_level_cache", "false");
    return properties;
}

@Bean
public LocalSessionFactoryBean sessionFactory()
{
    LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
    lsfb.setDataSource(this.dataSource());
    lsfb.setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
    lsfb.setHibernateProperties(this.hibProperties());
    return lsfb;
}

@Bean
public HibernateTransactionManager transactionManager()
{
    return new HibernateTransactionManager(this.sessionFactory().getObject());
}
 }

我的测试:

@Before
@Transactional
public void setUp() throws ParseException
{

    product1 = new Product();
    ..

    productService.addProduct(product1);

    product2 = new Product();
    ..

    product2 = productService.addProduct(product2);

    productService.addProductLicense(product2.getProductId(), licenceKey1Product2);
    productService.addProductLicense(product2.getProductId(), licenceKey2Product2);



}

  @Test
@Transactional
public void addProductLicenseTest()
{
    String licenseKey[] = { "thisisthekey", "thisisthekey2" };

    productService.addProductLicense(product1.getProductId(), licenseKey[0]);

            //sessionFactory.getCurrentSession().flush();
    //sessionFactory.getCurrentSession().clear();

    Product product1saved = productService.getProduct(product1.getProductId());

    assertEquals(1, product1saved.getProductLicenses().size());
    assertEquals(licenseKey[0], product1saved.getProductLicenses().iterator().next().getKey());

    productService.addProductLicense(product1.getProductId(), licenseKey[1]);

    product1saved = productService.getProduct(product1saved.getProductId());

    assertEquals(2, product1saved.getProductLicenses().size());

 }

【问题讨论】:

  • 你如何测试它?
  • 在 Junit 上标有“@Test”和“@Transactional”的测试中,我调用 Product product1saved = productService.getProduct(product1.getProductId());
  • 通过编辑您的问题向我们展示此测试的完整代码
  • @JBNizet 我注意到添加 sessionFactory.getCurrentSession().flush(); sessionFactory.getCurrentSession().clear();在 productService.getProduct(product1.getProductId()) 之前;在单元测试中帮助了我很多,但现在我想知道为什么 hibernate 对 junit 有这种奇怪的行为?
  • 我可能能够解释为什么你的代码表现得像你看到的那样,但不是没有看到代码。贴出测试代码。

标签: java spring hibernate orm hibernate-mapping


【解决方案1】:

这就是(很可能)发生的事情。

您的整个测试方法都是事务性的。因此,服务的调用与测试本身的代码发生在同一事务中。

在测试中,您在没有任何许可的情况下创建和持久化产品。这会将 Product 实例存储在与事务相关的会话缓存中。此产品的许可证列表为空。

然后你调用一个服务方法,仍然在同一个事务中,它创建一个许可证,其产品是你之前创建的产品。我的猜测是该服务的代码如下所示:

Product product = (Product) session.get(Product.class, productId);
ProductLicense license = new ProductLicense();
license.setProduct(product);
session.persist(license);

在上面的代码中,产品是从会话缓存中获取的,然后许可证与找到的产品一起持久化。请注意,代码设置了许可的产品,但没有将许可添加到产品中,这就是问题所在。

然后您通过 ID 获取产品,仍然在同一个交易中。因此,Hibernate 直接从缓存中检索产品。并且在缓存中,由于您省略了将许可证添加到产品中,因此许可证列表仍然是空的。

因此,简而言之,您有责任维护双向关联的双方。如果只初始化所有者端,Hibernate 将保持关联,如果从数据库加载产品,它将正确初始化列表。但是,当产品在缓存中时,它会在您将其存储在缓存中时保持不变。这就是为什么您会看到一个空列表的原因,除非您明确清除会话并从数据库中重新加载状态。

【讨论】:

  • 是的,我同意你的看法。但这是在 Hibernate 缓存的第一级发生的事情。因此我想我只有在单元测试中才会有这种奇怪的行为,因为它们是在同一个事务下执行的。
  • 如果您的生产代码也添加了许可证,并且还希望许可证在产品列表中,那么您在生产代码中也会遇到同样的问题。这取决于你。
  • 对不起,愚蠢的观察,但如果在我的代码之后 session.update(product);我添加 session.flush();它会帮助我吗?我怀疑hibernate无法为我更新双方的一对多关系。因此,这意味着在您上面编写的代码之后,我必须添加一个 product.getLicences().add(license); session.update(产品);有点奇怪,我不需要只执行 session.update(license); ?
  • 不,冲洗没有帮助。 flush 只执行挂起的插入/删除/更新查询。 Hibernat 不会为您更新双方。这样做是你的责任。
  • 我有同样的问题,刷新没有帮助,如何在 JPA 上轻松重新加载?我在 EAGER 中使用单向
猜你喜欢
  • 2016-10-02
  • 2013-11-19
  • 2014-03-18
  • 1970-01-01
  • 2019-12-30
  • 2012-04-15
  • 2016-07-16
  • 2016-08-17
  • 1970-01-01
相关资源
最近更新 更多