Java Repository Bridge library in practice

For the past couple of weeks we’ve been blogging about the Java Repository Bridge (JaRB) project. For those who are not yet familiar with JaRB, I recomment reading our introduction post first. In previous blog posts we described the various components of JaRB in isolation. During this blog post we are going to use all previously mentioned components to create an actual Java EE application and show what kind of advantages JaRB offers.

We will be creating a simple message board, on which users can post messages. The project has a typical layered architecture (controller, service, dao), using Spring for dependency injection and Hibernate for object relational mapping.



Database schema


While most projects let Hibernate generate the database schema, we are going to create our schema using Liquibase. When using Liquibase, rather than Hibernate, we have a lot more control on schema generation. For example, the possibility to create views and name our constraints properly. Liquibase migrations are automated using a migrating data source, as described in a previous post:




<bean id="dataSource" class="org.jarb.migrations.MigratingDataSource">
<property name="delegate">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:jarb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
</property>
<property name="migrator">
<bean class="org.jarb.migrations.liquibase.LiquibaseMigrator">
<property name="changeLogPath" value="src/main/db/changelog.groovy"/>
</bean>
</property>
</bean>


Everytime the application context is loaded, our data source schema is migrated to its latest version. Because migration is done before initializing Hibernate, we can use Hibernate for schema verification. Migration change sets are described in Groovy DSL, which looks as follows:




databaseChangeLog() {
changeSet(author: "jeroen@42.nl", id: "1") {
comment("Create initial database schema.")
createTable(tableName: "posts") {
column(autoIncrement: true, name: "id", type: "BIGINT") {
constraints(nullable: false, primaryKey: true, primaryKeyName: "pk_posts_id")
}
column(name: "posted_datetime", type: "DATETIME")
column(name: "author", type: "VARCHAR(255)")
column(name: "title", type: "VARCHAR(255)")
column(name: "message", type: "TEXT")
}
addUniqueConstraint(tableName: "posts", columnNames: "title", constraintName: "uk_posts_title")
}
}


For Liquibase to accept our .groovy extension, we require the groovy extension. Note that the groovy extension project is not yet on the maven public repository, and thus needs to be installed manually.



Data injection


Whenever Hibernate creates the schema it will look for an import.sql file on the classpath, and execute any statements found inside. By switching to Liquibase, we lose this functionality initially. Fortunately Spring provides a similar component called the ResourceDatabasePopulator. This database populator will however throw an exception if the provided resource is not readable. For convenience reasons JaRB provides a SkipableSqlResourceDatabasePopulator that wraps the populator and does not throw an exception.



Also, JaRB enables you to insert data into the database using an Excel workbook. Rather then inserting data by SQL statements, we define our entities on sheets. Rows in that sheet will later be mapped to domain objects and inserted by Hibernate. Because we are using hibernate, the data is subject to an additional validation step.



For demonstration purposes, our sample application we will be using both an SQL and Excel file to insert data. Database populators are combined, and automated, as follows:




<bean class="org.springframework.jdbc.datasource.init.DataSourceInitializer">
<property name="dataSource" ref="dataSource"/>
<property name="databasePopulator">
<bean class="org.jarb.populator.CompoundDatabasePopulator">
<constructor-arg>
<list>
<!-- Using SQL statements -->
<bean class="org.jarb.populator.SkipableSqlResourceDatabasePopulator">
<constructor-arg value="classpath:import.sql"/>
</bean>
<!-- And an Excel workbook -->
<bean class="org.jarb.populator.excel.ExcelDatabasePopulator">
<property name="excelResource" value="classpath:import.xls"/>
<property name="excelDataManager">
<bean class="org.jarb.populator.excel.ExcelDataManagerFactoryBean">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</property>
</bean>
</list>
</constructor-arg>
</bean>
</property>
</bean>


Now, whenever our application context is loaded, the database schema is migrated to the latest version and some test posts are inserted.



Domain and validation


Lets start by defining our model:




@Entity @Table(name = "posts")
@DatabaseConstrained
public class Post {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "posted_datetime")
private Date postedOn = new Date();
@Email
private String author;
@NotEmpty
private String title;
private String message;
...
}


The model seems quite normal, persistence mapping is done by JPA annotations and some JSR303 constraint annotations are added for validation. However, the model seems incomplete, we have not defined any @NotNull and @Length annotations. When using JaRB, you no longer have to define those annotations on a persistable model! Length and nullability checks are done automatically using column metadata, just by including one @DatabaseConstrained annotation. More information can be found in a previous blog post.



Now, whenever we verify a post instance without an author, we will receive the ‘author’ cannot be null violation message. We receive this “not null” violation because the author column has a not null constraint in our database.



For our @DatabaseConstrained validator to run, we need to have a ValidatorFactory and EntityAwareColumnMetadataRepository bean in our application context:




<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean id="columnMetadataRepository" class="org.jarb.constraint.database.column.EntityAwareColumnMetadataRepository">
<constructor-arg ref="dataSource"/>
</bean>


Data access and violations


We can now define post objects and verify them, lets start with persisting:




