我的应用程序在后端使用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也推荐使用前一种方法。
答案 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)在查询中使用属性。