Friday, 15 February 2013

MarkLogic Java client

MarkLogic is a NoSQL document database that allows to handle XML efficiently.

Let's take a look how to setup MarkLogic database instance on a local machine and write a simple application that will perform CRUD and searching operations on XML documents.

First of all, we will need to download MarkLogic server (account is required).
Installation and starting procedures are described here - they're pretty straightforward.
When server is already started, we need to create a new database with REST API instance and a user having write access - follow this link.
REST is used by the Java client as a communication protocol but we can also use it manually in our browser.

Once database is created we can start writing the client code.

Let's start with setting up required dependencies:
<dependencies>
<dependency>
<groupId>com.marklogic</groupId>
<artifactId>client-api-java</artifactId>
<version>1.0-2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlmatchers</groupId>
<artifactId>xml-matchers</artifactId>
<version>0.10</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>dmc</id>
<name>MarkLogic Developer Community</name>
<url>http://developer.marklogic.com/maven2/</url>
</repository>
</repositories>
To use MarkLogic Java client we need to specify MarkLogic maven repository. We'll also use xml-matchers library to compare created XML documents.

Here's an example of XML document that will be representing person:
<person>
<name>Robin van Persie</name>
<age>29</age>
</person>
view raw person.xml hosted with ❤ by GitHub
Let's define an interface that will allow some simple CRUD and searching operations:
public interface PersonRepository {
void addPerson(String id, String person);
String getPerson(String id);
void removePerson(String id);
List<String> findByName(String name);
}
The sample implementation could look like:
public class MarkLogicPersonRepository implements PersonRepository {
private XMLDocumentManager documentManager;
private QueryManager queryManager;
public MarkLogicPersonRepository(XMLDocumentManager documentManager, QueryManager queryManager) {
this.documentManager = documentManager;
this.queryManager = queryManager;
}
public void addPerson(String id, String person) {
StringHandle handle = new StringHandle(person);
documentManager.write(id, handle);
}
public String getPerson(String personId) {
StringHandle handle = new StringHandle();
documentManager.read(personId, handle);
return handle.get();
}
public void removePerson(String personId) {
documentManager.delete(personId);
}
public List<String> findByName(String name) {
KeyValueQueryDefinition query = queryManager.newKeyValueDefinition();
queryManager.setPageLength(10); // LIMIT RESULT
query.put(queryManager.newElementLocator(new QName("name")), name);
SearchHandle resultsHandle = new SearchHandle();
queryManager.search(query, resultsHandle);
return getResultListFor(resultsHandle);
}
private List<String> getResultListFor(SearchHandle resultsHandle) {
List<String> result = new ArrayList<String>();
for (MatchDocumentSummary summary : resultsHandle.getMatchResults()) {
StringHandle content = new StringHandle();
documentManager.read(summary.getUri(), content);
result.add(content.get());
}
return result;
}
}
In order to perform CRUD operations we need to have DocumentManager object (in our case XMLDocumentManager as we're handling XML). It is thread-safe object (can be shared across multiple threads) and its usage is quite intuitive. Each operation needs a specific handle object - as our interface declared String, we'll use StringHandle that is being populated with the result by manager.

To do query operations QueryManager is required. There are many types of queries, we'll use searching by element value.
It's a little bit more complicated than simple CRUD operations - SearchHandle object is initially populated by running the query on query manager.
Then we're iterating over each SearchHandle's result represented by MatchDocumentSummary object and retrieve its URI, that is given to DocumentManager that reads full document. 
Please note that the number of returned documents has been limited to 10.

The integration test (it requires running MarkLogic server):
public class MarkLogicPersonRepositoryIntegrationTest {
private static final String NAME = "Robin van Persie";
private static final String SAMPLE_PERSON = "<person><name>" + NAME + "</name><age>29</age></person>";
private MarkLogicPersonRepository personRepository;
@Before
public void setUp() {
DatabaseClient client = DatabaseClientFactory.newClient("localhost", 8003, "rest-writer", "x", DIGEST);
personRepository = new MarkLogicPersonRepository(client.newXMLDocumentManager(), client.newQueryManager());
}
@Test
public void shouldAddAndRetrievePersonAsXmlDocument() {
// given
String personId = randomUUID().toString();
personRepository.addPerson(personId, SAMPLE_PERSON);
// when
String result = personRepository.getPerson(personId);
// then
assertThat(the(result), isEquivalentTo(the(SAMPLE_PERSON)));
}
@Test(expected = ResourceNotFoundException.class)
public void shouldRemovePerson() {
// given
String personId = randomUUID().toString();
personRepository.addPerson(personId, SAMPLE_PERSON);
// when
personRepository.removePerson(personId);
// then
personRepository.getPerson(personId);
}
@Test
public void shouldFindPersonByName() {
// given
personRepository.addPerson(randomUUID().toString(), SAMPLE_PERSON);
// when
List<String> result = personRepository.findByName(NAME);
// then
assertThat(the(result.get(0)), isEquivalentTo(the(SAMPLE_PERSON)));
}
}

In setUp() method DatabaseClientFactory creates DatabaseClient based on the given credentials (they need to be the same as the one used during setting up the database).
Once we have the client we can create managers needed by the implementation.

One thing to note: when manager cannot find the document it throws ResourceNotFoundException.

The whole project can be found at github.


Wednesday, 6 February 2013

Spring Data JPA sample project

In previous post I showed how to setup a sample project with JPA and Hibernate.
Even though it wasn't difficult, there was a main disadvantage - we need to do a lot of coding around our DAO objects even if we want only simple operations.
Spring Data JPA helps us reduce data access coding.

Let's start with defining dependencies:

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.9.Final</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.0.RELEASE</version>
<scope>test</scope>
</dependency>
Compared to previous project there are more dependencies because of Spring. spring-test is needed to allow our test use the spring context. And this time we're going o use HSQLDB.

pesistence.xml  is much smaller because the persistence configuration will be defined in spring context.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="springJpaPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>pl.mjedynak.model.Person</class>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
view raw persistence.xml hosted with ❤ by GitHub
Please note that Hibernate is still the persistence provider. The Person class is exactly the same as before.

The context is defined as:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<context:component-scan base-package="pl.mjedynak"/>
<jpa:repositories base-package="pl.mjedynak.repository"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:file:sampledb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceXmlLocation" value="persistence.xml"/>
<property name="persistenceUnitName" value="springJpaPersistenceUnit"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
We need to specify dataSourceentityManagerFactory and transactionManager.
The pl.mjedynak package will be scanned by spring to do autowiring.
Spring Data JPA introduces the concept of repository which is higher level of abstraction than the DAO.
In our context we define a package with the repositories.

The only thing to do to be able to manage our Person class is to create an interface that will extend CrudRepository - it gives us basic operations like save, find, delete etc.


public interface PersonRepository extends CrudRepository<Person, Long> {
}
We can of course expand it with more sophisticated methods if we want.

The integration test is almost the same as before, except it needs to use spring context.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-context.xml")
public class PersonRepositoryIntegrationTest {
@Autowired
private PersonRepository personRepository;
@Test
public void shouldFindPreviouslySavedPerson() {
// given
Integer age = 22;
String name = "Charlie";
Person person = aPerson().
withAge(age).
withName(name).build();
personRepository.save(person);
// when
List<Person> result = (List<Person>) personRepository.findAll();
// then
assertThat(result, hasSize(1));
Person foundPerson = result.get(0);
assertThat(foundPerson.getAge(), is(age));
assertThat(foundPerson.getName(), is(name));
}
}


The whole project can be found at: https://github.com/mjedynak/spring-data-jpa-example