Sunday, December 20, 2009

Hibernate Search + Apache Lucene (tricks)

In this post I would like to show a few Hibernate Search tricks which could be useful in some cases (at least, I use them quite often):

  • how to get all indexed properties for entity?
  • final Class< ? > entityClass = ...;

    final FullTextSession fullTextSession = Search.getFullTextSession(
    sessionFactory.getCurrentSession() );

    final SearchFactory searchFactory = fullTextSession.getSearchFactory();
    final ReaderProvider readerProvider = searchFactory.getReaderProvider();

    final Collection< String > names = new ArrayList< String >();
    IndexReader indexReader = null;

    try {
    indexReader = readerProvider.openReader( searchFactory.getDirectoryProviders( clazz ) );
    for( Object obj: indexReader.getFieldNames( FieldOption.INDEXED ) ) {
    if( obj instanceof String ) {
    String name = ( String )obj;
    names.add( name );
    }
    }
    } finally {
    if( indexReader != null ) {
    readerProvider.closeReader( indexReader );
    }
    }
  • how to reindex whole existing database?
  • final FullTextSession fullTextSession = Search.getFullTextSession(
    sessionFactory.getCurrentSession() );

    final Set< ? > entitites = new HashSet< ? >();
    // Get all indexed persistent entities from Hibernate session factory
    Iterator< ? > iterator = sessionFactory.getConfiguration().getClassMappings();
    while( iterator.hasNext() ) {
    Object obj = iterator.next();
    if( obj instanceof PersistentClass ) {
    PersistentClass persistentClass = ( PersistentClass )obj;
    try {
    Class< ? > clazz = Class.forName( persistentClass.getClassName() );
    if( clazz.getAnnotation( Indexed.class ) != null ) {
    entitites.add( clazz );
    }
    }
    } catch( ClassNotFoundException ex ) {
    ex.printStackTrace();
    }
    }

    for( Class< ? > entityClass: entitites ) {
    fullTextSession.purgeAll( entityClass );
    fullTextSession.flushToIndexes();

    for( Object entity: session.createCriteria( entityClass ).list() ) {
    fullTextSession.index( entity );
    }
    }

    fullTextSession.flushToIndexes();
    fullTextSession.getSearchFactory().optimize();

Monday, December 7, 2009

Distributed Hibernate Search with Apache Tomcat 6, ActiveMQ and Spring

Today I would like to share my experience with configuring Hibernate Search in master/slave(s) deployment using Apache ActiveMQ, Spring and running all this stuff inside Apache Tomcat 6 container.

How it works:
- Hibernate Search supports distributed configuration using JMS back-end and master / slave(s) index
- master server exposes index over network share (NFS,...)
- slave(s) on regular base replicate this master copy to own local copies

Used version:
- Apache Tomcat 6.0.20
- Hibernate Search 3.1.1 GA
- Apache ActiveMQ 5.3.0
- Spring 2.5.6
- XBean-Spring 3.6

Master Index Configuration
Master configuration is a little bit complicated. Here are configuration-specific properties:

${local.index.dir} - directory to store master index
${master.index.dir} - directory to copy master index to, it's shared network location for replication with slave(s)

First of all, for simplification, let's run ActiveMQ broker on the same host. For this purpose we could use simple embedded broker configuration placed into WEB-INF/activemq.xml:

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd">

<amq:broker brokerName="HibernateSearchBroker">
<amq:managementContext>
<amq:managementContext createConnector="false"/>
</amq:managementContext>

<amq:transportConnectors>

</amq:transportConnectors>
</amq:broker>

<amq:queue name="queue/hibernatesearch" physicalName="hibernateSearchQueue" />
</beans>
Then we need to configure JNDI resources (JMS Connection Factory and Queue) through web application META-INF/context.xml file (Tomcat-specific):
...
<!-- ActiveMQ ConnectionFactory -->
<Resource
name="jms/ConnectionFactory"
auth="Container"
type="org.apache.activemq.ActiveMQConnectionFactory"
description="JMS Connection Factory"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
brokerURL="tcp://0.0.0.0:61616?trace=true"
brokerName="HibernateSearchBroker" />

