The Artima Developer Community
Sponsored Link

Java Buzz Forum
Apache OJB Unit of Work Tutorial

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Brian McCallister

Posts: 1282
Nickname: frums
Registered: Sep, 2003

Brian McCallister is JustaProgrammer who thinks too much.
Apache OJB Unit of Work Tutorial Posted: Oct 13, 2003 6:35 AM
Reply to this message Reply

This post originated from an RSS feed registered with Java Buzz by Brian McCallister.
Original Post: Apache OJB Unit of Work Tutorial
Feed Title: Waste of Time
Feed URL: http://kasparov.skife.org/blog/index.rss
Feed Description: A simple waste of time and weblog experiment
Latest Java Buzz Posts
Latest Java Buzz Posts by Brian McCallister
Latest Posts From Waste of Time

Advertisement

Here is, finally, the promised Unit of Work tutorial implemented via Apache OJB's OTM layer.

Firstly, let's look at at a simple object model for a gift registry type application. We will look at only the portion which models user's requesting gifts (there's also a "who can see me, suggested gift, etc etc). This first class represents User's of the application:

package org.skife.kim.model; import org.skife.kim.model.Gift; import java.util.ArrayList; import java.util.List; import java.util.Collections; import java.io.Serializable; public class User implements Serializable { private List giftsRequested; private String handle; private String firstName; private String lastName; private String password; private Integer id; private User() { // For OJB } public static User newUser(String handle, String password) { User user = new User(); user.handle = handle; user.password = password; user.giftsRequested = new ArrayList(); return user; } /** * Gift Factory Method */ public Gift requestGift() { Gift gift = new Gift(this); this.giftsRequested.add(gift); return gift; } public Integer getId() { return this.id; } public List getGiftsRequested() { return Collections.unmodifiableList(this.giftsRequested); } public String getHandle() { return handle; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
This second class represents gifts that the user wants:
package org.skife.kim.model; import java.io.Serializable; /** * Gifts are created via factory methods on User */ public class Gift implements Serializable { private String name; private String description; private Integer levelOfDesire; private Double cost; private String whereToBuy; private Integer id; private User recipient; private Gift() { // For OJB } Gift(User recipient) { this.recipient = recipient; } public Integer getId() { return this.id; } public Double getCost() { return cost; } public void setCost(Double cost) { this.cost = cost; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Integer getLevelOfDesire() { return levelOfDesire; } public void setLevelOfDesire(Integer levelOfDesire) { this.levelOfDesire = levelOfDesire; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User getRecipient() { return recipient; } public String getWhereToBuy() { return whereToBuy; } public void setWhereToBuy(String whereToBuy) { this.whereToBuy = whereToBuy; } }
Notice that these classes are, for the most part, simple data containers. They know their respective primary keys (id attributes) but that is a convenience in place more for crossing the web request-application boundary so I do not have to bind them to temporary id's mapped in the user session, other than that they are purely object model.

Now, the idea behind a Unit of Work is to capture all of the changes to persistent objects and be able to commit them all in a transational way. Mr. Fowler suggests using an interface where you attach objects, and mark them dirty if they get dirty. There is a potential race condition in this where an object can be retrieved, chanegd by another thread, then be attached to the Unit of Work. To get around this I prefer to pass the unit of work into the object repository used to retrieve objects. As the repository manages the object lifecycles, and needs to know about the underlieing persistence framework anyway it makes a good place to make objects transaction on their unit of work. So, as I am showing real code I have a BaseRepository which only knows how to add and remove objects to the underlieing persistence layer:

package org.skife.kim.infra; import org.apache.ojb.otm.lock.LockingException; public class BaseRepository { public static void add(Object user, Unit unit) throws UnitException { try { unit.getConnection().makePersistent(user); } catch (LockingException e) { throw new UnitException("Unable to add object", e); } } public static void remove(Object thing, Unit unit) throws UnitException { try { unit.getConnection().deletePersistent(thing); } catch (LockingException e) { throw new UnitException("Unable to add object", e); } } }
I also have a repository for each queryable object in the domain model which encapsulates the common queries (it still typically provides a general purpose query engine, but I am leaving that out of this tutorial as it is irrelevent):
package org.skife.kim.infra; import org.skife.kim.infra.BaseRepository; import org.skife.kim.infra.Unit; import org.skife.kim.infra.UnitException; import org.skife.kim.model.User; import org.apache.ojb.odmg.oql.EnhancedOQLQuery; import java.util.Iterator; public class UserRepository extends BaseRepository { public static User findById(Integer id, Unit unit) throws UnitException { try { EnhancedOQLQuery query = unit.getConnection().newOQLQuery(); query.create("select allprofiles from " + User.class.getName() + " where id = $1"); query.bind(id); User user = null; Iterator i = unit.getConnection().getIteratorByOQLQuery(query); if (i.hasNext()) user = (User) i.next(); return user; } catch (Exception e) { throw new UnitException("Unable to find User: " + e.getMessage(), e); } } }
Here are the first places we see the Unit of Work in use (I call it Unit as it is faster to type). In this implementation the Unit of Work provides a package-local accessor to retrieve an OTMConnection which provides access to Apache OJB. As we will see, a transaction is implicit in our Unit of Work, so once a connection is retrieved it can be used freely. Every retrieval, addition, or removal done via the repository will be done within context of the Unit of Work passed in to it.

Now, the Unit of Work is a straightforward beast that just grabs an OTMConnection and starts a Transaction on it. The Unit then provides the transactional context to whatever needs it and can be committed or rolled back via its two publicly accessible methods:

package org.skife.kim.infra; import org.apache.ojb.otm.core.Transaction; import org.apache.ojb.otm.OTMConnection; import org.apache.ojb.otm.kit.SimpleKit; import org.apache.ojb.broker.PersistenceBrokerFactory; /** * Unit of Work shortened for typing purposes * * TODO: Add a factory method that takes a long argument and will rollback the transaction * after an elapsed time has passed. Each getConnection() call extends the time * by the initial value. This allows for clearing the connection before the session * times out in web apps */ public class Unit { private Transaction transaction; private OTMConnection connection; private Unit() { this.connection = SimpleKit.getInstance().acquireConnection(PersistenceBrokerFactory.getDefaultKey()); this.transaction = SimpleKit.getInstance().getTransaction(this.connection); } public static Unit begin() { Unit unit = new Unit(); unit.transaction.begin(); return unit; } /** * @throws org.skife.kim.infra.UnitException if the Unit of Work has already been committed * or rolled back - either via this or the underlying * Transaction/OTMConnection */ public void commit() throws UnitException { if (! transaction.isInProgress()) { throw new UnitException("Unit of work already closed"); } this.transaction.commit(); this.connection.close(); } /** * @throws org.skife.kim.infra.UnitException if the Unit of Work has already been committed * or rolled back - either via this or the underlying * Transaction/OTMConnection */ public void rollback() throws UnitException { if (! transaction.isInProgress()) { throw new UnitException("Unit of work already closed"); } this.transaction.rollback(); this.connection.close(); } /** * @return the OTMConnection in use by this Unit. It is already transactional */ OTMConnection getConnection() { return this.connection; } }
A Unit is created via the Unit.begin() factory method which obtains the OTMConnection and starts its transaction. A Unit is a lightweight one-use object. Once it is committed, it can be thrown away. It wouldn't be a lot of work to make them reusable, but I like thinking of them as one-shot objects. If this turns out to be a bog on performance I can muddy it up and make the reusable -- but the profiler will tell me if that matters. Once it is started (begin) it must be committed or rolled back or there is a potential for resource leakage as the Unit holds a transaction and connection. Finally, it provides a package-local means for the repository implementations to retrieve its transactionally bound OTMConnection.

So lets use it -- here I ripped out the actual logic part of a generic Command type object -- it returns a Map of results

public Map go() throws Exception { Map map = new HashMap(); Unit unit = Unit.begin(); User user = UserRepository.findById(this.forUserId, unit); if (user == null) { List errors = (map.get(ERRORS) == null ? new ArrayList() : (List) map.get(ERRORS)); errors.add("No user was found for forUserId"); map.put(ERRORS, errors); return map; } Gift gift = user.requestGift(); BeanUtils.copyProperties(gift, this); BaseRepository.add(gift, unit); unit.commit(); map.put(NEW_GIFT, gift); return map; }
This starts a Unit of Work via @Unit.begin() @. Notice that the query for the User against the UserRepository and the addition of the Gift to the BaseRepository both pass in the Unit created for this transaction so that all changes are bound to the transaction. Looking at it now I should probably have captured the thrown exception from the commit so that I could roll it back (and close the OTMConnection so I will have to go make a change to the application. Hard one to write a unit test for as it is a resource depletion bug. Anyway, there it is.

This all works because the OTM (Object Transaction Manager) in OJB implements all the work for object level transactions already. All the Unit really does in this case is make it convenient to program with (will save you a lot of hassle -- it doesn't add any real functionality. However, convenience is good so I think it is a worthwhile abstraction. If direct access to the OTMConnection is needed than the unit.getConnection() can be made public. I prefer to keep it hidden so that the already tightly coupled Unit and Repository implementations are the only ones that need to know about the underlying OJB implementation. Still, that might be overkill.

The strengths of this implementation are that it works against pretty much any database in common use, and even Prevayler as there is an OJB interface to Prevayler in CVS at the moment. It protects transactional integrity all the way from object materialization through the end of the transaction -- there is no makeDirty(...) type functionality. Any change made to a transactional object is captured. A subtle benefit, really a side effect so I don't know if it should be used or not, is that once an object retrieved within a transaction is unbound (ie, the transaction is committed) it becomes a simple transfer object where changes are not committed back to the persistence store. This can be passed around to a web application without fear of the application changing anything.

Read: Apache OJB Unit of Work Tutorial

Topic: Winter is Coming Previous Topic   Next Topic Topic: Started writing a user guide for Pebble

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use