BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles ActiveJPA – Active Record Pattern for JPA

ActiveJPA – Active Record Pattern for JPA

ActiveRecord is a Ruby on Rails’ ORM layer, roughly comparable to Hibernate in Java. ActiveRecord is based on conventions rather than configuration, so it is easier to work with than Hibernate. It really shines when it comes to simplifying the basic operations for creating, reading, updating, and deleting data.

With ActiveRecord, your model class doubles as a Data Access Object (DAO) to perform the CRUD operations. After my early investigations I was so impressed with ActiveRecord that I started looking for solutions that simplify usage of ORMs based on the Java Persistence API (JPA).

Most JPA applications have some kind of Data Access Layer (DAL) to interact with the database. Usually a DAL consists of Data Access Objects (DAO) or classes of the Repository design pattern.

While a DAO is usually implemented in a one-to-one relationship with an entity object, repositories are implemented per aggregate root. In either case these applications end up creating multiple classes to interact with the database. While the right kind of abstraction can limit the number of classes created, it still introduces an additional layer that you have to maintain and test in your application.

ActiveJPA is a Java implementation of Martin Fowler’s Active Record pattern over JPA. It wraps around JPA and provides useful abstractions to simplify data access. With ActiveJPA, models themselves act as a DAO and interact with the database without you having to write any additional code for the DAL.

Since ActiveJPA uses the JPA spec, all ORM implementations (Hibernate, EclipseLink, OpenJPA, etc.) that implement JPA can be used with ActiveJPA.

Converting your existing JPA model to ActiveJPA

To convert your model to ActiveJPA, just extend your model implementation from org.activejpa.entity. Model:

@java.persistence.Entity    
public class YourModel extends org.activejpa.entity.Model {
}

Performing CRUD operations

Your models will inherit a bunch of CRUD functions from the ActiveJPA model class.

// Get order by id    
Order order = Order.findById(12345L);

// Get all orders for a customer that are shipped
List orders = Order.where("customerEmail", "dummyemail@dummy.com", "status", "shipped");

// Get the first order matching the filter
Long count = Order.first("customerEmail", "dummyemail@dummy.com", "status", "shipped");

// Get the unique order matching the conditions
Long count = Order.one("customerEmail", "dummyemail@dummy.com", "status", "shipped");

// Dump everything
List orders = Order.all();

// Check if order exists with the given identifier
boolean exists = Order.exists(1234L);

// Save order
order.persist();

// Delete order
order.delete();

// Refresh the order

order.refresh();

// Merge order with the one in persistence context

order.merge();

Filters & Pagination

You don’t need to create JPQL or criteria queries to filter records. ActiveJPA exposes a sophisticated filter to perform conjunctions across different operations:

// Get all orders matching the given email and billing amount greater than 1000 & paginate it    
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition("customerEmail", Operator.eq, "dummyemail@dummy.com");

filter.addCondition(new Condition("billingAmount", Operator.gt, 1000.00);
List orders = Order.where(filter);

// Count of orders matching the filter
Long count = Order.count(filter);

// Delete all orders matching the filter
Long count = Order.deleteAll(filter);

Nested queries

ActiveJPA allows nesting of the filter parameters. This makes it easier for creating dynamic queries on the fly. For instance you can get all orders that have at least one order item created out of a product of category ‘book’.

// Get all orders containing at the least one book item  
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition("orderItems.product.category", Operator.eq, "books");
List orders = Order.where(filter);

Working with collections

All the CRUD operations discussed above can be performed at the collection level as well. The query scope is set to collection class in such cases:

// Find order item by id within an order    
order.collections("orderItems").findById(123L);

// Get the first order item that is shipped:
order.collections(“orderItems”).first(“status”, “shipped”);

// Get all order items that are cancelled
order.collections(“orderItems”).where(“status”, “cancelled”);

// Get all the items in the collection
order.collections(“orderItems”).all();

// Add an item to the collection
order.collections(“orderItems”).add(orderItem);

// Add an item to the collection
order.collections(“orderItems”).remove(orderItem);

Filters and pagination can be applied to a collection as well.

// Search order items by filter within an order and paginate it

Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);