<!-- ActiveMQ HibernateSearch queue -->
<Resource
name="queue/hibernatesearch"
auth="Container" type="org.apache.activemq.command.ActiveMQQueue"
description="Hibernate search queue"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="hibernateSearchQueue" />
...
Next step is configuration of the Hibernate Search itself through Hibernate configuration file (hibernate.cfg.xml):
<property name="hibernate.search.default.directory_provider">org.hibernate.search.store.FSMasterDirectoryProvider</property>
<property name="hibernate.search.default.indexBase">${local.index.dir}</property>
<property name="hibernate.search.default.sourceBase">${master.index.dir}</property>
<property name="hibernate.search.default.refresh">60</property>
One important difference between master and slave codebase: master implementation must include subclass of AbstractJMSHibernateSearchController as message listener. For example,

import javax.jms.MessageListener;

import org.hibernate.Session;
import org.hibernate.search.backend.impl.jms.AbstractJMSHibernateSearchController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class JMSHibernateSearchController
extends AbstractJMSHibernateSearchController
implements MessageListener {

@Override
protected void cleanSessionIfNeeded(Session session) {
// clean session here ...
}

@Override
protected Session getSession() {
// return new session here ...
}
}

Finally, let's wrap it up inside Spring configuration file applicationContext.xml:
<bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
<property name="config" value="WEB-INF/activemq.xml" />
<property name="start" value="true" />
</bean>

<bean name="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jms/ConnectionFactory" />
</bean>

<bean name="jmsHibernateSearchQueue" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/queue/hibernatesearch" />

<bean id="hibernateSearchController" class="<your implementation of AbstractJMSHibernateSearchController>" />

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" depends-on="broker">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="destination" ref="jmsHibernateSearchQueue"/>
<property name="messageListener" ref="hibernateSearchController" />
</bean>
With those configurations in place Hibernate Search master is ready to run.


Slave Index Configuration
Slave(s) configuration is much simple. Here are configuration-specific properties:

${server} - server which runs ActiveMQ broker
${local.index.dir} - directory to store local index (master copy)
${master.index.share} - mounted network share with master index

First of all, we need to configure JNDI resources (JMS Connection Factory and Queue) through web application META-INF/context.xml file (Tomcat-specific):
...
<!-- ActiveMQ ConnectionFactory -->
<Resource
name="jms/ConnectionFactory"
auth="Container"
type="org.apache.activemq.ActiveMQConnectionFactory"
description="JMS Connection Factory"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
brokerURL="tcp://${server}:61616?trace=true"
brokerName="HibernateSearchBroker" />

<!-- ActiveMQ HibernateSearch queue -->
<Resource
name="queue/hibernatesearch"
auth="Container" type="org.apache.activemq.command.ActiveMQQueue"
description="Hibernate search queue"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="hibernateSearchQueue" />
...
Then we have to configure Hibernate Search itself through Hibernate configuration file (hibernate.cfg.xml):
<property name="hibernate.search.default.directory_provider">org.hibernate.search.store.FSSlaveDirectoryProvider</property>
<property name="hibernate.search.default.indexBase">${local.index.dir}</property>
<property name="hibernate.search.default.sourceBase">${master.index.share}</property>
<property name="hibernate.search.default.refresh">60</property>
<property name="hibernate.search.worker.backend">jms</property>
<property name="hibernate.search.worker.jms.connection_factory">java:comp/env/jms/ConnectionFactory</property>
<property name="hibernate.search.worker.jms.queue">java:comp/env/queue/hibernatesearch</property>
<property name="hibernate.search.worker.jndi.java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</property>
And ... that's it!

Few additional words about testing all this stuff with JUnit. Only problem is JNDI which could be mocked up with Spring JNDI templates. For example:

<bean name="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jms/ConnectionFactory" />
<property name="jndiTemplate">
<bean class="org.springframework.mock.jndi.ExpectedLookupTemplate">
<constructor-arg index="0" value="java:comp/env/jms/ConnectionFactory" />
<constructor-arg index="1">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://0.0.0.0:61616</value>
</property>
</bean>
</constructor-arg>
</bean>
</property>
</bean>

