Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate does not load one to many relationships sets even with eager fetch

I have the following hibernate mapping.

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

But when I call product.getProductLicences() I always get an empty Set, even inside a transactional method.

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

The following is the ProductDaoImpl class

@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

}

And in addition if I call Hibernate.initialize(product); does not perform any join between the tables. The instruction sessionFactory.getCurrentSession().get(Product.class, productId); does not produce any query to the database (I have the property show_sql equals to true). But if I uncomment sessionFactory.getCurrentSession().refresh(result); I can see the sql and the set is loaded, and I cannot understand why in the other case is not loaded. But I cannot understand what is wrong in my mapping.

The product class is:

@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 class:

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

}

And finally my configuration is the following:

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

My Test:

@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());

 }
like image 210
cloudy_weather Avatar asked Feb 25 '14 10:02

cloudy_weather


1 Answers

Here's what (most probably) happens.

Your whole test method is transactional. The calls of the service thus happen in the same transaction as the code of the test itself.

In the test, you're creating and persisting a Product, without any license. This stores a Product instance in the session cache, tied to the transaction. This product has an empty list of licenses.

Then you call a service method, still inside the same transaction, that creates a license whose product is the produc you created previously. My guess is that the code of this service looks like the following:

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

In the code above, the product is obtained from the session cache, and the license is then persisted with the found product. Note that the code sets the product of the license, but it doesn't add the license to the product, which is the problem.

Then you're getting the product by ID, still in the same transaction. Hibernate thus retrieves the product directly from the cache. And in the cache, since you omitted to add the license to the product, the license list is still empty.

So, in short, it's your responsibility to maintain both sides of the bidirectional association. If you only initialize the owner side, Hibernate will persist the association, and will initialize the list correctly if it loads the product from the database. But while the product is in the cache, it stays as you stored it in the cache. This is why you see an empty list, unless you explicitely clear the session and thus reload the state from the database.

like image 87
JB Nizet Avatar answered Nov 16 '22 04:11

JB Nizet