I have simple Money object that is mapped as component (@Embeddable) inside simple Account object.
The problem I'm experiencing is that Hibernate fails to properly track changes made to Money fields, i.e. something like:
account.getAmount().setAmount(new BigDecimal(3));
[account.getAmount() returns Money component which in turn has setAmount() method]
What is even worse -- it doesn't work consistently. In the example code below it appears to "notice" the first update but then ignores all subsequent updates (within the same session). Perhaps it is actually some kind of bug with data flushing (notice the update being executed even though session is not being finished).
I searched documentation for information whether Hibernate is 'supposed' to track changes to objects mapped as components or whether it treats them as 'atomic' (or 'primitive') objects and tracks only reference change -- but came up empty.
More explanations below the code sample.
My specific code uses Hibernate annotations and EJB3-style access, but I doubt the problem is specific to those, so posting in general forum.
Hibernate version:
Hibernate-Version: 3.2.1.ga
Mapping documents:
using annotations
Configuration:
Code:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="manager1" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver"/>
<property name="hibernate.connection.username" value="..."/>
<property name="hibernate.connection.password" value="..."/>
<property name="hibernate.connection.url" value="jdbc:postgresql:database"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<!-- Naming strategy to allow multiple instances of embeddable object -->
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultComponentSafeNamingStrategy" />
<!-- DB creation -->
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<!-- Debug section -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
</properties>
</persistence-unit>
</persistence>
Code between sessionFactory.openSession() and session.close():Classes that can be used to reproduce the problem:
Money.javaCode:
package hibbug;
import java.math.BigDecimal;
import javax.persistence.Embeddable;
@Embeddable
public class Money
{
private BigDecimal amount;
private String currency;
public String getCurrency()
{
return currency;
}
public void setCurrency(String newCurrency)
{
currency = newCurrency;
}
public BigDecimal getAmount()
{
return amount;
}
public void setAmount(BigDecimal newAmount)
{
amount = newAmount;
}
}
Account.javaCode:
package hibbug;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Account
{
private String name;
private Money amount;
private Long id;
@Id @GeneratedValue
public Long getId()
{
return id;
}
public void setId(Long newId)
{
id = newId;
}
public Money getAmount()
{
return amount;
}
public void setAmount(Money newAmount)
{
amount = newAmount;
}
public String getName()
{
return name;
}
public void setName(String newName)
{
name = newName;
}
}
HibbugMain.javaCode:
package hibbug;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class HibbugMain
{
public static void main(String[] args)
{
EntityManagerFactory emf;
Map<String, String> configOverrides = new HashMap<String, String>();
configOverrides.put("hibernate.hbm2ddl.auto", "create-drop");
emf = Persistence.createEntityManagerFactory("manager1", configOverrides);
EntityManager em = emf.createEntityManager();
// Create object for manipulation.
EntityTransaction tx = em.getTransaction();
tx.begin();
Account acc = new Account();
acc.setName("Testing account");
Money money = new Money();
money.setCurrency("USD");
money.setAmount( new BigDecimal(0) );
acc.setAmount(money);
em.persist(acc);
tx.commit();
em.close();
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
// Increase value in loop.
for( int i = 1; i <= 4; i++ )
{
acc = (Account)
em.createQuery( "from Account" ).getSingleResult();
Money mn = acc.getAmount();
System.out.println("Current value: " + mn.getAmount());
mn.setAmount( new BigDecimal(i) );
System.out.println("Set value to: " + i);
// Money mn = new Money();
// System.out.println("Current value: " + mn.getAmount());
// mn.setAmount( new BigDecimal(i) );
// acc.setAmount(mn);
// System.out.println("Set value to: " + i);
}
tx.commit();
em.close();
}
}
Name and version of the database you are using:PostgreSQL 8.2
The generated SQL (show_sql=true):Code:
log4j:WARN No appenders could be found for logger (org.hibernate.ejb.Version).
log4j:WARN Please initialize the log4j system properly.
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
/* insert hibbug.Account
*/ insert
into
Account
(name, amount_amount, amount_currency, id)
values
(?, ?, ?, ?)
Hibernate:
/*
from
Account */ select
account0_.id as id0_,
account0_.name as name0_,
account0_.amount_amount as amount3_0_,
account0_.amount_currency as amount4_0_
from
Account account0_
Current value: 0.00
Set value to: 1
Hibernate:
/* update
hibbug.Account */ update
Account
set
name=?,
amount_amount=?,
amount_currency=?
where
id=?
Hibernate:
/*
from
Account */ select
account0_.id as id0_,
account0_.name as name0_,
account0_.amount_amount as amount3_0_,
account0_.amount_currency as amount4_0_
from
Account account0_
Current value: 1
Set value to: 2
Hibernate:
/*
from
Account */ select
account0_.id as id0_,
account0_.name as name0_,
account0_.amount_amount as amount3_0_,
account0_.amount_currency as amount4_0_
from
Account account0_
Current value: 2
Set value to: 3
Hibernate:
/*
from
Account */ select
account0_.id as id0_,
account0_.name as name0_,
account0_.amount_amount as amount3_0_,
account0_.amount_currency as amount4_0_
from
Account account0_
Current value: 3
Set value to: 4
As you can see from the log, Hibernate executes only single UPDATE statement after the first change. All the subsequent changes are ignored -- this is also confirmed by examining resulting value in the database.
I think Hibernate is executing update before executing query for the second time -- probably to ensure query consistency. But I have no idea why it does it only once and ignores subsequent changes.
Note that the code that is commented out performs correctly -- you get 4 updates if you use it and the resulting value is 4 (as expected).