<bean name="jmsHibernateSearchQueue" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/queue/hibernatesearch" />
<property name="jndiTemplate">
<bean class="org.springframework.mock.jndi.ExpectedLookupTemplate">
<constructor-arg index="0" value="java:comp/env/queue/hibernatesearch" />
<constructor-arg index="1">
<bean id="jmsHibernateSearchQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="queue/hibernateSearchQueue"/>
</bean>
</constructor-arg>
</bean>
</property>
</bean>
...

Tuesday, November 17, 2009

Freemarker ... powerful templating in Java

In most applications there is a point when you as developer need to create a template (for e-mail, notification, message, ...). Basically, the very simple template is just a string with some parameters like "Hello, ${name}" which have to be substituted at run-time. In many cases simple replace(...) is enough, but what if you need some logic inside template (expressions, flow control, function calls, ... )? For those who need powerful templates in Java projects I would like to recommend Freemarker - Java Template Engine Library.

Freemarker, in fact, has very good documentation. It's easy to start using it without any specific knowledge. In this post I would like to share a few issues which I found very useful. Let's start with very basic e-mail message template.
Hello ${user.fistName} ${user.lastName}!
Welcome to the world of templates!
In this template we assume that some object (bean) user with public properties firstName and lastName must be passed to template engine in order to build e-mail message. Let's save this template to message.ftl file (.ftl is default file extension for Freemarker templates). To process this template we need few simple steps:
Configuration cfg = new Configuration();

// Specify the data source where the template files come from.
cfg.setDirectoryForTemplateLoading( new File("/where/you/store/templates"));
// Specify how templates will see the data-model
cfg.setObjectWrapper( ObjectWrapper.DEFAULT_WRAPPER );

final Map< String, Object > context = new HashMap< String, Object >();
final User user = new User( "First Name", "Last Name" );
context.put( "user", user );

final Template t = cfg.getTemplate( "message.ftl");
final Environment env = t.createProcessingEnvironment( context, writer );
env.process();
It's quite clear what the code does: create configuration, create context (simple map) and then process template (stored in file). When this code snippet finishes, writer will contain fully processed template. The tricky part here is such code:
// Specify how templates will see the data-model
cfg.setObjectWrapper( ObjectWrapper.DEFAULT_WRAPPER );
We will see what it means later but for know it's enough to say that it has influence on how Freemarker processes objects passed to template (via context).

We are done with e-mail message but what if you need to construct e-mail subject in template as well and return it back to callee? Freemarker allows to do that using environment. Let's modify the template a little bit:
<#assign subject="Congratulations ${user.fistName} ${user.lastName}!" />
Hello ${user.fistName} ${user.lastName}!
Welcome to the world of templates!
So in this template we create internal variable subject which we will use later in code. We need just a few additional steps:
...
env.process();

String subject = "";
final TemplateModel subjectModel = env.getVariable( "subject" );
if( subjectModel instanceof TemplateScalarModel ) {
subject = ( ( TemplateScalarModel )subjectModel ).getAsString();
}
Next interesting question is about object properties / functions / static functions which you could use inside template. Basically, with ObjectWrapper.DEFAULT_WRAPPER you are free to use any object property which comply with Java Beans specification. In case you need, for example, calls like getClass(), you need ObjectWrapper.BEANS_WRAPPER.
cfg.setObjectWrapper( ObjectWrapper.BEANS_WRAPPER );  
Using static functions / properties could be done by means of static models. Let say you have a class with static my.package.StaticUtil. To use static members of this class inside the template we need to use code like:
final BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
final TemplateHashModel staticModels = wrapper.getStaticModels();

final TemplateHashModel staticUtil =( TemplateHashModel )staticModels
.get( "my.package.StaticUtil" );

context.put( "StaticUtil", staticUtil );
Later in template you can use constructions like ${StaticUtil.someStaticFunction()} to access static members.

That's it for now. One thing is worthwhile to say is that Freemarker has good integration with Eclipse via JBoss Tools.

Saturday, July 11, 2009

Hibernate Search + Apache Lucene (continued)

Let's continue very useful and interesting topic about Hibernate Search and Apache Lucene. In my last post we discovered new annotations which allow persistent entities to be "searchable". In this post I am going to show how to query such entities with various search criteria.

