Repositories are one of my favorite patterns to make an abstraction of the data layer of an application. You may have heard about it if you have ever read an article about Domain Driven Design. Hence in this post I consider you have the basic familiarity with it and will focus mostly on how to implement this pattern in a typical Java SE application by using Apache DeltaSpike data module.
What is Apache DeltaSpike?
Apache DeltaSpike project provides a comprehensive set of CDI extensions each focusing on a specific aspect of a typical application like security, data access, configuration or test. This allows Java developers to focus more on the business part of the application.
Here we’ll try to see how we can use the data module of this library as a basis to implement our repositories. Also in order to have a CDI container in our Java SE application, we’ll incorporate another DeltaSpike module called cdi control. It provides an abstraction layer on top of well known CDI containers like Weld or OpenWebBeans.
The example scenario
We’ll try to keep the scenario as simple as possible and will focus on the implementation details. Therefore we’ll only have a simple Person entity and will try to see how we can save/delete/update and also implement different finders for it. Eventually our final code would be a service class like this:
public class PersonService { @Inject private PersonRepository personRepository; public Person save(String firstName, String lastName) { Person person = new Person(); person.setFirstName(firstName); person.setLastName(lastName); person.setCreateDate(new Date()); return personRepository.save(person); } public List<person> findAll() { return personRepository.findAll(); } public List<person> findByName(String name) { return personRepository.findByFirstNameOrderByCreateDateDesc(name); } }
Adding dependencies
The Apache DeltaSpike has a core module that should always be included. Beside that, you should add a specific module (in this case the data module) to your project as needed. You can check out the whole project at my GitHub repository at https://github.com/moghaddam/javaindeed.
Here are the dependencies for the DeltaSpike core modules (deltaspike version is defined as property on top of the pom file):
<properties> <deltaspike.version>1.9.0</deltaspike.version> </properties> <dependency> <groupid>org.apache.deltaspike.core</groupid> <artifactid>deltaspike-core-api</artifactid> <version>${deltaspike.version}</version> <scope>compile</scope> </dependency> <dependency> <groupid>org.apache.deltaspike.core</groupid> <artifactid>deltaspike-core-impl</artifactid> <version>${deltaspike.version}</version> <scope>runtime</scope> </dependency>
and here are the Apache DettaSpike data module dependencies:
<dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-data-module-api</artifactId> <version>${deltaspike.version}</version> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-data-module-impl</artifactId> <version>${deltaspike.version}</version> </dependency>
Next the cdi control module together with the Weld implementation itself to be used as the CDI container:
<dependency> <groupId>org.apache.deltaspike.cdictrl</groupId> <artifactId>deltaspike-cdictrl-api</artifactId> <version>${deltaspike.version}</version> </dependency> <dependency> <groupId>org.apache.deltaspike.cdictrl</groupId> <artifactId>deltaspike-cdictrl-weld</artifactId> <version>${deltaspike.version}</version> </dependency> <dependency> <groupId>org.jboss.weld.se</groupId> <artifactId>weld-se-core</artifactId> <version>3.0.5.Final</version> </dependency>
Finally the H2 in memory database, its JDBC driver and the Hibernate dependencies should also be added:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.3.6.Final</version> </dependency>
Setup CDI and JPA configuration
The CDI container will scan a JAR file only if it could find the beans.xml file in its META-INF folder. So the the next step is to add it to our project:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="all" version="2.0"> </beans>
Next we should add the persistence.xml file to the META-INF folder:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="sample-unit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <class>com.javaindeed.deltaspike.javase.Person</class> <properties> <!-- Configuring JDBC properties --> <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test"/> <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <!-- Hibernate properties --> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.show_sql" value="false"/> </properties> </persistence-unit> </persistence>
Since it’s an standalone application and we don’t have any application server, we’ll not have a JTA transaction. As can be seen, the value of the “transaction-type” is set to “RESOURCE-LOCAL”.
Adding JPA Entity
Now it’s time to add our Person entity. It has the following basic properties:
- id
- firstName
- lastname
- creationDate
@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String firstName; @Column private String lastName; @Temporal(TemporalType.TIMESTAMP) @Column private Date createDate; // getters and setters... }
Setup EntityManager producer
To be able to access the entity manager instance via injection, we should provide a producer method for that. Here we can use the @PersistenceUnitName annotation provided by DeltaSpike jpa module. Basically it scans the classpath and finds all persistence.xml files and then tries to create an instance of EntityManagerFactory for the persistence unit we requested.
@ApplicationScoped public class EntityManagerProducer { @Inject @PersistenceUnitName("sample-unit") private EntityManagerFactory entityManagerFactory; @Produces @Default public EntityManager getEntityManager() { return entityManagerFactory.createEntityManager(); } /** * Closes the entity manager produced earlier. It's called automatically by CDI container * * @param entityManager the entity manager produced earlier in the {@link #getEntityManager()} method */ public void closeEntityManager(@Disposes @Default EntityManager entityManager) { if (entityManager.isOpen()) { entityManager.close(); } } /** * Closes the entity manager factory instance so that the CDI container can be gracefully shutdown */ @PreDestroy public void closeFactory() { if(entityManagerFactory.isOpen()) { entityManagerFactory.close(); } } }
By doing so, DeltaSpike knows how to access our entity manager.
Implementing the Repository
To introduce a new repository for an entity, you should have a class or an interface implementing EntityRepository<E, PK extends Serializable> interface. Its type parameters are:
- T: The entity class that it’s going to manage
- PK: The type of the primary key of the entity (e.g. Long, Integer, etc.)
Below you can find the list of its methods:
E findBy(PK primaryKey); Optional<E> findOptionalBy(PK primaryKey); List<E> findAll(); List<E> findAll(int start, int max); List<E> findBy(E example, SingularAttribute<E, ?>... attributes); List<E> findBy(E example, int start, int max, SingularAttribute<E, ?>... attributes); List<E> findByLike(E example, SingularAttribute<E, ?>... attributes); List<E> findByLike(E example, int start, int max, SingularAttribute<E, ?>... attributes);
Fortunately there is an abstract class named AbstractEntityRepository implementing this interface which you can extend your repository class from. Also you should remember to annotate the repository class with the @Repository annotation.
@Repository(forEntity = Person.class) public abstract class PersonRepository extends AbstractEntityRepository<Person, Long> { public abstract List<Person> findByFirstNameOrderByCreateDateDesc(String name); }
You can see here that:
- The repository is defined as an abstract class. That’s because at runtime, the DeltaSpike picks-up all classes/interfaces marked with @Repository annotation and provides an implementation for them dynamically.
- The finder method has no implementation. Mainly because the method name follows a naming convention defined by DeltaSpike called Query Method Expression (more on that follows). The actual implementation of the finder method will be generated at runtime based on this naming.
What is Query Method Expression?
It’s a convention to define a method name in such a way that it would be possible for the DeltaSpike to tokenize/parse and create an appropriate JPA query based on it. Here are the the basic rules for naming methods (based on the original documentations):
- The query method must return an entity, an Optional of an entity, a list of entities or a stream of entities.
- It must start with the findBy prefix (or related findOptionalBy, findAnyBy, findAll, findFirst or findTop).
- Followed by a property of the Repository entity and an optional comparator (check the next item). The property will be used in the query together with the comparator. Note that the number of arguments passed to the method depend on the comparator.
- You can add more blocks of property-comparator which have to be concatenated by a boolean operator. This is either an And or Or
- You can also use the same way for delete an entity. Basically it must start with the removeBy keyword (or related deleteBy)
For more detailed explanation, check Apache DeltaSpike online documentation.
The main method of the application
Now that we have implemented our Repository, the PersonService class we defined at the beginning of the post should work. So here are the remaining steps that will be covered in our Main class:
- Bootstrap the CDI container using DeltaSpike CDI container module
- Get access to the PersonService class via CDI container
- Call business methods on the service instance
- Shutdown the CDI container
I’ve added necessary comments to the Java code itself.
public class Main { private static final Logger logger = LogManager.getLogger(Main.class); private CdiContainer cdiContainer; public static void main(String[] args) { Main main = new Main(); main.run(); } private void run() { initCdiContainer(); try { persistPersons(); fetchAllPersons(); findByName("John"); } finally { shutdownCdiContainer(); } } private void findByName(String firstName) { log("Find person by first name = '" + firstName + "'"); PersonService personService = (PersonService) getBean(PersonService.class); List<Person> people = personService.findByName(firstName); people.forEach(logger::info); } private void fetchAllPersons() { log("Finding all persons"); PersonService personService = (PersonService) getBean(PersonService.class); List<Person> people = personService.findAll(); for(int i=0; i<people.size(); i++) { logger.info("Person [" + i + "]: " + people.get(i)); } } private void persistPersons() { log("Persisting sample persons"); PersonService personService = (PersonService) getBean(PersonService.class); personService.save("John", "Smith"); personService.save("Will", "Smith"); personService.save("Black", "Smith"); } private void initCdiContainer() { log("Initializing CDI container"); cdiContainer = CdiContainerLoader.getCdiContainer(); cdiContainer.boot(); } private void shutdownCdiContainer() { log("Shutting down CDI container"); cdiContainer.shutdown(); } /** * Encapsulates the necessary steps to get a reference to an instance of a Bean via CDI container * * @param type Type of the bean requested * @return An instance of the requested bean (either newly created or an already existing instance based on the * scope of the bean) */ private Object getBean(Class type) { BeanManager beanManager = cdiContainer.getBeanManager(); Set<Bean<?>> personServiceBean = beanManager.getBeans(type); Bean<?> bean = beanManager.resolve(personServiceBean); CreationalContext<?> context = beanManager.createCreationalContext(bean); return beanManager.getReference(bean, type, context); } private void log(String message) { logger.info("/***********************************************************"); logger.info(" *\t\t\t" + message); logger.info(" ***********************************************************/"); } }
Removing an entity
In order to remove an entity, we can still use the method expressions but the prefix would be removeBy. For example to remove a person using lastName property, we can add a method to our repository as:
abstract void removeByLastName(@NotNull String lastName);
Summary
What demonstrated in this post is definitely not the best practice or something that would be recommended to be used in production. I was trying to show how easy it would be to setup a project data access layer using DeltaSpike data module. I highly recommend readers to take a look at project’s documentation to know about more advanced features.