JPA/Hibernate使用java键映射。时间年月
我正在开发一个财务应用程序,其中时间单位为月,计划为(年,月)。FTT代表Financial Transaction Tax,给出一点上下文
我们公司首先推广清洁数据库的方法。我们使用Hibernate 5.1。我知道这是下线,但不能改变
我目前正在重构一个实体
@Entity
@Table(name="FTT_REPORT")
public class FttReport {
@Column(name="REPORT_ID")
private final Long id = null;
@Column(name="REFERENCE_YEAR")
private int referenceYear; //Old code, I could use YearMonth directly
@Column(name="REFERENCE_MONTH")
private int referenceMonth; //Old code, I could use YearMonth directly
private BigDecimal [a lot of tax figures];
@ElementCollection(fetch = FetchType.EAGER)
@MapKeyClass(YearMonth.class)
@OrderBy("REFERENCE_YEAR, REFERENCE_MONTH")
// @MapKeyType(@Type(type = "com.acme.FttYearMonthUserType")) #I'll explain soon why it's commented out
@AttributeOverrides({//
@AttributeOverride(name = "key.year", column = @Column(name = "REFERENCE_YEAR")),//
@AttributeOverride(name = "key.month", column = @Column(name = "REFERENCE_MONTH")),//
})
@CollectionTable(//
name = "FTT_REPORT_ADJUSTMENTS",//
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "FK_FTT_ADJUSTMENT_REPORT"),//
joinColumns = @JoinColumn(name = "REPORT_ID")//
)
private final SortedMap<YearMonth, FttAdjustment> adjustments = new TreeMap<>();
}
FttAdjustment
只是一个只有数字列的可嵌入函数
一个简短的解释。我们的应用程序每个月计算一次税务报告,但有时出现延迟交易,客户必须向政府支付罚款/附加税。我们不会将该数字与总税额混合,但需要向用户显示所有“调整”月份的所有附加税的完整报告
在这一点上,我知道我可以通过使用LocalDate
作为映射键来命名这一天,并假设这一天总是第一天
但我必须先尝试更干净的方法
通过以上映射,以下操作是成功的:
- Hibernate使用我喜欢的正确列正确地创建数据库创建脚本(没有
@MapKeyType
) - Hibernate成功地添加了预期的主键和外键约束(没有
@MapKeyType
) - Hibernate成功{
}这是单元测试中的第一次税务调整🎉 李>
不起作用的是检索。当Hibernate检索第一个经过调整测试的FTT端口时。此时,MapKeyType仍被注释掉
Caused by: org.hibernate.InstantiationException: No default constructor for entity: : java.time.YearMonth
at org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:81) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.tuple.component.AbstractComponentTuplizer.instantiate(AbstractComponentTuplizer.java:83) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.type.ComponentType.instantiate(ComponentType.java:577) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.type.ComponentType.instantiate(ComponentType.java:583) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:681) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:325) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.readIndex(AbstractCollectionPersister.java:845) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.collection.internal.PersistentMap.readFrom(PersistentMap.java:265) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.finishUpRow(CollectionReferenceInitializerImpl.java:77) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.readRow(AbstractRowReader.java:121) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:122) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:88) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:688) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:75) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2004) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:567) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:249) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:563) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.forceInitialization(AbstractPersistentCollection.java:731) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.engine.internal.StatefulPersistenceContext.initializeNonLazyCollections(StatefulPersistenceContext.java:918) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:347) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.Loader.doList(Loader.java:2622) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.Loader.doList(Loader.java:2605) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2434) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.Loader.list(Loader.java:2429) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:501) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:370) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:216) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1339) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:87) ~[hibernate-core-5.1.17.Final.jar:5.1.17.Final]
at com.acme.core.data.dao.hibernate.BaseDaoImpl.findAll(BaseDaoImpl.java:927) ~[acme-3.10.5-BETA-15.jar:47cac235aaafde97a5117a64431433f405db8026]
到目前为止还很糟糕。其他实体,如Trade
通过使用自定义类型,平铺地使用YearMonth列
@MappedSuperclass
public abstract class FttAbstractTrade {
@Type(type = "com.acme.FttYearMonthUserType")
@Columns(columns = {//
@Column(name = "REFERENCE_YEAR"),//
@Column(name = "REFERENCE_MONTH")//
})
protected YearMonth referencePeriod;
}
此时,请注意@MapKeyType
已被注释掉,因此我尝试使用自定义类型启用该注释。我想,至少自定义类型处理程序知道必须通过带有year和month的构造函数实例化YearMonth
很酷,但是现在Hibernate不会映射REFERENCE_YEAR
和REFERENCE_MONTH
列,而是尝试查找基本的year
和month
列
我知道如果我接受Hibernate强制使用的列名,应用程序就会工作,但在这成为一个彻底的阻碍之前,我还有一点时间来调查和学习
启用MapKeyType
注释后,会发生启动错误(因为hbm2ddl.auto
是validate
)
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing column [month] in table [FTT_REPORT_ADJUSTMENTS]
问题:
嗯。。。基本上,我可以问“我怎样才能让这东西工作?”但好的措辞是
- 在地图中使用非常规复杂类型作为键的正确方法是什么?所谓非常规,我指的是一种没有在Hibernate中本机注册(也没有在JSR-310扩展中注册)并且没有POJO格式的类型
- 为什么,当我启用
MapKeyType
时,我的AttributeOverride
注释会被完全忽略?我本以为会覆盖这些柱子 - 有没有更好的方法告诉Hibernate我想让这个多键映射命名它的列我喜欢的方式李>
编辑
我已经通过更改AttributeOverrides
解决了启动问题
我调试了Hibernate自己的源代码,找到了它喜欢命名的属性
通过在Hibernate的源代码(AbstractPropertyHolder line 264)中设置断点,我发现Hibernate在本例中使用关键字index
来标识映射键
我变了
@AttributeOverrides({//
@AttributeOverride(name = "index.year", column = @Column(name = "REFERENCE_YEAR")),//
@AttributeOverride(name = "index.month", column = @Column(name = "REFERENCE_MONTH")),//
})
现在应用程序启动了,但我仍然得到了实例化异常
编辑
用户类型的源代码FttYearMonthUserType
public class FttYearMonthUserType implements UserType
{
@Override
public int[] sqlTypes()
{
return new int[] { Types.INTEGER, Types.INTEGER };
}
@Override
public Class returnedClass()
{
return YearMonth.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException
{
return Objects.equals(x, y);
}
@Override
public int hashCode(Object x) throws HibernateException
{
return Objects.hashCode(x);
}
@Override
public YearMonth nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException
{
int year = rs.getInt(names[0]);
if (rs.wasNull())
return null;
int month = rs.getInt(names[1]);
if (rs.wasNull())
return null;
return YearMonth.of(year, month);
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException
{
if (Objects.isNull(value))
{
st.setNull(index, Types.INTEGER);
st.setNull(index + 1, Types.INTEGER);
}
else
{
YearMonth yearMonth = (YearMonth) value;
Integer year = yearMonth.getYear();
Integer month = yearMonth.getMonthValue();
st.setObject(index, year, Types.INTEGER);
st.setObject(index + 1, month, Types.INTEGER);
}
}
@Override
public YearMonth deepCopy(Object value) throws HibernateException
{
if (value == null)
return null;
YearMonth yearMonth = (YearMonth) value;
return YearMonth.of(yearMonth.getYear(), yearMonth.getMonthValue());
}
@Override
public boolean isMutable()
{
return false; //Fixed after conversation
}
@Override
public Serializable disassemble(Object value) throws HibernateException
{
return deepCopy(value);
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return deepCopy(cached);
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return deepCopy(original);
}
}
编辑
在对Hibernate的内核进行了更多调试之后,我发现了下面的内容
在AbstractCollectionPersister constructor上,我调试了路径indexedCollection.getIndex().getType()
,它是空的。它必须是一个Type
UserType
不扩展Type
,而CompositeType
扩展,这甚至不是CompositeUserType
该语句返回我正在检查的默认ComponentType
现在我已经了解到,在SessionFactory初始化阶段,该键的属性应该改变。或者,根据Hibernate提供的现成持久器类型判断,也许不可能使用非POJO类型作为映射键,也许需要使用该值作为键列的存储
# 1 楼答案
我在hibernate documentation中看到了非常相似的映射,但有一个重要的区别:映射键映射到单个列。我在文档中找不到证据,但对于
@ElementCollection
关联,恐怕无法使用映射到多个列的map key进行类似映射然而,我能够为
@OneToMany
协会做这件事。下面我将提供一个例子假设我们有以下模式:
我们可以使用以下映射:
FttAdjustment
是一个实体时,才能使用{updatable = false
被添加到@JoinColumn
中,以避免在插入新的FttAdjustment
时进行额外的更新查询李>5.4.10.Final
测试的李>附言,还有一个附加说明。
@AttributeOverride
是JPA为overriding embeddable types列名定义的注释,但您尝试使用它来设置hibernate custom basic types的列名。恐怕这是个错误