JPA2 CriteriaBuilder:使用LOB属性进行更大比较

时间:2013-09-02 12:59:06

标签: sql-server jpa-2.0 criteria-api

我的应用程序在后端使用SQLServer和JPA2。应用程序使用每个实体的时间戳列(SQLServer意义上的 ,相当于行版see here)来跟踪新修改的实体。 NB SQLServer将此列存储为二进制(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方法,它只需要params为Comparable,这将导致另一个编译器错误。

总而言之,我的问题是:如何使用条件api为byte []属性添加moreThan谓词?任何帮助将不胜感激。

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

1 个答案:

答案 0 :(得分:0)

我知道这是几年前被问到的,以防其他人偶然发现这个问题。为了在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中的object和timestamp列中使用DateTime字段。然后你会使用&#39;其中lastUpdatedDate&lt; =:date&#39;。

假设您没有该时间戳列,或者无法保证在进行更改时它会得到正确更新;或者让我们说你的商店喜欢SQLServer并且想要使用rowver。

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

由于数据库生成了行方法,因此您可以向数据库询问当前最大行标记&#39; (自定义sql服务器)或者您可以简单地保存具有RowVersion属性的对象,然后使用该对象生成的RowVersion作为查询的边界,以查找在此之后更改的程序。后一种解决方案更便携,解决方案如下。

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

这是SyncPoint代码段。注意注释以指定自定义转换器(不要忘记使列可插入= 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规范类中使用的代码段:

/**
 * 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)包装字节的类型[] b)JPA转换器c)在查询中使用属性。