Once entities are annotated, they could be queried with regular Apache Lucene query. Simple flow looks like this:
Session session = sessionFactory.getNewSession();
FullTextSession fullTextSession = Search.getFullTextSession( session );

try {
fullTextSession.beginTransaction();

// query - regular Lucene query, f.e. "author=King"
// entities - list of entity classes, like Entity1.class, Entity2.class, ...

FullTextQuery q = fullTextSession
.createFullTextQuery( query, entities );

// Get a collection of matched entities
Collection< Object > results = q.list();

fullTextSession.getTransaction().commit();
} catch (Exception ex ) {
fullTextSession.getTransaction().rollback();
throw ex;
} finally {
session.close();
}
That's how Hibernate Search makes it simple. The only difference from Hibernate's model is FullTextSession usage. Here are few simple details about how Hibernate Search works on top of Apache Lucene:

  • for each entity class Apache Lucene creates own index

  • Hibernate Search adds a few additional properties (like persistent entity Id and Type) to associate Apache Lucene document and Hibernate entity

  • for each query Hibernate Search uses query rewrite to ensure only indexed properties of a particular entity are present in query

  • entity's (re)indexing occurs when database transaction commits (but Hibernate Search allows you manually (re)index entity)

Hibernate Search also allows you to use very powerful features like filters (with parameters) and sorting. Filters are extremely useful in case of security rules or search with search results implementation.
...
FullTextQuery q = fullTextSession
.createFullTextQuery( query, entities );
.setSort( new Sort(
new SortField[] {
new SortField( "name", false )
}
)
);

// Enable filter with name 'filterName' and parameter 'value' set to value
Object value = ...;
q.enableFullTextFilter( "filterName" )
.setParameter( "value", value );

// Get a collection of matched entities
Collection< Object > results = q.list();
...
It's very simple to integrate and use Hibernate Search into existing applications. It most cases I found integration seamless.

There are a bunch of interesting issues that might occur:

  • how to get all indexed properties for entity?

  • how to reindex whole existing database?

  • how to deploy master/slave(s) configuration?

For those I am going to create dedicated post because an implementation is a little bit tricky (but in general not complicated at all).

Sunday, May 31, 2009

Hibernate Search + Apache Lucene

It worthwhile to say that every Java developer knows about Apache Lucene project. No doubts, the best open source search engine! But today it's not about Apache Lucene, it's about Hibernate Search project, just another excellent library from Hibernate team.

So, what if you need some search capabilities in your application? For sure, it depends on requirements, but let's consider Java application which uses database as back-end and Hibernate as ORM framework. I would suggest a few ways to do search in this case:
1) Implement own search framework based on full text search capabilities of your database server (vendor-locked solution).
2) Implement own search framework with SQL/HQL queries. Works well for very simple scenarios. but is not enough in most cases.
3) Use Hibernate Search (and Apache Lucene). As powerful as Apache Lucene and as flexible as Hibernate framework. The most preferable solution.

From developer point of view, integration of the Hibernate Search is just a matter of additional annotations together with a few configuration lines in hibernate.cfg.xml. Let's start with last one.

<property name="hibernate.search.default.directory_provider">
org.hibernate.search.store.RAMDirectoryProvider
</property>

That tells Hibernate Search to store all indexes in RAM (don't do that on production system!). And ... that's it!

Now let's look on typical persistent entity and ... how to make it searchable!
@Entity
@Indexed
public class User {
private Long id;
private String firstName;
private String lastName;

@Id
@GeneratedValue( strategy = GenerationType.AUTO )
@Column( name = "UserID" )
@DocumentId
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}

@Basic
@Column( name = "FirstName", length = 50 )
@Field( index = Index.UN_TOKENIZED )
public String getFirstName() {
return firstName;
}

public void setFirstName( String firstName ) {
this.firstName = firstName;
}

@Basic
@Column( name = "LastName", length = 50 )
@Field( index = Index.UN_TOKENIZED )
public String getLastName() {
return lastName;
}

public void setLastName( String lastName ) {
this.lastName = lastName;
}
}

