我正在做一个财务应用程序,其中的时间单位是月份,打算作为(年,月)。FTT代表金融交易税,给出一些上下文
我们公司提倡一种干净的数据库优先方法.我们使用Hibernate 5.1。我知道这是EOL,但不能改变。
我目前正在重构一个实体
@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只是一个只有数值列的可嵌入的
一个简短的解释。我们的应用程序每个月都会计算纳税报告,但有时会出现延迟交易,客户不得不向政府缴纳罚款/surcharge税。我们不把这个数字合并到总税收中,但需要向用户显示所有“调整”月份的所有附加税的完整报告。
在这一点上,我知道我可以使用LocalDate作为映射键将其称为一天,并假定日期总是第一天。
但我必须先尝试一种更清洁的方法。
通过上述映射,以下内容是成功的:
@MapKeyType)@MapKeyType)INSERT在单元测试中的第一次税收调整。不起作用的是检索。当Hibernate检索到用于调整的第一个FttReport时。此时,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已被注释掉,因此我尝试使用自定义类型启用该注释。至少,我认为自定义类型处理程序知道必须通过带有年份和月份的构造函数实例化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]问题
好吧..。基本上,我可以问:“我怎样才能让这个东西工作?”但好的措辞是..。
MapKeyType时,AttributeOverride的注释会被完全忽略?我希望覆盖这些列编辑
我已经通过改变AttributeOverrides来解决我的启动问题。
我调试了Hibernate自己的源代码,找到了,它喜欢这个要命名的属性。
通过在Hibernate的源代码(AbstractPropertyHolder行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构造函数上,我调试了路径indexedCollection.getIndex().getType(),它是空的。一定是Type。UserType不扩展Type,而CompositeType则扩展,这甚至不是CompositeUserType。
该语句返回我正在检查的默认ComponentType

现在,我已经理解在SessionFactory init阶段,该键的属性应该改变。或者,从Hibernate提供的现成的持久化器类型判断,可能不可能使用非POJO类型作为映射键,或者可能需要将值用作键列的存储库?
发布于 2020-09-13 11:37:06
我在hibernate 文档中看到了非常类似的映射,但是有一个重要的区别:映射键映射到单个列。我在文档中找不到证据,但恐怕@ElementCollection关联不可能使用映射键映射到几个列的类似映射。
然而,我能够为@OneToMany协会做这件事。因此,下面我将提供一个例子。
假设我们有以下模式:
create table FTT_REPORT
(
REPORT_ID int not null,
primary key (REPORT_ID)
);
create table FTT_REPORT_ADJUSTMENTS
(
REPADJ_ID int not null,
REPADJ_REPORT_ID int not null,
REPADJ_YEAR int,
REPADJ_MONTH int,
primary key (REPADJ_ID),
foreign key (REPADJ_REPORT_ID) references FTT_REPORT(REPORT_ID)
);我们可以使用以下映射:
@Entity
@Table(name="FTT_REPORT")
public class FttReport
{
@Id
@Column(name="REPORT_ID")
private Long id;
@OneToMany(cascade = CascadeType.ALL)
@OrderBy("REPADJ_YEAR, REPADJ_MONTH")
@JoinColumn(name = "REPADJ_REPORT_ID", updatable = false)
@MapKey(name = "adjYearMonth")
private SortedMap<YearMonth, FttAdjustment> adjustments;
}
@Entity
@Table(name = "FTT_REPORT_ADJUSTMENTS")
public class FttAdjustment
{
@Id
@Column(name = "REPADJ_ID")
private Long id;
// this needs only for adding new FttAdjustment
@Column(name = "REPADJ_REPORT_ID")
private Long reportId;
@Type(type = "com.acme.FttYearMonthUserType")
@Columns(columns = {
@Column(name = "REPADJ_YEAR"),
@Column(name = "REPADJ_MONTH")
})
private YearMonth adjYearMonth;
}@MapKey是一个实体时,才能使用FttAdjustment注释。updatable = false被添加到@JoinColumn中,以避免在插入新FttAdjustment时进行额外的更新查询。5.4.10.Final测试的。还有另外一个音符。@AttributeOverride是JPA为覆盖可嵌入类型列名定义的注释,但是您尝试使用它来设置hibernate自定义基本类型的列名。恐怕这是个错误。
https://stackoverflow.com/questions/63691367
复制相似问题