我有一个 Spring data JPA 存储库,它由 postgresql 数据库支持。作为查询的一部分,我想在查询中使用间隔。我设法通过创建一个
SQLFunction
来实现它,它像这样转换字符串:
public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory)
throws QueryException {
return "cast(" + args.get(0) + " as interval)";
}
它可以工作,但实际上它不会转换为间隔类型,而是转换为字符串(
cstring
),然后由 postgresql 重新转换为间隔。虽然这有效,但当它被传递给另一个 SQL 函数时,它会产生一些不良影响(例如,在某些情况下,服务器端准备好的语句参数预计是一个间隔,但实际上是一个 cstring,在大型查询中最终效率低下) .
我知道 postgresql 驱动程序具有 PGInterval 类型,应该可以实现此目的。但是,我无法让它与 Spring Data JPA 参数一起使用。
我尝试直接传递 PGInterval 但它被转换为
bytea
。
我尝试将 Converter 与 Duration 类一起使用,但它似乎没有被调用,并且参数以 bigint 形式发送。
@Converter
public class DurationConverter implements AttributeConverter<Duration, PGInterval> {
@Override
public PGInterval convertToDatabaseColumn(Duration duration) {
try {
return new PGInterval(duration.toString());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public Duration convertToEntityAttribute(PGInterval pgInterval) {
if (pgInterval.getYears() == 0 && pgInterval.getMonths() == 0) {
return Duration.ofDays(pgInterval.getDays())
.plusHours(pgInterval.getHours())
.plusMinutes(pgInterval.getMinutes())
.plusSeconds(pgInterval.getWholeSeconds())
.plusMillis(pgInterval.getMicroSeconds() / 1000);
}
else {
throw new RuntimeException(String.format("Cannot convert interval with %s years and %s months to Duration that needs a precise interval", pgInterval.getYears(), pgInterval.getMonths()));
}
}
}
我什至尝试创建一个 Hibernate UserType:
@Component
@TypeDef(defaultForType = Duration.class, typeClass = DurationType.class)
public class DurationType implements UserType {
public int[] sqlTypes() {
return new int[] {Types.OTHER};
}
public Class<?> returnedClass() {
return Duration.class;
}
@Override
public boolean equals(Object o1, Object o2) throws HibernateException {
return o1.equals(o2);
}
@Override
public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
@Override
public Object nullSafeGet(
ResultSet resultSet,
String[] names,
SharedSessionContractImplementor sharedSessionContractImplementor,
Object o)
throws HibernateException, SQLException {
try {
final PGInterval pgi = (PGInterval) resultSet.getObject(names[0]);
final int years = pgi.getYears();
final int months = pgi.getMonths();
final int days = pgi.getDays();
final int hours = pgi.getHours();
final int mins = pgi.getMinutes();
final int seconds = pgi.getWholeSeconds();
final int microseconds = pgi.getMicroSeconds();
if (years == 0 && months == 0) {
return Duration.ofDays(days)
.plusHours(hours)
.plusMinutes(mins)
.plusSeconds(seconds)
.plusMillis(microseconds / 1000);
} else {
return null;
}
} catch (Exception e) {
return null;
}
}
@Override
public void nullSafeSet(
PreparedStatement statement,
Object value,
int index,
SharedSessionContractImplementor sharedSessionContractImplementor)
throws HibernateException, SQLException {
if (value == null) {
statement.setNull(index, Types.OTHER);
} else {
final Duration duration = ((Duration) value);
final int days = (int) duration.toDaysPart();
final int hours = duration.toHoursPart();
final int mins = duration.toMinutesPart();
final int secs = duration.toSecondsPart();
final PGInterval pgi = new PGInterval(0, 0, days, hours, mins, secs);
statement.setObject(index, pgi);
}
}
public boolean isMutable() {
return false;
}
public Serializable disassemble(Object value) throws HibernateException {
throw new HibernateException("not implemented");
}
public Object assemble(Serializable cached, Object owner) throws HibernateException {
throw new HibernateException("not implemented");
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
throw new HibernateException("not implemented");
}
}
但在这种情况下,
Duration
被翻译为 bigint
。
有没有办法让 Spring Data JPA 在翻译查询方法参数时发送
interval
?
我们可以使用 Hibernate 注释将
PostgreSQL Interval
映射到 Java Duration
:
休眠 6:
@Entity
@Table
public class SampleEntity {
@Type(PostgreSQLIntervalType.class)
@Column(
name = "presale_period",
columnDefinition = "interval"
)
private Duration presalePeriod;
}
休眠5:
@Entity
@Table
@TypeDef(typeClass = PostgreSQLIntervalType.class, defaultForType = Duration.class)
public class SampleEntity {
@Column(
name = "presale_period",
columnDefinition = "interval"
)
private Duration presalePeriod;
}
还有 spring-jpa 存储库:
public interface SampleEntityRepository extends JpaRepository<SampleEntity, UUID>{
// Drived query
Collection<SampleEntity> findBySalePeriodGreaterThan(Duration duration);
// HQL
@Query("select p from Product p where p.salePeriod < ?1")
Collection<SampleEntity> findBySalePeriodLessThan(Duration duration);
}
更多详情可以参考这个post