A few annotations in bold (@Indexed, @Field, @DocumentId) make all the magic. Hibernate Search provides additional listeners which take care on entity indexing (Lucene documents creation/deletion). In next post I will explain how to search stored entities by specific criteria. For now, I would mention a great book Hibernate Search in Action which perfectly explains all details.

Wednesday, April 1, 2009

Testing Spring applications

In my last post we talked about Spring. As a test infected developer, my commitment is to test as much as I can to be sure in quality of my code. So, is it feasible to test (and how?) applications which heavily use Spring? The answers are: yes and very easy with help of Spring testing framework. We will consider a few basic scenarios with testing simple Spring beans and then touch a little bit context-related issues.

So. let's start with Spring beans under the test. I prefer to use Java 5 and annotations to publish regular POJOs as Spring beans and declare dependency injection between beans as well. The bean will be MessageService as in code snippet below:

package com.krankenhouse.services;

@Component
public class MessageService {
@Autowired private ITransport impl;
@Autowired private ILogger logger;

public void send( final String message ) {
logger.log( message );
impl.send( message );
}

public String receive() {
final String message = impl.receive();
logger.log( message );
return message;
}
}

This bean is pretty simple and just demonstrates two important concepts: exporting POJO as Spring bean (@Component) and using dependency injection (@Autowired). Before diving into how to test it, let's create applicationContext.xml (placed inside WEB-INF folder) file.
<?xml version="1.0" encoding="UTF-8"?>
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:annotation-config/>
<context:component-scan base-package="com.krankenhouse.services" />
</beans>

OK, now we're ready to write our first test, named MessageServiceTestCase. There are basically several ways to do that, depending which features do you need. The first one looks like:

package com.krankenhouse.services;

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = {
"classpath:/META-INF/applicationContext.xml"
}
)
public class MessageServiceTestCase {
@Autowired private MessageService messageService;

public void testSend() {
messageService.send( "Message" );
}
}
And that's it! Spring takes care about context initialization, beans creation, dependency injection, etc. If you need application context inside the test, then a little bit different approach could be used (by inheriting test class from AbstractJUnit4SpringContextTests class):
package com.krankenhouse.services;

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = {
"classpath:/META-INF/applicationContext.xml"
}
)
public class MessageServiceTestCase
extends AbstractJUnit4SpringContextTests {
public void testSend() {
MessageService messageService =
( MessageService )applicationContext
.getBean( "messageService");
messageService.send( "Message" );
}
}
There's one important issue concerning Spring context: only one context will be created for each test class instance (not test method!). If you would like to recreate context for each single test method, you have to adorn these ones with @DirtiesContext annotation. Unfortunately, there is no class-level annotation for that up to 2.5.x versions.

In spring with Spring ...

Today I'm going to talk a little bit about Spring. Unfortunately, I didn't use heavily this great framework so far. And that's my fault. Spring is definitely worth to learn and use. Beforehand I would like to recommend an excellent book Pro Spring 2.5 which gave me good start in using Spring.

So, why Spring? Over the years I've been developing software pursuing the same principles: good and clever design, simplicity (keep it simple but not simpler), and easy configuration. In our object-oriented programming era it's all about objects. How simple are relations (dependencies) between objects, how loosely coupled are modules/subsystems/objects, how painful is deployment? And are you able to cover with unit tests each piece of critical functionality (you definitely must do that)?

Spring definitely helps with solving all those issues. Its core is dependency injection container which allows to inject dependencies in declarative manner (either in XML configuration or using Java 5 annotations). And a bunch of additional modules provides everything you need from AOP to JUnit support in order to develop high-quality JEE applications.

Monday, February 9, 2009

Testing persistence layer

As a supporter of TDD I prefer to test as much as it's necessary to be sure the code works properly (but I'm not a paranoid in that). With respect to this, testing persistence layer takes a very important place in my TDD practices.

By "testing persistence layer" I basically mean two things:
- testing that ORM mapping (Hibernate) is valid
- testing that application works well with rich test data sets

Using persistence layer tests allows to verify corner cases as well as application behavior in case of different database faults. Even more, development with testing in mind allows to design low coupled and well layered application architecture.

