状況:
Java.util.Date型の変数を持つ永続可能なクラスがあります。
import Java.util.Date;
@Entity
@Table(name = "prd_period")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Period extends ManagedEntity implements Interval {
@Column(name = "startdate_", nullable = false)
private Date startDate;
}
DBの対応するテーブル:
CREATE TABLE 'prd_period' (
'id_' bigint(20) NOT NULL AUTO_INCREMENT,
...
'startdate_' datetime NOT NULL
)
次に、PeriodオブジェクトをDBに保存します。
Period p = new Period();
Date d = new Date();
p.setStartDate();
myDao.save(p);
その後、DBからオブジェクトを抽出しようとすると、タイムスタンプタイプの変数startDateで返されます-そして、equals(...)を使用しようとしているすべての場所がfalseを返します。
Question:HibernateがTimestampではなくJava.util.Date型のオブジェクトとして日付を返すよう強制する手段はありますか?たとえば、Java.util.Date型の既存の変数を明示的に変更せずに、機能する必要がありますか?
注:
注釈が使用されているか、セッターが変更されている明示的なソリューションをいくつか見つけました-しかし、日付変数を持つ多くのクラスがあります-したがって、いくつかの集中ソリューションが必要であり、以下に説明するすべては十分ではありません:
アノテーション@Type:を使用-Java.sql.Dateが返されます
@Column
@Type(type="date")
private Date startDate;
アノテーション@Temporal(TemporalType.DATE)の使用-Java.sql.Dateが返されます
@Temporal(TemporalType.DATE)
@Column(name=”CREATION_DATE”)
private Date startDate;
セッター(ディープコピー)を変更することにより-Java.util.Dateが返されます
public void setStartDate(Date startDate) {
if (startDate != null) {
this.startDate = new Date(startDate.getTime());
} else {
this.startDate = null;
}
}
自分のタイプの作成により:-Java.util.Dateが返されます
詳細はこちら: http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
だから、私はこの問題にしばらく時間を費やし、解決策を見つけました。それはきれいなものではありませんが、少なくとも出発点-誰かがいくつかの有用なコメントでこれを補うかもしれません。
プロセスで見つけたマッピングに関する情報:
Hibernate型からプロパティ型への基本的なマッピングを含むクラスはorg.hibernate.type.TypeFactoryです。このマッピングはすべて、変更不可能なマップに保存されます
private static final Map BASIC_TYPES;
...
basics.put( Java.util.Date.class.getName(), Hibernate.TIMESTAMP );
...
BASIC_TYPES = Collections.unmodifiableMap( basics );
Hibernateタイプorg.hibernate.type.TimestampTypeに関連付けられたJava.util.Dateタイプを見るとわかるように
次の興味深い瞬間-Hibernate org.hibernate.cfg.Configurationの作成-マップされたクラスに関するすべての情報を含むオブジェクト。このクラスとそのプロパティは、次のように抽出できます。
Iterator clsMappings = cfg.getClassMappings();
while(clsMappings.hasNext()){
PersistentClass mapping = (PersistentClass) clsMappings.next();
handleProperties(mapping.getPropertyIterator(), map);
}
プロパティの大部分はorg.hibernate.mapping.SimpleValueタイプのオブジェクトです。関心のあるポイントは、メソッドSimpleValue.getType()です。このメソッドでは、DBの操作中にプロパティ値を前後に変換するために使用される型が定義されます
Type result = TypeFactory.heuristicType(typeName, typeParameters);
この時点で、SimpleValueオブジェクトをJava.util.Date型のプロパティに変換するBASIC_TYPESを変更することができないことを理解しています。これは、変換する正確な型を知ることができるカスタムオブジェクトに置き換えます。
解決策:
HibernatePersistenceクラスを拡張し、そのメソッドcreateContainerEntityManagerFactoryをオーバーライドして、カスタムコンテナーエンティティマネージャーファクトリを作成します。
public class HibernatePersistenceExtensions extends HibernatePersistence {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
if ("true".equals(map.get("hibernate.use.custom.entity.manager.factory"))) {
return CustomeEntityManagerFactoryFactory.createCustomEntityManagerFactory(info, map);
} else {
return super.createContainerEntityManagerFactory(info, map);
}
}
}
Hibernate構成オブジェクトを作成し、Java.util.Dateプロパティの値オブジェクトを変更してから、カスタムエンティティマネージャーファクトリを作成します。
public class ReattachingEntityManagerFactoryFactory {
@SuppressWarnings("rawtypes")
public static EntityManagerFactory createContainerEntityManagerFactory(
PersistenceUnitInfo info, Map map) {
Ejb3Configuration cfg = new Ejb3Configuration();
Ejb3Configuration configured = cfg.configure( info, map );
handleClassMappings(cfg, map);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
@SuppressWarnings("rawtypes")
private static void handleClassMappings(Ejb3Configuration cfg, Map map) {
Iterator clsMappings = cfg.getClassMappings();
while(clsMappings.hasNext()){
PersistentClass mapping = (PersistentClass) clsMappings.next();
handleProperties(mapping.getPropertyIterator(), map);
}
}
private static void handleProperties(Iterator props, Map map) {
while(props.hasNext()){
Property prop = (Property) props.next();
Value value = prop.getValue();
if (value instanceof Component) {
Component c = (Component) value;
handleProperties(c.getPropertyIterator(), map);
} else {
handleReturnUtilDateInsteadOfTimestamp(prop, map);
}
}
private static void handleReturnUtilDateInsteadOfTimestamp(Property prop, Map map) {
if ("true".equals(map.get("hibernate.return.date.instead.of.timestamp"))) {
Value value = prop.getValue();
if (value instanceof SimpleValue) {
SimpleValue simpleValue = (SimpleValue) value;
String typeName = simpleValue.getTypeName();
if ("Java.util.Date".equals(typeName)) {
UtilDateSimpleValue udsv = new UtilDateSimpleValue(simpleValue);
prop.setValue(udsv);
}
}
}
}
}
ご覧のとおり、すべてのプロパティを繰り返し処理し、Java.util.Date型のプロパティのUtilDateSimpleValueをSimpleValue-objectに置き換えます。これは非常にシンプルなクラスです-SimpleValueオブジェクトと同じインターフェース、たとえばorg.hibernate.mapping.KeyValueを実装します。コンストラクターでは、元のSimpleValueオブジェクトが渡されます。したがって、UtilDateSimpleValueへのすべての呼び出しは、1つの例外を除いて元のオブジェクトにリダイレクトされます。メソッドgetType(...)は、カスタムタイプを返します。
public class UtilDateSimpleValue implements KeyValue{
private SimpleValue value;
public UtilDateSimpleValue(SimpleValue value) {
this.value = value;
}
public SimpleValue getValue() {
return value;
}
@Override
public int getColumnSpan() {
return value.getColumnSpan();
}
...
@Override
public Type getType() throws MappingException {
final String typeName = value.getTypeName();
if (typeName == null) {
throw new MappingException("No type name");
}
Type result = new UtilDateUserType();
return result;
}
...
}
最後のステップは、UtilDateUserTypeの実装です。元のorg.hibernate.type.TimestampTypeを拡張し、そのメソッドget()を次のようにオーバーライドします。
public class UtilDateUserType extends TimestampType{
@Override
public Object get(ResultSet rs, String name) throws SQLException {
Timestamp ts = rs.getTimestamp(name);
Date result = null;
if(ts != null){
result = new Date(ts.getTime());
}
return result;
}
}
以上です。少し注意が必要ですが、現在ではすべてのJava.util.Dateプロパティは、既存のコードの追加の変更(注釈またはセッターの変更)なしでJava.util.Dateとして返されます。 Hibernate 4以上で確認したように、独自の型を置き換えるもっと簡単な方法があります(詳細はこちらを参照してください: Hibernate TypeResolver )。どんな提案や批判も歓迎します。
カスタムUserTypeを使用する簡単な代替方法は、永続化されたBeanの日付プロパティのセッターに新しいJava.util.Dateを構築することです。例:
import Java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Column;
@Entity
public class Purchase {
private Date date;
@Column
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
// force Java.sql.Timestamp to be set as a Java.util.Date
this.date = new Date(date.getTime());
}
}
アプローチ1と2は明らかに機能しません。なぜなら、Java.sql.Date
ではなく、JPA/Hibernate仕様ごとにJava.util.Date
オブジェクトを取得するからです。アプローチ3と4から、後者の方がより宣言的であり、フィールド注釈とゲッター注釈の両方で動作するため、後者を選択します。
@tschoが指摘してくれたように、参照したブログ投稿でソリューション4を既にレイアウトしました。たぶんdefaultForType(以下を参照)は、あなたが探していたcentralized solutionを与えるはずです。もちろん、日付(時刻なし)とタイムスタンプフィールドを区別する必要があります。
今後の参考のために、独自のHibernateの使用の概要を残します serType ここに:
HibernateにJava.util.Date
インスタンスを提供するには、 @ Type および @ TypeDef アノテーションを使用して、Java.util.Date Javaは、データベースとの間でタイプします。
コアリファレンスマニュアルの例を参照してください here 。
TimestampAsJavaUtilDateType
1つのエンティティまたはpackage-info.Javaに@TypeDefアノテーションを追加します。両方ともセッションファクトリでグローバルに使用できます(上記のマニュアルリンクを参照)。 defaultForTypeを使用して、タイプJava.util.Date
のすべてのマップ済みフィールドにタイプ変換を適用できます。
@TypeDef
name = "timestampAsJavaUtilDate",
defaultForType = Java.util.Date.class, /* applied globally */
typeClass = TimestampAsJavaUtilDateType.class
)
オプションで、defaultForType
の代わりに、フィールド/ゲッターに@Typeで個別に注釈を付けることができます。
@Entity
public class MyEntity {
[...]
@Type(type="timestampAsJavaUtilDate")
private Java.util.Date myDate;
[...]
}
追伸まったく異なるアプローチを提案するために、通常、とにかくequals()を使用してDateオブジェクトを比較することはしません。代わりに、メソッドとユーティリティクラスを使用して比較します。正確な実装タイプに関係なく、2つのDateインスタンスのカレンダー日付(または秒などの別の解像度)のみ。それは私たちにとってうまくいきました。
Hibernate 4.3.7.Finalのソリューションを次に示します。
pacakge-info.Javaに含まれるもの
@TypeDefs(
{
@TypeDef(
name = "javaUtilDateType",
defaultForType = Java.util.Date.class,
typeClass = JavaUtilDateType.class
)
})
package some.pack;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
JavaUtilDateType:
package some.other.or.same.pack;
import Java.sql.Timestamp;
import Java.util.Comparator;
import Java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.VersionType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.Java.JdbcTimestampTypeDescriptor;
import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor;
/**
* Note: Depends on hibernate implementation details hibernate-core-4.3.7.Final.
*
* @see
* <a href="http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch06.html#types-custom">Hibernate
* Documentation</a>
* @see TimestampType
*/
public class JavaUtilDateType
extends AbstractSingleColumnStandardBasicType<Date>
implements VersionType<Date>, LiteralType<Date> {
public static final TimestampType INSTANCE = new TimestampType();
public JavaUtilDateType() {
super(
TimestampTypeDescriptor.INSTANCE,
new JdbcTimestampTypeDescriptor() {
@Override
public Date fromString(String string) {
return new Date(super.fromString(string).getTime());
}
@Override
public <X> Date wrap(X value, WrapperOptions options) {
return new Date(super.wrap(value, options).getTime());
}
}
);
}
@Override
public String getName() {
return "timestamp";
}
@Override
public String[] getRegistrationKeys() {
return new String[]{getName(), Timestamp.class.getName(), Java.util.Date.class.getName()};
}
@Override
public Date next(Date current, SessionImplementor session) {
return seed(session);
}
@Override
public Date seed(SessionImplementor session) {
return new Timestamp(System.currentTimeMillis());
}
@Override
public Comparator<Date> getComparator() {
return getJavaTypeDescriptor().getComparator();
}
@Override
public String objectToSQLString(Date value, Dialect dialect) throws Exception {
final Timestamp ts = Timestamp.class.isInstance(value)
? (Timestamp) value
: new Timestamp(value.getTime());
// TODO : use JDBC date literal escape syntax? -> {d 'date-string'} in yyyy-mm-dd hh:mm:ss[.f...] format
return StringType.INSTANCE.objectToSQLString(ts.toString(), dialect);
}
@Override
public Date fromStringValue(String xml) throws HibernateException {
return fromString(xml);
}
}
このソリューションは、主に、JdbcTimestampTypeDescriptor型の匿名クラスを介して追加の動作を追加するTimestampType実装に依存しています。
Javaプラットフォームライブラリには、インスタンス化可能なクラスを拡張し、値コンポーネントを追加するクラスがあります。たとえば、Java.sql.TimestampはJava.util.Dateを拡張し、ナノ秒フィールドを追加します。 Timestampのequals実装は対称性に違反しており、TimestampオブジェクトとDateオブジェクトが同じコレクションで使用されているか、そうでなければ混在している場合、不安定な動作を引き起こす可能性があります。それらを別々に保持している限り、それらを混合することを妨げるものは何もなく、結果として生じるエラーはデバッグするのが難しい可能性があります。タイムスタンプクラスのこの動作は間違いであり、エミュレートするべきではありません。
このリンクをチェックしてください
http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
エンティティクラスの_Java.util.Date
_フィールドにthis注釈@Temporal(TemporalType.DATE)
を追加するだけです。
詳細については、 this stackoverflow answerを参照してください。
JUnit assertEqualsがDateとHibernateが発行した「Java.util.Date」型(質問で説明したように、実際にはタイムスタンプです)との比較に失敗したのと同様に、これに問題が発生しました。マッピングを「Java.util.Date」ではなく「date」に変更すると、HibernateがJava.util.Dateメンバーを生成することがわかります。 Hibernateバージョン4.1.12でXMLマッピングファイルを使用しています。
このバージョンは「Java.util.Timestamp」を出力します。
<property name="date" column="DAY" type="Java.util.Date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
このバージョンは「Java.util.Date」を出力します:
<property name="date" column="DAY" type="date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
ただし、Hibernateを使用してDDLを生成する場合、これらは異なるSQLタイプ( 'date'の日付と 'Java.util.Date'のタイムスタンプ)を生成します。