© 2008-2017 The original authors.
Note
|
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. |
Preface
Project metadata
-
Version control - http://github.com/paulcwarren/spring-content/
-
Bugtracker - http://github.com/paulcwarren/spring-content/issues
-
Release repository - https://repo1.maven.org/maven2/
-
Snapshots repository - https://oss.sonatype.org/content/repositories/snapshots
1. Introduction
Traditional document management use cases often require content to be locked and versioned. The Spring Versions JPA module provides these capabilities.
Spring Versions JPA can be used with the following storage modules:
2. Getting Started
2.1. Introduction
Spring Versions are enhancements to Spring Data that extend the existing optimistic locking semantics of Entities to
any associated Spring Content Resources and also adds user-facing pessimistic locking and versioning semantics to
Entities and their Content through a new LockingAndVersioningRepository
interface defined in the base module
Spring Version Commons.
Spring Versions JPA is the JPA-based implementation of these enhancements.
2.2. Adding Spring Versions JPA to a Spring Boot project
Spring Versions JPA has both a Spring Boot starter and auto-configuration so the simplest way to get to started is
with a Spring Boot application. Add the spring-versions-jpa-boot-starter
to the classpath:
dependencies {
...
compile("com.github.paulcwarren:spring-versions-jpa-boot-starter:${version}")
...
}
<dependencies>
...
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-versions-jpa-boot-starter</artifactId>
<version>${version}</version>
</dependency>
...
</dependencies>
2.3. Adding Spring Versions JPA to a Non-Spring Boot Project
Whilst it is the easiest way to get started Spring Versions JPA does not require Spring Boot however. To a add
Spring Versions JPA to a non-Spring Boot project, add the spring-versions-jpa
dependency to the classpath:
dependencies {
...
compile("com.github.paulcwarren:spring-versions-jpa:${version}")
...
}
<dependencies>
...
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-versions-jpa</artifactId>
<version>${version}</version>
</dependency>
...
</dependencies>
2.4. Configuring Spring Versions JPA
Before they can be used, the locking and versioning capabilities must be configured. This is done by importing the
@Configuration
class org.springframework.versions.jpa.JpaLockingAndVersioningConfig
into your application and
initializing the database with the pre-defined sql scripts:
@Configuration
@Import(JpaLockingAndVersioningConfig.class)
public class LockingAndVersioningConfig {
@Value("/org/springframework/versions/jpa/schema-drop-hsqldb.sql") // (1)
private Resource dropVersionSchema;
@Value("/org/springframework/versions/jpa/schema-hsqldb.sql")
private Resource createVersionSchema;
@Bean
DataSourceInitializer datasourceInitializer(DataSource dataSource) {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropVersionSchema);
databasePopulator.addScript(createVersionSchema);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
-
Replace with schema resources for database. Supported databases; H2, HSQL, MySQL, Postgres and SQL Server.
Important
|
This step is unnecessary if you are using Spring Boot as it will automatically enable Spring Versions JPA
when you place com.github.paulcwarren:spring-versions-jpa-boot-starter on your application’s classpath and your application
is annotated as a @SpringBootApplication .
|
Configure the Spring Content Storage module by adding the package org.springframework.versions
to your
@Enable…Repositories package context.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Configuration
@EnableJpaRepositories(basePackages={"tests.versioning", "org.springframework.versions"}) // (1)
@EnableFilesystemStores(basePackages = "tests.versioning")
public static class ApplicationConfiguration {
}
}
-
This is required whether you are using Spring Boot, or not.
3. Locking And Versioning
3.1. Fundamentals
Locking and Versioning supports both optimistic and pessimistic locking strategies as well as versioning of Spring Data Entities and associated Spring Content Resources.
3.2. Optimistic Locking
Once configured (see Configuring Spring Versions JPA) optimistic locking semantics are automatically extended to Spring Content Resource’s associated with @Versioned Spring Data Entities.
As a result any ContentStore
operation that is attempted on an out-of-date Entity, that with an out-of-date @Version
value, will throw javax.persistence.OptimisticLockException
.
In addition, any ContentStore
operation on an up-to-date Entity’s content will cause the @Version value to be
incremented rendering all other copies out-of-date and forcing those to be re-fetched.
3.3. Pessimistic Locking and Versioning
To use a pessimistic locking strategy and versioning, the repository should be made to extend
LockingAndVersioningRepository
. For example:
public interface DocumentRepository extends JpaRepository<Document, Long>, LockingAndVersioningRepository {}
The LockingAndVersioningRepository
interface adds (and overrides) the following methods to a Repository
:
public interface LockingAndVersioningRepository<T, ID extends Serializable> {
/**
* Locks the entity and returns the updated entity (@Version and @LockOwner) attributes updated, otherwise
* returns null.
*
* @param <S> the type of entity
* @param entity the entity to be locked
* @return the locked entity
* @throws SecurityException if no authentication exists
*/
<S extends T> S lock(S entity);
/**
* Unlocks the entity and returns the updated entity (@Version and @LockOwner) attributes updated, otherwise
* returns null
*
* @param <S> the type of entity
* @param entity the entity to unlock
* @return the unlocked entity
* @throws LockOwnerException if the current principal is not the lock owner
* @throws SecurityException if no authentication exists
*/
<S extends T> S unlock(S entity);
/**
* Overridden implementation of save that enforces locking semantics
*
* @param <S> the type of entity
* @param entity the entity to save
* @return the saved entity
* @throws LockOwnerException if the current principal is not the lock owner
* @throws SecurityException if no authentication exists
*/
<S extends T> S save(S entity);
/**
* Creates and returns a new version of the entity. This new version becomes the latest version in the version
* list.
*
* This method requires the entity class to have a copy constructor used for cloning the new version instance.
*
* @param <S> the type of entity
* @param entity the entity to base the new versionWithEntity on
* @param info the version info
* @return the new versionWithEntity
* @throws LockingAndVersioningException if entity is not the latest
* @throws LockOwnerException if the current principal is not the lock owner
* @throws SecurityException if no authentication exists
*/
<S extends T> S version(S entity, VersionInfo info);
/**
* Returns the latest version of all entities. When extending LockingAndVersioningRepository this
* method would usually be preferred over CrudRepository's findAll that would find all versions
* of all entities.
*
* @param <S> the type of entity
* @return list of latest versionWithEntity entities
*/
<S extends T> List<S> findAllLatestVersion();
/**
* Returns a list of all versions for the given entity.
*
* @param <S> the type of entity
* @param entity the entity to find versions for
* @return list of entity versions
*/
<S extends T> List<S> findAllVersions(@Param("entity") S entity);
/**
* Deletes a given entity version. The entity must be the head of the version list.
*
* If the entity is locked the lock will be carried over to the previous version when
* it becomes the new head.
*
* @param <S> the type of entity
* @param entity the entity to delete
* @throws LockingAndVersioningException if entity is not the latest
* @throws LockOwnerException if the current principal is not the lock owner
* @throws SecurityException if no authentication exists
*/
<S extends T> void delete(S entity);
}
Lock and version information is recorded on each Entity instance by adding the following attribute annotations to the
Entity class that is the subject of LockingAndVersioningRepository
:
Annotation | Type | Required | Description |
---|---|---|---|
AncestorId |
@Id |
Yes |
The previous version in the set. The type of this field will be dictated by the entity’s @Id field. |
AncestorRootId |
@Id |
Yes |
The first version in the set. The type of this field will be dictated by the entity’s @Id field. |
SuccessorId |
@Id |
Yes |
The next version in the set. The type of this field will be dictated by the entity’s @Id field. |
LockOwner |
String |
No |
The name of the lock owner. |
VersionNumber |
String |
No |
The entity’s version number. |
VersionLabel |
String |
No |
The entity’s version label. |
public class Document {
@Id
@GeneratedValue
private Long id;
@Version
private Long vstamp;
@ContentId
private UUID contentId;
@ContentLength
private int contentLen;
@MimeType
private String mimeType;
@LockOwner
private String lockOwner;
@AncestorId
private Long ancestorId;
@AncestorRootId
private Long ancestralRootId;
@SuccessorId
private Long successorId;
@VersionNumber
private String version;
@VersionLabel
private String label;
}
4. REST API
4.1. The Lock Resource
When a repository extending LockingAndVersioningRepository
is exported a lock endpoint
will be available at the /{repository}/{id}/lock
URI.
curl -X PUT http://localhost:8080/docs/1234/lock
curl -X DELETE http://localhost:8080/docs/1234/lock
4.1.1. Supported HTTP Methods
The Lock resource supports PUT
and DELETE
. All other HTTP methods will cause a
405 Method Not Allowed
.
PUT
Acquires a pessimistic lock on the resource.
DELETE
If held, releases the pessimistic lock on the resource.
Supported media types
Not applicable.
4.2. The Version Resource
When a repository extending LockingAndVersioningRepository
is exported a version
endpoint will be available at the /{repository}/{id}/version
URI.
curl -X PUT http://localhost:8080/docs/1234/version -d '{"number": "1.1", "label": "a minor change"}'
4.2.1. Supported HTTP Methods
The version resource supports PUT
. All other HTTP methods will cause a 405 Method
Not Allowed
.
PUT
Creates a new version of the entity.
Supported media types
application/json
4.3. The FindAllVersionsLatest Resource
When a repository extending LockingAndVersioningRepository
is exported a findAllVersionsLatest
endpoint will be available at the /{repository}/findAllVersionsLatest
URI.
curl -X GET http://localhost:8080/docs/findAllVersionsLatest
4.3.1. Supported HTTP Methods
The version resource supports GET
. All other HTTP methods will cause a 405 Method
Not Allowed
.
GET
Returns the latest version of all entities.
Supported media types
Not applicable.
4.4. The FindAllVersions Resource
When a repository extending LockingAndVersioningRepository
is exported a findAllLatestVersions
endpoint will be available at the /{repository}/{id}/findAllVersions
URI.
curl -X GET http://localhost:8080/docs/1234/findAllVersions
4.4.1. Supported HTTP Methods
The version resource supports GET
. All other HTTP methods will cause a 405 Method
Not Allowed
.
GET
Returns all versions of the given entity.
Supported media types
Not applicable.