To start with, let's reuse two classes Employer and Employee from previous post and develop simple test cases for them. Thanks to Hibernate, we're completely decoupled from underlying database so we can create any suitable testing configuration. And thanks to HSQLDB project we're able to easily create in-memory database and relegate standalone database server deployment. For sure, we'll create test cases based on JUnit framework.

First, let's create abstract test case encapsulating all configuration in it. Basically, we have to configure Hibernate, add annotated classes, and create session factory.


import org.hibernate.Session;
import org.hibernate.dialect.HSQLDialect;
import org.hsqldb.jdbcDriver;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Environment;

public abstract class AbstractPersistentTestCase {
private AnnotationConfiguration configuration;
private SessionFactory sessionFactory;

@Before
protected void setUp() throws Exception {
configuration = new AnnotationConfiguration();
configuration.setProperty( Environment.DIALECT,
HSQLDialect.class.getName() );
configuration.setProperty( Environment.DRIVER,
jdbcDriver.class.getName() );
configuration.setProperty( Environment.URL,
"jdbc:hsqldb:mem:testdb" );
configuration.setProperty( Environment.CURRENT_SESSION_CONTEXT_CLASS,
"org.hibernate.context.ThreadLocalSessionContext" );
configuration.setProperty( Environment.HBM2DDL_AUTO,
"create-drop" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE,
"0" );
configuration.setProperty( Environment.SHOW_SQL,
"false" );
configuration.setProperty( Environment.FORMAT_SQL,
"true" );

configuration.addAnnotatedClass( Employee.class )
.addAnnotatedClass( Employer.class );

sessionFactory = configuration.buildSessionFactory();
}

@After
public void tearDown() {
SchemaExport schemaExport = new SchemaExport( configuration );
schemaExport.drop( true, true );
}

protected Session getSession() {
return sessionFactory.getCurrentSession( );
}
}

Then, let's develop a simple test case for persisting Employer and Employee classes. There's one important note here. Those test methods should be executed within transaction boundary (for example, with help of AspectJ). Otherwise, the HibernateException comes up.


public class PersistentTestCase extends AbstractPersistentTestCase {
@Test
public void testSaveEmployer() {
Employer employer = new Employer();
getSession().save( employer );

Assert.assertNotNull( employer.getId() );
}

@Test
public void testSaveEmployee() {
Employer employer = new Employer();
getSession().save( employer );

Employee employee = new Employee();
employee.setEmployer( employer );
getSession().save( employee );

Assert.assertNotNull( employee.getId() );
}
}

Here I covered very simple scenario. Just to give the idea. Next step will be preparing test data sets with help of DbUnit. It allows to prepare data in XML format (among others) and upload this file into database. Let's prepare quite simple XML file 'dataset.xml':

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<employer id="1" name="IBM" email="ibm@ibm.com" />
<employer id="2" name="Microsoft" email="microsoft@microsoft.com" />
</dataset>
And that's it! DbUnit will automatically map XML elements to tables and XML attributes to columns! Awesome! Let's develop the test case using DbUnit and newly created data set.


public class DatasetTestCase extends AbstractPersistentTestCase {
@Before
protected void setUp() throws Exception {
super.setUp();
uploadTestDataset();
}

private void uploadTestDataset() throws Exception
{
final Session session = getSession();
Transaction t = session.beginTransaction( );

try {
IDataSet dataSet = new FlatXmlDataSet(
new FileInputStream(
new File(
getClass().getResource( "/dataset.xml" ).toURI()
)
)
);

IDatabaseConnection connection =
new DatabaseConnection( session.connection() );
DatabaseOperation.CLEAN_INSERT.execute( connection,
dataSet );

t.commit( );
} finally {
if ( t.isActive( ) ) {
t.rollback( );
}
}
}

@Test
public void testLoadEmployer() {
Employer employer = ( Employer )getSession().load(
Employer.class, new Integer( 1 ) );

Assert.assertEquals( "IBM", employer.getName() );
Assert.assertEquals( "ibm@ibm.com", employer.getEmail() );
}
}

This scenario is very simple as well. But it's just a foundation ... Developing comprehensive datasets and testing application business logic against them is great step in achieving high product quality. Those techniques allow to fully cover very complex flows with tests and detect errors early.

