在 JPA-SQL(或 HQL)查询中兼容的 Hibernate Composite UserType
Hibernate CompositeUserType that is comparible in JPA-QL (or HQL) query
我为 Hibernate 创建了一个自定义类型来存储 OffsetDateTime
的时间戳 和 偏移量(因为默认的 JPA 2.2 / Hibernate 5.2 java 8支持实现):
public class OffsetDateTimeHibernateType implements CompositeUserType {
@Override
public Class returnedClass() {
return OffsetDateTime.class;
}
@Override
public String[] getPropertyNames() {
return new String[] {"dateTime", "zoneOffset"};
}
@Override
public Type[] getPropertyTypes() {
// Not sure if we should use LocalDateTimeType.INSTANCE instead of TIMESTAMP
return new Type[]{StandardBasicTypes.TIMESTAMP, StandardBasicTypes.INTEGER};
}
@Override
public Object getPropertyValue(Object o, int propertyIndex) {
if (o == null) {
return null;
}
OffsetDateTime offsetDateTime = (OffsetDateTime) o;
switch (propertyIndex) {
case 0:
return Timestamp.valueOf(offsetDateTime.toLocalDateTime());
case 1:
return offsetDateTime.getOffset().getTotalSeconds();
default:
throw new IllegalArgumentException("The propertyIndex (" + propertyIndex
+ ") must be 0 or 1.");
}
}
@Override
public OffsetDateTime nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws SQLException {
if (resultSet == null) {
return null;
}
Timestamp timestamp = (Timestamp) StandardBasicTypes.TIMESTAMP.nullSafeGet(resultSet, names[0], session, owner);
if (timestamp == null) {
throw new IllegalStateException("The timestamp (" + timestamp + ") for an "
+ OffsetDateTime.class.getSimpleName() + "cannot be null.");
}
LocalDateTime localDateTime = timestamp.toLocalDateTime();
Integer zoneOffsetSeconds = (Integer) StandardBasicTypes.INTEGER.nullSafeGet(resultSet, names[1], session, owner);
if (zoneOffsetSeconds == null) {
throw new IllegalStateException("The zoneOffsetSeconds (" + zoneOffsetSeconds + ") for an "
+ OffsetDateTime.class.getSimpleName() + "cannot be null.");
}
return OffsetDateTime.of(localDateTime, ZoneOffset.ofTotalSeconds(zoneOffsetSeconds));
}
@Override
public void nullSafeSet(PreparedStatement statement, Object value, int parameterIndex, SessionImplementor session)
throws SQLException {
if (value == null) {
statement.setNull(parameterIndex, StandardBasicTypes.TIMESTAMP.sqlType());
statement.setNull(parameterIndex, StandardBasicTypes.INTEGER.sqlType());
return;
}
OffsetDateTime offsetDateTime = (OffsetDateTime) value;
statement.setTimestamp(parameterIndex, Timestamp.valueOf(offsetDateTime.toLocalDateTime()));
statement.setInt(parameterIndex, offsetDateTime.getOffset().getTotalSeconds());
}
// ************************************************************************
// Mutable related methods
// ************************************************************************
@Override
public boolean isMutable() {
return false;
}
@Override
public Object deepCopy(Object value) {
return value; // OffsetDateTime is immutable
}
@Override
public Object replace(Object original, Object target, SessionImplementor session, Object owner) {
return original; // OffsetDateTime is immutable
}
@Override
public void setPropertyValue(Object component, int property, Object value) {
throw new UnsupportedOperationException("A OffsetDateTime is immutable.");
}
// ************************************************************************
// Other methods
// ************************************************************************
@Override
public boolean equals(Object a, Object b) {
if (a == b) {
return true;
} else if (a == null || b == null) {
return false;
}
return a.equals(b);
}
@Override
public int hashCode(Object o) {
if (o == null) {
return 0;
}
return o.hashCode();
}
@Override
public Serializable disassemble(Object value, SessionImplementor session) {
return (Serializable) value;
}
@Override
public Object assemble(Serializable cached, SessionImplementor session, Object owner) {
return cached;
}
}
现在,我希望能够比较它,所以这个 JPA-QL 查询有效:
@NamedQuery(name = "Shift.myQuery",
query = "select sa from Shift sa" +
" where sa.endDateTime >= :startDateTime" +
" and sa.startDateTime < :endDateTime")
在此型号上:
@Entity
public class Shift {
@Type(type = "...OffsetDateTimeHibernateType")
@Columns(columns = {@Column(name = "startDateTime"), @Column(name="startDateTimeOffset")})
private OffsetDateTime startDateTime;
@Type(type = "...OffsetDateTimeHibernateType")
@Columns(columns = {@Column(name = "endDateTime"), @Column(name="endDateTimeOffset")})
private OffsetDateTime endDateTime;
...
}
但是失败了:
HHH000177: Error in named query: Shift.myQuery: org.hibernate.hql.internal.ast.QuerySyntaxException: >= operator not supported on composite types. [select sa from org.optaplanner.openshift.employeerostering.shared.shift.Shift sa where sa.endDateTime >= :startDateTime and sa.startDateTime < :endDateTime]
at org.hibernate.hql.internal.ast.QuerySyntaxException.generateQueryException(QuerySyntaxException.java:79)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:218)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:142)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:115)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:76)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:150)
at org.hibernate.internal.NamedQueryRepository.checkNamedQueries(NamedQueryRepository.java:155)
at org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:796)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:492)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:422)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:880)
如何使我的 CustomUserType 具有可比性?
Hibernate 无法知道如何将您的自定义类型与多列进行比较。您知道列之间的关系,但 Hibernate 不知道。没有测试它(如果我有时间可以做)我认为你可以重写查询以使用部件的 属性 名称,例如:
select sa from Shift sa
where sa.endDateTime.dateTime >= :startDateTimeDateTimePart
and sa.startDateTime.dateTime < :endDateTimeDateTimePart
要使其与偏移量一起使用,您需要标准化您比较的值,即将偏移量表示的小时数添加到日期。您可以使用数据库的自定义函数来执行此操作,请参阅 JPA 2.2 (https://jcp.org/aboutJava/communityprocess/mrel/jsr338/index.html) 中的 4.6.17.3。当然,您也可以在数据库中定义一个自定义比较函数,将两个部分作为输入参数并使用该函数调用它,但我个人会尽量坚持使用预定义的函数。无论您使用什么数据库,都应该涵盖向时间戳添加小时数。
我为 Hibernate 创建了一个自定义类型来存储 OffsetDateTime
的时间戳 和 偏移量(因为默认的 JPA 2.2 / Hibernate 5.2 java 8支持实现
public class OffsetDateTimeHibernateType implements CompositeUserType {
@Override
public Class returnedClass() {
return OffsetDateTime.class;
}
@Override
public String[] getPropertyNames() {
return new String[] {"dateTime", "zoneOffset"};
}
@Override
public Type[] getPropertyTypes() {
// Not sure if we should use LocalDateTimeType.INSTANCE instead of TIMESTAMP
return new Type[]{StandardBasicTypes.TIMESTAMP, StandardBasicTypes.INTEGER};
}
@Override
public Object getPropertyValue(Object o, int propertyIndex) {
if (o == null) {
return null;
}
OffsetDateTime offsetDateTime = (OffsetDateTime) o;
switch (propertyIndex) {
case 0:
return Timestamp.valueOf(offsetDateTime.toLocalDateTime());
case 1:
return offsetDateTime.getOffset().getTotalSeconds();
default:
throw new IllegalArgumentException("The propertyIndex (" + propertyIndex
+ ") must be 0 or 1.");
}
}
@Override
public OffsetDateTime nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws SQLException {
if (resultSet == null) {
return null;
}
Timestamp timestamp = (Timestamp) StandardBasicTypes.TIMESTAMP.nullSafeGet(resultSet, names[0], session, owner);
if (timestamp == null) {
throw new IllegalStateException("The timestamp (" + timestamp + ") for an "
+ OffsetDateTime.class.getSimpleName() + "cannot be null.");
}
LocalDateTime localDateTime = timestamp.toLocalDateTime();
Integer zoneOffsetSeconds = (Integer) StandardBasicTypes.INTEGER.nullSafeGet(resultSet, names[1], session, owner);
if (zoneOffsetSeconds == null) {
throw new IllegalStateException("The zoneOffsetSeconds (" + zoneOffsetSeconds + ") for an "
+ OffsetDateTime.class.getSimpleName() + "cannot be null.");
}
return OffsetDateTime.of(localDateTime, ZoneOffset.ofTotalSeconds(zoneOffsetSeconds));
}
@Override
public void nullSafeSet(PreparedStatement statement, Object value, int parameterIndex, SessionImplementor session)
throws SQLException {
if (value == null) {
statement.setNull(parameterIndex, StandardBasicTypes.TIMESTAMP.sqlType());
statement.setNull(parameterIndex, StandardBasicTypes.INTEGER.sqlType());
return;
}
OffsetDateTime offsetDateTime = (OffsetDateTime) value;
statement.setTimestamp(parameterIndex, Timestamp.valueOf(offsetDateTime.toLocalDateTime()));
statement.setInt(parameterIndex, offsetDateTime.getOffset().getTotalSeconds());
}
// ************************************************************************
// Mutable related methods
// ************************************************************************
@Override
public boolean isMutable() {
return false;
}
@Override
public Object deepCopy(Object value) {
return value; // OffsetDateTime is immutable
}
@Override
public Object replace(Object original, Object target, SessionImplementor session, Object owner) {
return original; // OffsetDateTime is immutable
}
@Override
public void setPropertyValue(Object component, int property, Object value) {
throw new UnsupportedOperationException("A OffsetDateTime is immutable.");
}
// ************************************************************************
// Other methods
// ************************************************************************
@Override
public boolean equals(Object a, Object b) {
if (a == b) {
return true;
} else if (a == null || b == null) {
return false;
}
return a.equals(b);
}
@Override
public int hashCode(Object o) {
if (o == null) {
return 0;
}
return o.hashCode();
}
@Override
public Serializable disassemble(Object value, SessionImplementor session) {
return (Serializable) value;
}
@Override
public Object assemble(Serializable cached, SessionImplementor session, Object owner) {
return cached;
}
}
现在,我希望能够比较它,所以这个 JPA-QL 查询有效:
@NamedQuery(name = "Shift.myQuery",
query = "select sa from Shift sa" +
" where sa.endDateTime >= :startDateTime" +
" and sa.startDateTime < :endDateTime")
在此型号上:
@Entity
public class Shift {
@Type(type = "...OffsetDateTimeHibernateType")
@Columns(columns = {@Column(name = "startDateTime"), @Column(name="startDateTimeOffset")})
private OffsetDateTime startDateTime;
@Type(type = "...OffsetDateTimeHibernateType")
@Columns(columns = {@Column(name = "endDateTime"), @Column(name="endDateTimeOffset")})
private OffsetDateTime endDateTime;
...
}
但是失败了:
HHH000177: Error in named query: Shift.myQuery: org.hibernate.hql.internal.ast.QuerySyntaxException: >= operator not supported on composite types. [select sa from org.optaplanner.openshift.employeerostering.shared.shift.Shift sa where sa.endDateTime >= :startDateTime and sa.startDateTime < :endDateTime]
at org.hibernate.hql.internal.ast.QuerySyntaxException.generateQueryException(QuerySyntaxException.java:79)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:218)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:142)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:115)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:76)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:150)
at org.hibernate.internal.NamedQueryRepository.checkNamedQueries(NamedQueryRepository.java:155)
at org.hibernate.internal.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:796)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:492)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:422)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:880)
如何使我的 CustomUserType 具有可比性?
Hibernate 无法知道如何将您的自定义类型与多列进行比较。您知道列之间的关系,但 Hibernate 不知道。没有测试它(如果我有时间可以做)我认为你可以重写查询以使用部件的 属性 名称,例如:
select sa from Shift sa
where sa.endDateTime.dateTime >= :startDateTimeDateTimePart
and sa.startDateTime.dateTime < :endDateTimeDateTimePart
要使其与偏移量一起使用,您需要标准化您比较的值,即将偏移量表示的小时数添加到日期。您可以使用数据库的自定义函数来执行此操作,请参阅 JPA 2.2 (https://jcp.org/aboutJava/communityprocess/mrel/jsr338/index.html) 中的 4.6.17.3。当然,您也可以在数据库中定义一个自定义比较函数,将两个部分作为输入参数并使用该函数调用它,但我个人会尽量坚持使用预定义的函数。无论您使用什么数据库,都应该涵盖向时间戳添加小时数。