0

我的应用程序在后端使用 SQLServer 和 JPA2。应用程序使用每个实体的时间戳列(在 SQLServer 意义上,相当于行版本,请参见此处)来跟踪新修改的实体。注意 SQLServer 将此列存储为binary(8)

每个实体都有各自的timestamp属性,映射为@Lob,这是二进制列的方式:

@Lob
@Column(columnDefinition="timestamp", insertable=false, updatable=false)
public byte[] getTimestamp() {
...

服务器向移动客户端发送增量更新以及最新的数据库时间戳。然后,移动客户端将在下一次刷新请求时将旧时间戳传回服务器,以便服务器知道只返回新数据。下面是一个典型的查询(在 JPQL 中)的样子:

select v from Visit v where v.timestamp > :oldTimestamp

请注意,我使用字节数组作为查询参数,并且以这种方式在 JPQL 中实现时效果很好。

当尝试使用 Criteria API 做同样的事情时,我的问题就开始了:

private void getFreshVisits(byte[] oldVersion) {
  EntityManager em = getEntityManager();
  CriteriaQuery<Visit> cq = cb.createQuery(Visit.class);
  Root<Visit> root = cq.from(Visit.class);
  Predicate tsPred = cb.gt(root.get("timestamp").as(byte[].class), oldVersion); // compiler error
  cq.where(tsPred);
  ...
}

以上将导致编译器错误,因为它要求该gt方法严格与Number. 可以改为使用greaterThan只需要参数的方法,Comparable这将导致另一个编译器错误。

综上所述,我的问题是:如何使用条件 api 为 byte[] 属性添加大于谓词?任何帮助将不胜感激。

PS。至于我为什么不使用常规的 DateTimelast_modified列:由于并发性和实现同步的方式,这种方法可能会导致更新丢失。Microsoft 的 Sync Framework文档也推荐前一种方法。

4

1 回答 1

1

我知道几年前有人问过这个问题,但以防万一其他人偶然发现这个问题。为了在 JPA 中使用 SQLServer rowver 列,您需要做几件事。

创建一个将包装 rowver/timestamp 的类型:

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;

/**
 * A RowVersion object
 */
public class RowVersion implements Serializable, Comparable<RowVersion> {

    @XmlTransient
    @JsonIgnore
    private byte[] rowver;

    public RowVersion() {
    }

    public RowVersion(byte[] internal) {
        this.rowver = internal;
    }

    @XmlTransient
    @JsonIgnore
    public byte[] getRowver() {
        return rowver;
    }

    public void setRowver(byte[] rowver) {
        this.rowver = rowver;
    }

    @Override
    public int compareTo(RowVersion o) {
        return new BigInteger(1, rowver).compareTo(new BigInteger(1, o.getRowver()));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RowVersion that = (RowVersion) o;
        return Arrays.equals(rowver, that.rowver);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(rowver);
    }
}

这里的关键是,如果你想在计算中使用它(你肯定会这样做),它会实现 Comparable ..

接下来创建一个 AttributeConverter,它将从 byte[] 移动到您刚刚创建的类:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

/**
 * JPA converter for the RowVersion type
 */
@Converter
public class RowVersionTypeConverter implements AttributeConverter<RowVersion, byte[]> {

    @Override
    public byte[] convertToDatabaseColumn(RowVersion attribute) {
        return attribute != null ? attribute.getRowver() : null;
    }

    @Override
    public RowVersion convertToEntityAttribute(byte[] dbData) {
        return new RowVersion(dbData);
    }
}

现在让我们将此 RowVersion 属性/类型应用于现实世界的场景。假设您想查找在某个时间点或之前发生更改的所有程序。

解决此问题的一种直接方法是在 db.xml 中的对象和时间戳列中使用 DateTime 字段。然后你会使用'where lastUpdatedDate <= :date'。

假设您没有该时间戳列,或者无法保证在进行更改时它会正确更新;或者假设您的商店喜欢 SQLServer 并想改用 rowver。

该怎么办?有两个问题需要解决。一是如何生成rowver,二是如何使用生成的rowver来查找程序。

由于数据库生成 rowver,您可以向 db 询问“当前最大 rowver”(自定义 sql 服务器事物),或者您可以简单地保存具有 RowVersion 属性的对象,然后使用该对象生成的 RowVersion 作为边界查找在那之后更改的程序的查询。后一种解决方案更便携,即下面的解决方案。

下面的 SyncPoint 类片段是用作“时间点”类型交易的对象。因此,一旦保存了 SyncPoint,附加到它的 RowVersion 就是保存时的 db 版本。

这是 SyncPoint 片段。注意指定自定义转换器的注释(不要忘记使列 insertable = false,updateable = false):

/**
 * A sample super class that uses RowVersion
 */
@MappedSuperclass
public abstract class SyncPoint {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // type is rowver for SQLServer, blob(8) for postgresql and h2    
    @Column(name = "current_database_version", insertable = false, updatable = false)
    @Convert(converter = RowVersionTypeConverter.class)
    private RowVersion currentDatabaseVersion;

    @Column(name = "created_date_utc", columnDefinition = "timestamp", nullable = false)
    private DateTime createdDate;
    ...

另外(对于这个例子)这里是我们想要找到的 Program 对象:

@Entity
@Table(name = "program_table")
public class Program {

    @Id
    private Integer id;

    private boolean active;

    // type is rowver for SQLServer, blob(8) for postgresql and h2
    @Column(name = "rowver", insertable = false, updatable = false)
    @Convert(converter = RowVersionTypeConverter.class)
    private RowVersion currentDatabaseVersion;

    @Column(name = "last_chng_dt")
    private DateTime lastUpdatedDate;
    ...

现在您可以在 JPA 标准查询中使用这些字段,就像其他任何内容一样。这是我们在 spring-data Specifications 类中使用的片段:

/**
 * Find Programs changed after a synchronization point
 *
 * @param filter that has the changedAfter sync point
 * @return a specification or null
 */
public Specification<Program> changedBeforeOrEqualTo(final ProgramSearchFilter filter) {
    return new Specification<Program>() {
        @Override
        public Predicate toPredicate(Root<Program> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            if (filter != null && filter.changedAfter() != null) {
                // load the SyncPoint from the db to get the rowver column populated
                SyncPoint fromDb = synchronizationPersistence.reload(filter.changedBeforeOrEqualTo());
                if (fromDb != null) {
                    // real sync point made by database
                    if (fromDb.getCurrentDatabaseVersion() != null) {
                        // use binary version
                        return cb.lessThanOrEqualTo(root.get(Program_.currentDatabaseVersion),
                                fromDb.getCurrentDatabaseVersion());
                    } else if (fromDb.getCreatedDate() != null) {
                        // use timestamp instead of binary version cause db doesn't make one
                        return cb.lessThanOrEqualTo(root.get(Program_.lastUpdatedDate),
                                fromDb.getCreatedDate());
                    }
                }
            }
            return null;
        }
    };
}

上面的规范适用于二进制当前数据库版本或时间戳。这样我可以在 SQLServer 以外的数据库上测试我的东西和所有上游代码。

就是这样:a)类型来包装 byte[] b)JPA 转换器 c)在查询中使用属性。

于 2016-03-03T23:14:14.000 回答