我知道几年前有人问过这个问题,但以防万一其他人偶然发现这个问题。为了在 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)在查询中使用属性。