如何将自定义用户类型作为参数传递给 Spring Data JPA 查询方法?

问题描述 投票:0回答:1

我有一个 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

postgresql hibernate jpa spring-data-jpa spring-data
1个回答
0
投票

我们可以使用 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

© www.soinside.com 2019 - 2024. All rights reserved.