HibernateによるNaNの永続化の防止
-
05-07-2019 - |
質問
Progressデータベースに接続するJPAプロバイダーとしてHibernateを使用しています。 NaN値が永続化されると、多くの問題が発生します。特定の状況で行が読み取れないようにします。 NaN(およびおそらく+および-無限大)を別の値に変換するために標準のdouble型の永続性にフックする方法はありますか? NaNまたは無限大の情報が失われても関係ありません。読みやすい行が必要です!
次のようなことができるとわかっています:
@Column(name = "doubleColumn")
public double getDoubleColumn() {
return PersistedDouble.convert(doubleColumn);
}
しかし、データベースにマップされたdoubleの場合、手動でメンテナンスを追加する必要があるため、メンテナンスが心配です。
解決
この最初の印象は、Hibernateが double
を永続化する型を探すことです。したがって、 DoubleType
。ただし、"を定義した後、すべての Double
タイプに @ org.hibernate.annotations.type(type =" myDouble")
の注釈を付ける必要があります。 myDouble" package-infoで @ org.hibernate.annotations.TypeDef
を使用-メンテナンスのためにこれらすべてを回避したいと思います(Hibernateの中心部に行く必要があることを超えて)。
他のヒント
休止状態自体を変更できます。必要なのは、クラスDoubleTypeを変更することだけです。
もちろん、休止状態の進化に合わせてそのパッチを維持する必要がありますが、単一のかなり安定したクラスであると考えると、ドメインモデルのすべてのdoubleにUserTypeを指定するよりも簡単です。
この議論の後、私はその休止状態を感じていますNaNを他の何かに変換する方法を提供していません。 NaN値は、Beanメンバー変数に書き込まれる前であっても(ガード/変換コードをセッターに追加する前に)、事前に防止する必要があると思います。
編集
最高の不快な解決策は、保護コードを使用することであり、さらに悪いことに、値が数値であるかどうかを示すために、テーブルに追加の列を追加することです。確かにクエリと挿入操作を複雑にするものは何ですか。ただし、データベースにNaNが必要であり、jdbcドライバー/データベースと適切に動作するように戦うことはできません(そして、NUMBERフィールドの有効な入力としてNaNを受け入れます)。
最終的にUserTypeソリューションを使用しましたが、単体テストでメンテナンスの問題を解決しました。型クラスは次のとおりです。
public class ParsedDoubleType extends DoubleType {
private static final long serialVersionUID = 1L;
@Override
public void set(PreparedStatement st, Object value, int index) throws SQLException {
Double doubleValue = (Double) value;
if (doubleValue.isInfinite() || doubleValue.isNaN()) {
Logger.getLogger(ParsedDoubleType.class).warn("Attempted to send a NaN or infinity value to the database "
+ "- this is not supported.\nStatement=" + st + " valueIndex=" + index);
doubleValue = Double.valueOf(0);
}
super.set(st, doubleValue, index);
}
}
単体テストは大まかに(簡潔にするために一部の詳細を削除しています):
Ejb3Configuration hibernateConfig = new Ejb3Configuration().configure("InMemoryDatabasePersistenceUnit", null);
for (Iterator<?> iterator = hibernateConfig.getClassMappings(); iterator.hasNext();) {
PersistentClass clazz = (PersistentClass) iterator.next();
Iterator<?> propertyIterator = clazz.getPropertyIterator();
while (propertyIterator.hasNext()) {
if (property.getType().equals(Hibernate.DOUBLE)) {
Assert.fail("Raw double type found. Annotate with @Type(type = \"package.ParsedDoubleType\")\n"
+ "Class " + clazz + " property " + property);
}
}
}
まったく同じ問題があり、これらのソリューションのガイダンスに従って、DoubleTypeを拡張するカスタムタイプクラスも準備しました。そのクラス内で、set関数でNaN値をnullに変換し、get関数ではその逆をnullに変換しました。これは、データベース列ではnullで問題ないためです。また、NaN可能列のマッピングをカスタムタイプクラスに変更しました。このソリューションは、hibernate 3.3.2で完全に機能しました。
残念ながら、Hibernateを3.6.10にアップグレードした後、動作しなくなりました。再び機能させるために、カスタムタイプをDoubleTypeの拡張から置き換えてUserTypeを実装しました。
重要なデータ型関数の実装は次のようになります。
private int[] types = { Types.DOUBLE };
public int[] sqlTypes()
{
return types;
}
@SuppressWarnings("rawtypes")
public Class returnedClass()
{
return Double.class;
}
また、getおよびset関数は次のとおりです。
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
{
Double value = rs.getDouble(names[0]);
if (rs.wasNull())
return Double.NaN;
else
return value;
}
public void nullSafeSet(PreparedStatement ps, Object value, int index) throws HibernateException, SQLException
{
Double dbl = (Double) value;
if ((dbl == null) || (Double.isNaN(dbl)))
ps.setNull(index, Types.DOUBLE);
else
ps.setDouble(index, dbl);
}
申し訳ありませんが、例と質問から判断すると、実際にはJavaの永続性を理解するのに問題があります。データベースエンティティは、ゲッターとセッターを介して自己管理されます。これらは、必要な検証を実行できます。実際に属性を設定せずに属性を設定すると、オブジェクト指向開発と永続性(特に管理対象エンティティ)のコア概念が失われます。 これらのような問題を抱えていることは、基本的な設計欠陥の確かな兆候です...ここにいくつかのアドバイスを与えるだけで、あなたはプロジェクトを再設計する必要があると思います-そしてそれが解決策です:
@Column(name="doubleColumn"}
private Double doubleColumn = Double.NaN //yes, this is intentional. Verily.
public void setDouble(Double d)
{
if(d.isNan || d.isInfinite()
{
//do something nice here
}
else
this.doubleColumn = d;
}
public Double getDouble()
{
return !this.doubleColumn.isNaN() && !this.doubleColumn.isInfinite() ? this.doubleColumn : new Double();
}
....それは簡単です。