@Repository
public class JpaPostRepository implements PostRepository {
@PersistenceContext
private EntityManager entityManager;

/** */
@Override
public List<Post> all() {
return entityManager.createQuery("from Post", Post.class).getResultList();
}

/** */
@Override
public void add(Post post) {
entityManager.persist(post);
}
}


Code wise our repository is very straight forward, we can query all posts and we can create a new post. JaRB enhances the repository layer with exception translation logic, that converts JDBC driver exceptions into more obvious constraint violation exceptions. Thus a violation of our ‘uk_posts_title’ unique constraint, would result in a ‘unique key violation exception’. We can also map custom exceptions to our constraints, allowing us to receive even better exceptions such as ‘post title already exists exception’. Configuration is done as follows:




<bean class="org.jarb.violation.integration.ConstraintViolationExceptionTranslatingBeanPostProcessor">
<property name="translator">
<bean class="org.jarb.violation.integration.JpaConstraintViolationExceptionTranslatorFactoryBean">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="exceptionClasses">
<map>
<entry key="uk_posts_title" value="org.jarb.sample.domain.PostTitleAlreadyExistsException"/>
</map>
</property>
</bean>
</property>
</bean>


Because we now receive proper exceptions for database violations, we can act on these violations in our code. Now we can provide clear feedback to the user that his desired post title already exists:




@RequestMapping(method = RequestMethod.POST)
public @ResponseBody PostCreateResult post(@Valid Post post) {
boolean success = false;
String message = null;
try {
post = postingService.createPost(post);
message = "Post was created succesfully!";
success = true;
} catch (PostTitleAlreadyExistsException e) {
message = "Post title '" + post.getTitle() + "' already exists";
}
PostCreateResult result = new PostCreateResult();
result.success = success;
result.message = message;
result.post = post;
return result;
}


Provide constraint metadata to frontend


Our application has access to database constraint metadata, and JSR303 constraint metadata (by annotations). We want to provide this constraint metadata to the front-end for client side validation and input descriptions. JaRB already provides a BeanConstraintMetadataGenerator that can describe the constraints of a bean. So all we need to do is create a request mapping that returns our post constraint information in JSON format:




@RequestMapping(value = "structure", method = RequestMethod.GET)
public BeanConstraintMetadata<Post> structure() {
return constraintMetadataGenerator.describe(Post.class);
}


Causing requests on ‘localhost:8080/jarb-sample/posts/structure.json’ to return:




{"beanConstraintMetadata":{
"beanType":"org.jarb.sample.domain.Post",
"propertiesMetadata":[
{"name":"message","required":false,"maximumLength":16777216,"fractionLength":null,"javaType":"java.lang.String","types":["TEXT"],"radix":null,"minimumLength":null},
{"name":"id","required":false,"maximumLength":64,"fractionLength":0,"javaType":"java.lang.Long","types":["NUMBER"],"radix":2,"minimumLength":null},
{"name":"author","required":true,"maximumLength":255,"fractionLength":null,"javaType":"java.lang.String","types":["TEXT","EMAIL"],"radix":null,"minimumLength":null},
{"name":"title","required":true,"maximumLength":255,"fractionLength":null,"javaType":"java.lang.String","types":["TEXT"],"radix":null,"minimumLength":1},
{"name":"class","required":false,"maximumLength":null,"fractionLength":null,"javaType":"java.lang.Class","types":["CUSTOM"],"radix":null,"minimumLength":null},
{"name":"postedOn","required":true,"maximumLength":26,"fractionLength":null,"javaType":"java.util.Date","types":["DATE"],"radix":null,"minimumLength":null}
]}
}


Allowing us to manipulate the form accordingly:




$.getJSON('posts/structure.json', function(data) {
$.each(data.beanConstraintMetadata.propertiesMetadata, function(index, propertyMetadata) {
var inputField = $('input[name=' + propertyMetadata.name + ']');
inputField.attr('minlength', propertyMetadata.minimumLength);
inputField.attr('length', propertyMetadata.maximumLength);
if(propertyMetadata.required) {
$('label[for=' + propertyMetadata.name + "]").append(" (*)");
inputField.addClass('required');
}
$.each(propertyMetadata.types, function(index, propertyType) {
if(propertyType == 'EMAIL') {
inputField.addClass('email');
}
});
});
});


Now all input fields, corresponding to a bean property, will have the correct (min)length attribute and a class for special types (e.g. email). Input labels now also include an (*) astrix for each required property, making the form more clear for end users.



Conclusion


As you can see JaRB provides many useful components, leading to atleast the following advantages:




  • (Liquibase) schema generation offers more control. Now we can e.g. use database views and properly name constraints. Schema changes can now also be defined by developers, relieving work from the DBA. In addition, the actual database schema is validated against our object mapping and unit tests.

  • Database population by excel to perform an additional validation step during insertion. Also, test data can be managed by people that have no SQL knowledge.

  • Database constraints information (@Length,@NotNull) no longer needs to be duplicated in the model, we can simply use @DatabaseConstrained.

  • Database constraint violations can be mapped to Java exceptions, allowing us to act on them properly.

  • Constraint metadata, from both the database as java class, is made available, allowing us to publish it to the frontend.



The sample project is available on github. Just checkout the code and start by mvn jetty:run (note that you have to manually install the liquibase groovy extension).

4 comments: