Archive for March 15th, 2016

15
Mar
16

Simple Example of a Generic JPA CRUD DataManager

This page describes how to create a Generic JPA Data Manager for CRUD operations. The sample on this page is self contained. An in-memory database is included along with a JUnit test case. You can download and run this example from the github repository link below.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

CRUD operations are implemented by boilerplate code. You can simplify your data managers by extending a Generic DataManager that implements the CRUD operations.

The CRUD operations include:

  • Create
  • Read
  • Update
  • Delete

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Test Case

The following is the main test case. It contains a reference to an EJB container that will be used to run the application. The produceContainer method accepts a name of a database. This will result in a file being created in the users $HOME/data/ directory. Each test case class can have a dedicated database instance. Once the test case is complete HSQL can write the results back to the file system or not. This functionality is customizable by passing properties in the connection URL for hsqldb.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/UserManagerApp/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    long userCount = userManagerService.getUserCount();
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
		userManagerService.addUser(new User(id, password));
		Long newUserCount = userManagerService.getUserCount();
		Assert.assertTrue(newUserCount > userCount);
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		User user = userManagerService.getUser(randomUser.getId());
		Assert.assertNotNull(user);
	}
}

Container Producer

This is where the database connections are configured. The static method accepts a database name and saves an instance of the database into the users $HOME/data directory. The database is initialized with a completely blank schema. Once the test is complete the user can open the resulting database “.script” file and insert the mock data. Next time the test case runs it will load the data specified in the “.script” file. The JPA implementation will also check for any changes in the entity beans and propagate the DDL changes to the script file.

Since the scripts are regular files Using this method the developer has flexibility. For example the developer can have a complete library of script files one for each test case. The script files could also be checked into SVN or GIT version control so they can be shared or after finding a bug they could be restored to their original condition etc…

src/test/java/org/test/ContainerProducer.java

package org.test;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;

public class ContainerProducer {

	public static final EJBContainer produceContainer(String dbName) {
	    // Create the container instance, passing it the properties map:
	    Map<String, Object> p = new HashMap<>();
	    String userHome = System.getProperty("user.home");
        p.put("DefaultDS", "new://Resource?type=DataSource");
        p.put("DefaultDS.JdbcUrl", "jdbc:hsqldb:file:"+userHome+"/data/"+dbName+";shutdown=true");

	    return javax.ejb.embeddable.EJBContainer.createEJBContainer(p);
	}
}

Generic CRUD JPA Data Managers

The following 2 files are the main focus of the article. Most of the boiler plate code for the crud operations have been implemented here. This leaves your datamanager free to implement any custom functionality.

src/main/java/org/test/CrudDataManager.java

package org.test;

import java.io.Serializable;

public interface CrudDataManager<T, PK extends Serializable> {
    T create(T t);
    T read(PK id);
    T update(T t);
    void delete(T t);
}

src/main/java/org/test/CrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class CrudDataManagerImpl<T, PK extends Serializable> implements CrudDataManager<T, PK> {

	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public CrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T update(T t) {
		return this.entityManager.merge(t);
	}

	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

Data Manager Layer

Embarrassingly simple data manager layer.

src/main/java/org/test/UserDataManager.java

package org.test;

import java.io.Serializable;
import java.util.Random;

public class UserDataManager extends CrudDataManagerImpl<User, Serializable> {

	public Long getUserCount() {
		return entityManager.createNamedQuery("getCount", Long.class).getSingleResult();
	}
	
	public User getRandom() {
        Long count = getUserCount();
        Long random = getRandomNumberInRange(0, count-1);

        User emp = entityManager.createNamedQuery("getAll", User.class).setFirstResult(random.intValue())
        		.setMaxResults(1)
        		.getSingleResult();
        
		return emp;
	}
	
	private static Long getRandomNumberInRange(long min, long max) {
		Random r = new Random();
		return r.longs(min, (max + 1)).findFirst().getAsLong();
	}
}

User Entity Class

The following represents a user object we will store to the database.

src/main/java/org/test/User.java

package org.test;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries({
	@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u"),
	@NamedQuery(name="getAll", query = "SELECT u from User u")
})
public class User implements Serializable {
	@Id
	@Column(name="userId", length=320, nullable=false)
	private String id;
	@Column(length=40, nullable=false)
	private String password;
	
	public User(){}
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", password=" + password + "]";
	}
}

Service Layer

Embarrassingly simple service layer. Each method executes within a transaction automatically.

src/main/java/org/test/UserManagerService.java

package org.test;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.slf4j.Logger;

@Stateless
public class UserManagerService {
	@Inject
	private Logger logger;

	@Inject
	private UserDataManager userDataManager;

	public void addUser(User user) throws Exception {
		logger.info("inside adduser");
		userDataManager.create(user);
	}
	
	public User getUser(String id) throws Exception {
		User user = userDataManager.read(id);
		return user;
	}

	public User getRandomUser() throws Exception {
		return userDataManager.getRandom();
	}
	public Long getUserCount() {
		return userDataManager.getUserCount();
	}
}

Logger CDI injection

Just a simple way to inject a logger into the entity classes.
src/main/java/org/test/LoggerProducer.java

package org.test;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    	// check log4j.xml to find out where logs are being written to.
    	return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

Project Configuration

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="file" value="${user.home}/logs/UserManager.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="org.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="org">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

The following file should in the META-INF/ directory. If its going to be part of a war it should be located in a jar file that is inside the WEB-INF/lib directory. Read your containers documentation on the proper location of this file if its not clear.

JPA Config

src/main/resources/META-INF/persistence.xml

<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="UserManager">
		<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>DefaultDS</jta-data-source>

		<properties>
			<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
		</properties>
	</persistence-unit>
</persistence>

CDI config

Create a blank src/main/resources/META-INF/beans.xml file to enable Java contexts and dependency injection (CDI)

Maven Configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.test</groupId>
	<artifactId>UserManagerApp</artifactId>
	<version>20160315</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.




Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 78 other followers

March 2016
S M T W T F S
« Feb   Mar »
 12345
6789101112
13141516171819
20212223242526
2728293031  

Blog Stats

  • 822,606 hits