Fork me on GitHub

Transaction HTTP Server Tutorial

This tutorial demonstrates providing transaction context to an OfficeFloor web application. OfficeFloor refers to context as Governance.

The example used for this tutorial is a managing a list of users. The user name and full name are stored in separate tables to require a transaction to safely create a user.

TransactionHttpServer screen shot.

As the user name and full name are both required, the example application will not create the user unless both are supplied.

Download Tutorial Source

Configuration

The following is the application.woof configuration for the example application.

application.woof configuration.

The transaction governance is added via right clicking and adding a governance. On selecting the JPA transaction the transaction governance is added. For the governance to apply a governance area must be added. All templates and sections within the governance area are then subject to the governance. The above configuration shows the CreateUser section to be under transaction management (governance).

The exception handling configured above has the web page rendered again in the case of a persistence failure. Should the user name or full name not be supplied the transaction is rolled back due to persistence failure. The page is then rendered with only the contents committed to the database.

Remaining configuration

The remaining configuration is as follows and included for completeness of this tutorial. See the other tutorials for more details.

Database Setup

public class Setup {

	public void setupDatabase(DataSource dataSource) throws SQLException {

		Connection connection = dataSource.getConnection();
		try {

			// Create the user name table
			connection
					.createStatement()
					.execute(
							"CREATE TABLE USER ( ID IDENTITY PRIMARY KEY, USERNAME VARCHAR(20) NOT NULL )");

			// Add user
			connection.createStatement().execute(
					"INSERT INTO USER ( USERNAME) VALUES ( 'daniel' )");

			// Create the person details table
			connection
					.createStatement()
					.execute(
							"CREATE TABLE PERSON ( ID IDENTITY PRIMARY KEY, USER_ID INTEGER NOT NULL, FULLNAME VARCHAR(50) NOT NULL, "
									+ " CONSTRAINT USER_FK FOREIGN KEY ( USER_ID ) REFERENCES USER ( ID ) )");

			// Add person
			ResultSet results = connection.createStatement().executeQuery(
					"SELECT ID FROM USER WHERE USERNAME = 'daniel'");
			results.next();
			long userId = results.getLong("ID");
			connection.createStatement().execute(
					"INSERT INTO PERSON ( USER_ID, FULLNAME ) VALUES ( " + userId
							+ ", 'Daniel Sagenschneider' )");

		} finally {
			connection.close();
		}
	}

}

application.objects

<objects>

	<managed-object source="net.officefloor.plugin.jdbc.datasource.DataSourceManagedObjectSource" type="javax.sql.DataSource">
		<property-file path="datasource.properties" />
	</managed-object>

	<managed-object source="net.officefloor.plugin.jpa.JpaEntityManagerManagedObjectSource" type="javax.persistence.EntityManager">
		<property name="persistence.unit.name" value="example" />
		<team name="CLOSE" type="javax.persistence.EntityManager" />
	</managed-object>

</objects>

The team is required for the EntityManager object as a Task is triggered to automatically close the EntityManager. OfficeFloor enables ManagedObjectSource to clean up themselves so that this code need not be in the POJOs.

datasource.properties

data.source.class.name=org.hsqldb.jdbc.jdbcDataSource
database=jdbc:hsqldb:mem:exampleDb
user=sa

Code

Governance provides context to the ManagedObject instances used by the Task. In the example application it is providing a transaction (context) to the EntityManager for creating the user. Governance interacts with the extension interfaces of the ManagedObject.

As Governance only applies to the ManagedObject instances it is unobtrusive to the POJO methods. Therefore see the other tutorials for explanation of the example application's code.

The code is included for the completeness of this tutorial.

Template

<html>
	<body>
		<br />
		<table border="1">
			<tr>
				<td>User</td>
				<td>Name</td>
			</tr>
			<!-- {Users} -->
			<tr>
				<td>${userName}</td>
				<td>${fullName}</td>
			</td>
			<!-- {EndUsers} -->
		</table>
		<br />
		<form action="#{create}">
			User: <input name="userName" type="text" />
			Name: <input name="fullName" type="text" />
			<input type="submit" value="Add" />
		</form>
	</body>
</html>

Template Logic

public class UsersLogic {

	public UserProperties[] getUsers(EntityManager entityManager) {

		// Obtain the users
		Query query = entityManager.createQuery("SELECT U FROM User U");
		List<User> list = query.getResultList();
		List<UserProperties> users = new ArrayList<UserProperties>();
		for (User user : list) {
			users.add(new UserProperties(user.getUserName(), user.getPerson()
					.getFullName()));
		}

		// Return the users
		return users.toArray(new UserProperties[list.size()]);
	}

	@NextTask("createUser")
	public UserProperties create(UserProperties user) {
		return user;
	}

}

Create User Service

public class UserService {

	@NextTask("userAdded")
	public void createUser(@Parameter UserProperties properties,
			EntityManager entityManager) {
		User user = new User();
		user.setUserName(nullBlankString(properties.getUserName()));
		Person person = new Person();
		person.setFullName(nullBlankString(properties.getFullName()));
		person.setUser(user);
		entityManager.persist(person);
	}

	private static String nullBlankString(String value) {
		return ((value == null) || (value.trim().length() == 0)) ? null : value;
	}
}

Entities

The example application uses the Project Lombok to simplify the coding of the JPA entities.

@Data
@Entity
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	private String userName;

	@OneToOne(mappedBy = "user", fetch = FetchType.EAGER)
	private Person person;
}
@Data
@Entity
public class Person {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	private String fullName;

	@OneToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "USER_ID")
	private User user;
}

Unit Test

The following unit test makes requests to create users.

	public void testCreateUser() throws Exception {

		// Request page
		this.doRequest("http://localhost:7878/users.woof");

		// Create user with all details
		this.doRequest("http://localhost:7878/users-create.woof?username=melanie&fullname=Melanie+Sagenschneider");

		// Attempt to create user that will fail database constraints
		this.doRequest("http://localhost:7878/users-create.woof?username=joe");

		// Validate melanie added
		EntityManager manager = Persistence.createEntityManagerFactory(
				"example").createEntityManager();
		User melanie = (User) manager.createQuery(
				"SELECT U FROM User U WHERE U.userName = 'melanie'")
				.getSingleResult();
		assertEquals("Melanie created", "Melanie Sagenschneider", melanie
				.getPerson().getFullName());

		// Validate joe not added
		try {
			manager.createQuery("SELECT U FROM User U WHERE U.userName = 'joe'")
					.getSingleResult();
			fail("Should not find Joe");
		} catch (NoResultException ex) {
		}
	}

	private void doRequest(String url) throws Exception {
		HttpResponse response = this.client.execute(new HttpGet(url));
		assertEquals("Request should be successful", 200, response
				.getStatusLine().getStatusCode());
		response.getEntity().writeTo(System.out);
	}

Two users are attempt to be created

  • first user is created
  • second user is not created due to missing data

Next

The next tutorial looks at Thread Injection.