TL;DR This post walks through the implementation details of spring-data-xml
. This library provides support when accessing XML databases like eXist-db with Java and XQuery. The code is available at GitHub: datenkollektiv/spring-data-xml.
Last week I had the opportunity to work with the NoSQL document database eXist.
After an amazing session, tightly packed with a mix of XQuery basics and pearls I started to implement a simple Java xqj-example
to get a better feeling for the rough edges of the new technology.
The moment I switched from querying simple integer sequences to real XML documents I resisted the initial attempt to copy large parts of the JUnit setup to a new test class. That was the moment I had a déjà vu: Don't repeat the DAO!.
I missed the "familiar and consistent, Spring-based programming model for data access" I got used to while working with Spring Data MongoDB.
It's a pity that eXist is missing from the list of data stores supported by Spring Data.
There is a promising project Spring XMLDB, but...Last Update: 2013-03-07
and the source seems to be available via CVS, only.
At that point, we had a quick kick-off and decided to start a draft implementation based on Spring Data.
In addition to the basic CRUD operations available from CrudRepository
we decided to offer a simple way to allow direct execution of XQueries:
import org.springframework.data.repository.CrudRepository;
interface XQJRepository<T, ID> extends CrudRepository<T, ID> {
Stream<T> execute(String xquery);
}
The following Spring integration tests demonstrates the simple use-case of an empty XQuery sequence:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ExistConfiguration.class)
class SimpleExistXQJRepositoryTests {
@Inject
private SimpleXQJRepository<Integer> existRepository;
@Test
void emptySequence() {
Stream<Integer> result = existRepository.execute("()");
assertEquals(0, result.count());
}
}
Let's look under the hood. The SimpleXQJRepository main purpose is to delegate the database work to and convert the Java entities to XML documents and back.
public class SimpleXQJRepository<T> implements XQJRepository<T> {
private final XQJOperations XQJOperations;
...
@Override
public Stream<T> execute(String xquery, VarBinder... varBinder) {
return XQJOperations.execute(xquery, varBinder).map(this::toEntity);
}
...
@Override
public <S extends T> S save(S entity) {
String id = this.entityInformation.getId(entity);
xqjOperations.store(collectionName, id, toDocumentConverter.convert(entity));
return entity;
}
...
@Override
public Optional<T> findById(String id) {
return xqjOperations.load(collectionName, id).findFirst().map(this::toEntity);
}
...
}
The methods of XQJOperations
either return void
or a Java Stream
.
interface XQJOperations {
Stream<XQItem> execute(String xquery, VarBinder... varBinder);
void store(String collectionName, String documentName, Document documentToSave);
Stream<XQItem> load(String collectionName, String documentName);
void delete(String collectionName, String documentName);
...
}
The actual implementation of the CRUD operations are realized (as you might have already guessed) via XQuery:
@Override
public void store(String collectionName, String documentName, Document documentToSave) {
String query = "declare variable $id external; " +
"declare variable $doc external; " +
"xmldb:store('" + collectionName + "', $id, $doc)";
execute(query,
expression -> expression.bindString(QName.valueOf("id"), "$documentName.xml", null),
expression -> expression.bindNode(QName.valueOf("doc"), documentToSave.getDocumentElement(), null));
}
Conclusion
This library closes the Gap between Spring Developers and XQuery experts.
- Without any XQuery know-how a Spring Developer can run basic CRUD operations against eXist-db.
- An XQuery expert get's an easy to use Java abstraction plus the possibility to use plain XQuery where needed.
Despite being far from production-ready, we decided to polished the initial code and publish spring-data-xml
.
The code is available at GitHub: datenkollektiv/spring-data-xml.
Happy coding with XQuery and the Spring Framework...