Hibernate Synchronizer - pozor na UserType
23. April 2007
Při čtení článku Schéma databáze – používáme hibernate mě napadlo, abych napsal o jednom problému, který souvisí s generováním POJOs z mapovacích souborů pomocí Hibernate Synchronizeru v případě používání vlastních typů (UserTypes).
Hibernate umožňuje psaní vlastních typů – takzvaných UserTypes.Mohou být buď jednoduché (skalární – s jednou vlastností) – typ UserType nebo kompozitní – CompositeUserType (více vlastností).
Ukažme si na příkladu použití kompozitního UserTypu:
package cz.vavru.sample.hibernate;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Currency;
import net.sf.hibernate.CompositeUserType;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.engine.SessionImplementor;
import net.sf.hibernate.type.Type;
import cz.vavru.sample.hibernate.MonetaryAmount;
public class MonetaryAmountType implements CompositeUserType {
public Class returnedClass() {
return MonetaryAmount.class;
}
...
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session,
Object owner) throws SQLException {
double value = resultSet.getDouble(names[0]);
if (resultSet.wasNull())
return null;
Currency currency = Currency.getInstance(resultSet.getString(names[1]));
return new MonetaryAmount(new Double(value), currency);
}
public void nullSafeSet(PreparedStatement statement, Object value, int index,
SessionImplementor session) throws SQLException {
if (value == null) {
statement.setNull(index, Hibernate.BIG_DECIMAL.sqlType());
statement.setNull(index + 1, Hibernate.CURRENCY.sqlType());
} else {
MonetaryAmount amount = (MonetaryAmount) value;
String currencyCode = amount.getCurrency().getCurrencyCode();
statement.setDouble(index, amount.getValue().doubleValue());
statement.setString(index + 1, currencyCode);
}
}
...
}
Příslušný doménový objekt MonetaryAmount
vypadá
následovně:
package cz.vavru.sample.hibernate;
import java.io.Serializable;
import java.util.Currency;
import java.util.Iterator;
public class MonetaryAmount implements Serializable {
private static final long serialVersionUID = Long.MIN_VALUE * 34;
private Double value;
private final Currency currency;
public MonetaryAmount(Currency currency) {
value = new Double(0d);
this.currency = currency;
}
public MonetaryAmount(BigDecimal bigDecimal, Currency currency) {
this.value = new Double(bigDecimal.doubleValue());
this.currency = currency;
}
public MonetaryAmount(Double value, Currency currency) {
this.value = value;
this.currency = currency;
}
public Currency getCurrency() {
return currency;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof MonetaryAmount))
return false;
final MonetaryAmount monetaryAmount = (MonetaryAmount) o;
if (!currency.equals(monetaryAmount.currency))
return false;
if (value != monetaryAmount.value)
return false;
return true;
}
public int hashCode() {
int result;
result = 29 * value.intValue() + currency.hashCode();
return result;
}
public String toString() {
return "Value: '" + getValue() + "', " + "Currency: '" + getCurrency() + "'";
}
}
A ukázka využití v mapovacím souboru jiného doménového
objektu, který MonetaryAmount
používá:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
<hibernate-mapping>
<class name="cz.vavru.sample.hibernate.MyEntity"
table="ccg_pg_yearly_settings" >
<id name="id" type="java.lang.Integer" column="id">
<generator class="increment" />
</id>
...
<property name="budget" not-null="false"
type="cz.vavru.sample.hibernate.MonetaryAmountType">
<column name="budget_amount" />
<column name="budget_currency" />
</property>
</class>
</hibernate-mapping>
Kód, který Hibernate Synchronizer vygeneruje nesprávně používá pro
vlastnost budget
typ MonetaryAmountUserType
místo správného MonetaryAmount
.
package cz.vavru.sample.hibernate;
public class MyEntity implements Serializable {
...
private cz.vavru.sample.hibernate.MonetaryAmountType budget;
public cz.vavru.sample.hibernate.MonetaryAmountType getBudget () {
return budget;
}
public void setBudget (cz.vavru.sample.hibernate.MonetaryAmountType budget) {
this.budget = budget;
}
...
}
Správný kód je tedy:
package cz.vavru.sample.hibernate;
public class MyEntity implements Serializable {
...
private cz.vavru.sample.hibernate.MonetaryAmount budget;
public cz.vavru.sample.hibernate.MonetaryAmount getBudget () {
return budget;
}
public void setBudget (cz.vavru.sample.hibernate.MonetaryAmount budget) {
this.budget = budget;
}
...
}
Přiznám se, že mi odhlaní tohoto problému zabralo asi hodinu.
Odkazy
Článek patří do kategorie: Java
2 Komentářů Přidat komentář
1. Ladislav Thon | 24. April 2007 v 7.51
K tomu jednu otázku, zcela mimo zaměření textu: jistě je vhodné ukládat peněžní obnos jako
double
? Víme přece, jak je to s jeho přesností už u základních početních úkonů typu násobení či dělení…2. Vlasta | 24. April 2007 v 8.22
[1]Správné je použit typ BigDecimal, ale zde jsem si jej dovolil nahradit Doublem.
Přidat komentář
Povolené HTML značky:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>
Odkazovat na tento článek | Přihlásit se k odběru těchto komentářů přes RSS Feed