The only problem with that is ... maintenance. It's particularly true for projects in active development. Changes in business logic lead to test failures (in most cases). Supporting huge tests code base can me nightmare. So ... I'm always looking for balanced solution.

Friday, January 30, 2009

Using Criteria API in Hibernate

Hibernate is awesome! I'm pretty excited about Hibernate and I'll continue to use it as primary ORM solution for my future projects. Today I'd like to share typical usage scenarios of Hibernate Criteria API which provides extremely powerful capabilities to build strong typed queries.

Let's start with few simple classes (Employer and Employee) mapped to database tables. We'll use those in future examples.
@Entity
@Table( name = "employees" )
public class Employee {
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
@Column( name = "id" )
private Integer id;

@Column( name = "name" )
private String name;

@Column( name = "email" )
private String email;

@ManyToOne
private Employer employer;

public void setId( Integer id ) {
this.id = id;
}

public Integer getId() {
return this.id;
}

public void setName( String name ) {
this.name = name;
}

public String getName() {
return this.name;
}

public void setEmail( String email ) {
this.email = email;
}

public String getEmail() {
return this.email;
}
};

@Entity
@Table( name = "employers" )
public class Employer {
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
@Column( name = "id" )
private Integer id;

@OneToMany( cascade = CascadeType.ALL, mappedBy = "employer" )
private Set<Employee> employees = new HashSet<Employee>();

public void setId( Integer id ) {
this.id = id;
}

public Integer getId() {
return this.id;
}

public Set<Employee> getEmployees() {
return this.employees;
}

public void setEmployees( Set<Employee> employees ) {
this.employees = employees;
}
};

Let say we have to find Employees and Employers using different search criteria. For all examples I assume that helper method getCurrentSession() returns Hibernate's session and each code fragment is running within transaction.

1) Find all Employees with name "Tom" (the easiest one).
// Get current session
final Session session = getCurrentSession();

// Create Criteria for Employee class
final Criteria criteria = session.createCriteria( Employee.class );
// Add name = "Tom" restriction
criteria.add( Restrictions.eq( "name", "Tom" );

// Run query and get the results
List< Employee gt; employees = criteria.list();
// Do something here
...

2) Find all Employers which don't have any Employees.
// Get current session
final Session session = getCurrentSession();

// Create Criteria for Employer class
final Criteria criteria = session.createCriteria( Employer.class );
// Add empty Employees restriction
criteria.add( Restrictions.isEmpty( "employees" );

// Run query and get the results
List< Employer gt; employers = criteria.list();
// Do something here
...

3) Find all Employers which have Employees with name "Tom".
// Get current session
final Session session = getCurrentSession();

// Create Criteria for Employer class
final Criteria criteria = session.createCriteria( Employer.class );
// Create alias for Employees collection
criteria.createAlias( "employees", "employees" );
// Add name = "Tom" restriction
criteria.add( Restrictions.eq( "employees.name", "Tom" );

// Run query and get the results
List< Employer > employers = criteria.list();
// Do something here
...

4) For this example let me introduce another table "black_list" and BlackListEntry entity mapping.
@Entity
@Table( "black_list" )
public class BlackListEntry {
@Id
@GeneratedValue( strategy = GenerationType.TABLE )
@Column( name = "email" )
private String email;

public void setEmail( String email ) {
this.email = email;
}

public String getEmail() {
return this.email;
}
};

The purpose of this entity is pretty simple: a black list of e-mail addresses. Those should be exclude from any mailing lists. Let's find all Employees whose e-mails are in that black list.
// Get current session
final Session session = getCurrentSession();

// Create detached Criteria
DetachedCriteria detachedCriteria =
DetachedCriteria.forClass( BlackListEntry.class );
// Create projection to select 'email' only
detachedCriteria.setProjection( Projections.property( "email" ) );

// Create Criteria for Employee class
final Criteria criteria = session.createCriteria( Employee.class );
// Add subquery restriction (all emails in black list)
criteria.add( Subqueries.propertyIn( "email", detachedCriteria ) );

// Run query and get the results
List< Employee > employees = criteria.list();
// Do something here
...

That's it. :-)