Tech notes for java-jpa

undefined

Posts

  1. PART 4: Jersey 2 and Dependency Injection with HK2
  2. PART 3: Using JPA and Postgresql in your application
  3. Java: Testing a REST service with a clean database (using sqlite)
  4. JPA: Mapping to an existing database
  5. JPA: Testing
  6. JPA: Named queries
  7. Tomcat 7: JPA using EclipseLink and Sqlite

PART 4: Jersey 2 and Dependency Injection with HK2

(This is part of a series http://blog.denevell.org/category_java-web-quick-start.html)

Previously, we had the JPA and adapter code in the request object. This was obviously not ideal.

Jersey 2 comes with a JSR330 (dependency injection) implementation baked in, named HK2, so we can move the JPA model code and the adapter code into separate objects and inject them in.

Let's start with a simple object that will adapt the list of JPA entities into a resource entities (same code as before).

echo '
package com.example.YOURPROJECT;

import java.util.ArrayList;
import java.util.List;

public class ExampleResourcesAdapter {
	
	public List<ExampleResource> adapt(List<ExampleEntity> list) { 
		ArrayList<ExampleResource> resList = new ArrayList<ExampleResource>();
		for (ExampleEntity exampleEExntity : list) {
			ExampleResource exampleItem = new ExampleResource();
			exampleItem.setStuff(exampleEntity.getTalky());
			resList.add(exampleItem);
		}
		return resList;
	}

}
' > src/main/java/com/example/YOURPACKAGE/ExampleResourcesAdapter.java

Now let's move the JPA stuff into its own model. In this case we're also using an interface, although we don't have to for the DI to work. (Same JPA code as before, but without the comments)

echo '
package com.example.YOURPROJECT;

import java.util.List;

public interface AddListModel {
    List<ExampleEntity> addAndList(String someString);
}
' > src/main/java/com/example/YOURPACKAGE/AddListModel.java

And now for the implementation of the interface.

echo '
package com.example.YOURPROJECT;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import org.apache.log4j.Logger;

public class AddListModelImpl implements AddListModel {

	@Override
	public List<ExampleEntity> addAndList(String someString) {
		EntityManager entityManager = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME").createEntityManager();
		EntityTransaction transaction = entityManager.getTransaction(); 
		List<ExampleEntity> list = null;
		try {
			transaction.begin();
			ExampleEntity entity = new ExampleEntity();
			entity.setTalky(someString);
			entityManager.persist(entity);
			TypedQuery<ExampleEntity> nq = entityManager.createNamedQuery("list", ExampleEntity.class);
			list = nq.getResultList();
			transaction.commit();
		} catch (Exception e) {
			Logger.getLogger(getClass()).error("Problem persisting", e);
			transaction.rollback();
			throw e; 
		} finally {
			entityManager.clear(); 
			entityManager.close();
		}		
		return list;
	}

}
' > src/main/java/com/example/YOURPACKAGE/AddListModelImpl.java

Now we have these objects, we need to tell the dependency injector about them. We do this using a AbstractBinder class.

(I think there's an automatic method for this, using annotations, but this is the only way I've got working.)

echo '
package com.example.YOURPROJECT;

import org.glassfish.hk2.utilities.binding.AbstractBinder;

public class DependencyBinder extends AbstractBinder {

	@Override
	protected void configure() {
		bind(AddListModelImpl.class).to(AddListModel.class);
		bind(ExampleResourcesAdapter.class).to(ExampleResourcesAdapter.class);
	}

}
' > src/main/java/com/example/YOURPACKAGE/DependencyBinder.java

Note we're binding the iplementatioon of the model interface on the first bind line. And on the second just binding two concreate classes together.

We need to tell Jersey about this AbstractBinder, so in your JerseyApplication.java class you need to register it:

register(new DependencyBinder());

Now we can see the new request has two @Inject lines and is much, much shorter (also has a new @Path)

echo '
package com.example.YOURPROJECT;

import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("example_jpa_di")
public class ExampleJPAWithDIRequest {
	
	@Inject AddListModel mModel;
	@Inject ExampleResourcesAdapter mAdapter;

	@Path("{example}")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public List<ExampleResource> example(@PathParam("example") String example) {
		List<ExampleEntity> list = mModel.addAndList(example);
		List<ExampleResource> resList = mAdapter.adapt(list);
		return resList;
	}
}
' > src/main/java/com/example/YOURPACKAGE/ExampleJPAWithDIRequeset.java

We can run it, and it'll have the same result as before (note the different url for the request)

gradle build
java -jar jetty-runner-9.1.0.M0.jar --port 8081 build/libs/YOUR_PROJECT_DIR.war

If you visit

http://localhost:8081/YOUR_PATH/example_jpa_di/ONE

and then visit

http://localhost:8081/YOUR_PATH/example_jpa_di/TWO

You should see the JSON, (should the database be blank before starting)

[{"stuff":"ONE"},{"stuff":"TWO"}]
java java-jpa java-jersey

PART 3: Using JPA and Postgresql in your application

(This is part of a series http://blog.denevell.org/category_java-web-quick-start.html)

Previously, in our build.gradle file we included these jars to allow us to talk to a Postgresql database through JPA.

compile 'postgresql:postgresql:9.1-901-1.jdbc4'
compile 'org.eclipse.persistence:eclipselink:2.4.0'

We needed the eclipse link repository for that.

repositories {
    maven {
        url 'http://download.eclipse.org/rt/eclipselink/maven.repo'
    }
    ...
}

Now we need to create a Postgresql database user and database to talk to. We'd normally set this up in the environment somehow before running the project.

(as root)
su - postgres
psql -c "create user test_username password 'test_password';"
psql -c "create database test_database owner test_username"
(logout from postgres and root)

Now we can create the persistence.xml file that tells JPA how to connect to our database. (Ensure there's no whitespace at the start of the file)

echo '<?xml version="1.0" encoding="UTF-8" ?>
<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" xmlns="http://java.sun.com/xml/ns/persistence">
  <persistence-unit name="PERSISTENCE_UNIT_NAME" transaction-type="RESOURCE_LOCAL">
    <mapping-file>META-INF/mapping.xml</mapping-file>
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
      <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test_database" />
      <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
      <property name="javax.persistence.jdbc.user" value="test_username" />
      <property name="javax.persistence.jdbc.password" value="test_password" />
      <property name="eclipselink.logging.level" value="ALL" />
	   </properties>
	 </persistence-unit>
</persistence>
' > src/main/resources/META-INF/persistence.xml

This is doing a couple of things

  • Naming the persistence-unit which we'll use when we come to initialise JPA
  • Makeing our database transactions will be local to this machine
  • Saying there's a mapping file in META-INF/mapping.xml that maps our Objects to the database
  • Saying we're using EclipseList for the persistence provider
  • Pointing JPA to our database
  • Providing the username and password to that database
  • Making EclipseLink log everything

Next we'll create a simple Object, or Entity, which we'll persist in the database. It's just a POJO.

echo '
package com.example.YOURPROJECT;

public class ExampleEntity {
	
	private long id; 
	private String talky;

	public String getTalky() {
		return talky;
	}
	public void setTalky(String talky) {
		this.talky = talky;
	}
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}

}
' > src/main/java/com/example/YOURPROJECT/ExampleEntity.java

We could use annotations on the object to map it to the database, and leave JPA to sort out the tables etc, but that always leads to pain.

So we're creating the mapping.xml file we referred to earlier. (Ensure there's no whitespace at the start of the file)

echo '<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm    
  http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
  version="2.0">
    <entity class="com.example.YOURPROJECT.ExampleEntity"> 
      <table name="example" />
      <named-query name="list">
        <query>select p from ExampleEntity p</query>
      </named-query>       
      <attributes>
        <id name="id">
          <generated-value strategy="auto" />
        </id>
        <basic name="talky">
          <column name="talky" nullable="false"/>
        </basic>
      </attributes>
    </entity>    
</entity-mappings>
' > src/main/resources/META-INF/mapping.xml

This is saying:

  • Specifying the entity you created above
  • Specifying the database table where this entity lives
  • Creating a named query called 'list' that lists all the entities
  • Defining an primary key id attribute, relating to 'id' in entity, that is an automatically generated value in the database, using the AUTO strategy (see http://www.objectdb.com/java/jpa/entity/generated)
  • Defining an talky attribute, relating to 'talky' in the entity, which is the 'talky' column in the database which cannot be null.

We'd normally use a database migration to ensure the table 'example' above exists, but in our case here, let's just create it in the database directly. We're also creating a sequence table so JPA can create unique primary keys.

psql -h localhost -U test_username -d test_database -c "create table example (id bigserial not null primary key, talky varchar(1000) not null);"
psql -h localhost -U test_username -d test_database -c "create table sequence (seq_name varchar(50) not null primary key, seq_count int);insert into sequence (seq_name, seq_count) values('SEQ_GEN', 1);"    

Now let's create a new request that create a JPA connections, add something to our database and lists everything in it to return.

The comments should example the basics of JPA and EntityManagers.

echo '
package com.example.YOURPROJECT;

import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.log4j.Logger;

@Path("example_jpa")
public class ExampleJPARequest {

	@Path("{example}")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public List<ExampleResource> example(@PathParam("example") String example) {
		// Get the EntityManager by creating an EntityManagerFactory via the persistence-unit name we provided.
		EntityManager entityManager = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME").createEntityManager();
		EntityTransaction transaction = entityManager.getTransaction(); // Not useful here, but useful to see
		List<ExampleEntity> list  = null;
		try {
			transaction.begin();
			// Add an entity
			ExampleEntity entity = new ExampleEntity();
			entity.setTalky(example);
			entityManager.persist(entity);
			// List entities, via the named query we defined in mapping.xml
			TypedQuery<ExampleEntity> nq = entityManager.createNamedQuery("list", ExampleEntity.class);
			list = nq.getResultList();
			// Commit the transaction
			transaction.commit();
		} catch (Exception e) {
			Logger.getLogger(getClass()).error("Problem persisting", e);
			transaction.rollback();
			throw e; // Ergo showing a 500 error. You may want to throw an exception that is not detailing stuff about your JPA connection
		} finally {
			entityManager.clear(); // Clears all the entities from the EntityManager
			entityManager.close();
		}
		
		// Adapt the entities into objects to return as JSON
		ArrayList<ExampleResource> resList = new ArrayList<ExampleResource>();
		for (ExampleEntity exampleEntity : list) {
			ExampleResource exampleItem = new ExampleResource();
			exampleItem.setStuff(exampleEntity.getTalky());
			resList.add(exampleItem);
		}
		return resList;
	}
}
' > src/main/java/com/example/YOURPROJECT/ExampleJPARequest.java

You should normally separate the database layer and entity adapters from the request layer, which can do nicely with Jersey's dependency injection, whic we'll come to later.

We can again run the project to see it in action:

gradle build
java -jar jetty-runner-9.1.0.M0.jar --port 8081 build/libs/YOUR_PROJECT_DIR.war

If you visit

http://localhost:8081/YOUR_PATH/example_jpa/ONE

and then visit

http://localhost:8081/YOUR_PATH/example_jpa/TWO

You should see the JSON

[{"stuff":"ONE"},{"stuff":"TWO"}]
java java-jpa java-web-quick-start java-jersey

Java: Testing a REST service with a clean database (using sqlite)

You can test REST responses like so with Jersey's client api.

	YourResponseObject result = service
		.path("somepath")
		.type(MediaType.APPLICATION_JSON)
		.put(YourResponseObject.class, yourInputObject);

	assertTrue(result.isSuccessful());

But your responses may depend on the state of your database.

And since you're not running your tests from a WAR, or what have you, you have no direct access to populate its seed or delete it.

The best way to do this is to create a rest method to clear the database to use during development, and remove in production.

The method to delete the database would look like:

	EntityTransaction trans = mEntityManager.getTransaction();
	trans.begin();
	Query q = mEntityManager.createQuery("delete from UserEntity");
	q.executeUpdate();
	trans.commit();
	closeEntityConnection();
	

It may be possible access the JPA database if the tests are run in a WAR, but I haven't tried that. Any experience would be welcomed in the comments.

java java-testing java-jersey java-jpa

JPA: Mapping to an existing database

In your persistence.xml you can specify an XML mapping file. Anything in this will overrule whatever is in your annotations.

Let's say the a column name is changed in a UAT database, but not in production. You can create a mapping file that remaps the annotation for the UAT database, by specifying this in your persistence.xml under the persistence-unit tag:

META-INF/db_mapping.xml

In that file you can set remap the column like so:

<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm    http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
    version="2.0">
    <entity class="org.denevell.tomcat.entities.write.AnotherThing">
       <table name="anotherthing" />
       <attributes>
         <basic name="text">
           <column name="sometext" nullable="false" />
         </basic>
       </attributes>
    </entity>
</entity-mappings>

After the car-crash of schema declarations, the entity specifies which class to map, to what table, and then the attributes within. The basic tag tells us the name of the property or field in your Java class. Then the column tags tell you want to map it to.

java-jpa java-jpa-databasemapping

JPA: Testing

You can test your JPA code by setting aside another folder structure, test/ for example, with a META-INF/ directory with a new persistence.xml file there pointing to a different database.

Here's new new persistence.xml:

 <?xml version="1.0" encoding="UTF-8" ?>
 <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"
     xmlns="http://java.sun.com/xml/ns/persistence">
   <persistence-unit name="example" transaction-type="RESOURCE_LOCAL">
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <class>org.denevell.tomcat.entities.write.AnotherThing</class>
     <properties>
       <property name="javax.persistence.jdbc.driver" value="org.sqlite.JDBC" />
       <property name="javax.persistence.jdbc.url" value="jdbc:sqlite:/home/YOURUSERNAME/test.db" />
       <property name="eclipselink.logging.level" value="ALL" />
       <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
     </properties>
   </persistence-unit>
</persistence>

The only different to the previous ones is that we're putting our database in a new location - one where the runner of the junit test has write access. And we're doing drop-and-create-tables in the table creation.

The next thing you need is the junit4 file that calls the persistence provider to test adding of the entity:

public class JPAStarterTest {
  @Test
  public void addAnotherThing() {
    // Arrange
    AnotherThing at = new AnotherThing();
    at.setText("hii");        	
    // Act
    tx.begin();
    em.persist(at);
    tx.commit();   		    
    // Assert
    assertNotNull("Id should not be null", at.getId());
    List<AnotherThing> list = em.createNamedQuery("listAll", AnotherThing.class).getResultList();
    assertEquals("Table has one entity", 1, list.size()); 
    assertEquals("Table has correcttext", "hii", list.get(0).getText());
  }

  @BeforeClass
  public static void beforeClass() {
    emf = Persistence.createEntityManagerFactory("exampletest");
    em = emf.createEntityManager();
  }
 
  @AfterClass
  public static void afterClass() {
    em.close();
    emf.close();
  }

  @Before
  public void before() {
    tx = em.getTransaction();
  }
  private static EntityManagerFactory emf;
  private static EntityManager em;
  private EntityTransaction tx;	
}

Now when you run this, ensure your new persistence.xml is in the META-INF/ directory of the classes.

java java-junit java-jpa

Page 1 of 2
next
Click me