filter.addCondition(new Condition(“status”, “shipped”);
order.collections("orderItems").where(filter);

// Get count of order items matching the filter in the order

order.collections(“orderItems”).count(filter);

Dynamic updates

ActiveJPA supports dynamically updating a model. This is quite useful in scenarios where, for example, a user updates a form from the browser. Instead of calling a setter method for each individual attribute, you can pass a map of attributes to update:

// Update attributes    
Map attributes = new HashMap();
attributes.put("billingAmount", 1000.0);
order.updateAttributes(attributes);

You can also update non-primitive/non-wrapper fields by passing maps to those objects. The example below shows updating a shipping address and billing amount of an order.

// Update billing amount and shipping address of an order
Map attributes = new HashMap();
Map address = new HashMap();
address.put(“city”, “Bangalore”);
address.put(“state”, “Karnataka”);
attributes.put(“shippingAddress”, address);
attributes.put("billingAmount", 1000.0);
order.updateAttributes(attributes);

Note: Updating list/set/array fields are not supported yet. It will be supported in the future versions

Transaction Handling

By default ActiveJPA starts a transaction for all the update operations if one doesn’t already exist, but you can also wrap a unit of work under a transaction as follows:

JPAContext context = JPA.instance.getDefaultConfig().getContext();    
context.beginTxn();
boolean failed = true;
try {
// Your unit of work here
failed = false;
} finally {
// Commit or rollback the transaction
context.closeTxn(failed);
}

If an outer transaction exists already, ActiveJPA will use that but will not commit or rollback the transaction. It will be the responsibility of the application to close the transaction.

Testing your Models

ActiveJPA provides a base test class for TestNG that hooks ActiveJPA into test runtime. Just ensure your test classes extends org.activejpa.entity.testng.BaseModelTest class. Below is a sample code,

public class OrderTest extends BaseModelTest {
@Test
public void testCreateOrder() {
Order order = new Order();
order.setCustomerEmail("dummyemail@dummy.com");
...
...
order.persist();
Assert.assertEquals(Order.where("customerEmail", "dummyemail@dummy.com").get(0), order);
}
}
}

Getting Started

Setting up Maven

ActiveJPA is available as a Maven artifact and should be fairly simple to integrate with your application. Just add the following maven dependency to your pom.xml file

<dependencies>    
<dependency>
<groupId>org.activejpa</groupId>
<artifactId>activejpa-core</artifactId>
<version>0.1.5</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>activejpa-repo</id>
<url>https://raw.github.com/ActiveJpa/activejpa/mvn-repo/releases</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>

Hooking into your application

ActiveJPA needs to be hooked into your application before the entity classes are loaded. If you are running a Tomcat server, the ideal place would be ServletContextListener. Below is code you could write in the context listener contextInitialized() method.

// Loads the Java agent dynamically    
ActiveJpaAgentLoader.instance().loadAgent();

// Add the persistence unit defined by persistence.xml identified by the name 'order'. The persistence.xml should be available in the classpath
JPA.addPersistenceUnit("order");

// If you have entity manager factory already created, you can attach the same to ActiveJpa
// JPA.addPersistenceUnit("order", entityManagerFactory);

Integrating with Spring Framework

Integrating ActiveJPA with frameworks like Spring is fairly simple. Most applications use Spring to configure JPA and manage the transactions using annotations. ActiveJPA can be configured in two ways - you can either let it create the entity manager factory or pass an already existing one. In the case of Spring configured JPA, we can use the entity manager factory created by Spring. This ensures ActiveJPA uses the same connections and transactions created by Spring and provides a seamless integration.

The code below demonstrates how to integrate ActiveJPA with a Spring application deployed on a servlet container. It uses a custom context loader listener to hook ActiveJPA into the application. Note that it is similar to the servlet example above, except that it uses the Spring Framework ContextLoaderListener:

public class CustomContextListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
try {
// This loads the javaagent dynamically
ActiveJpaAgentLoader.instance().loadAgent();
} catch (Exception e) {
throw new RuntimeException(e);
}
super.contextInitialized(event);
JPA.instance.addPersistenceUnit("default", getCurrentWebApplicationContext().getBean(EntityManagerFactory.class), true);
}
}

Sample Application

There are a lot more specific examples in the sample application from the ActiveJPA Project Page on GitHub, demonstrating Spring-ActiveJPA integration.

About the Author

Ganesh Subramanian is an Architect with Hightail and he has over 10+ years experience in architecting highly scalable, low latency distributed applications spanning across multiple domains. Ganesh was instrumental in architecting Flipkart's (India's largest e-commerce player) Supply chain management platform and he is an active contributor to the open source community (ActiveJPA, Minnal, AutoPojo, etc.). He is the author of the tech blog and he loves watching movies (all genres) and spending time with his family during his free time.

Rate this Article

Adoption
Style

BT