4.1 Patterns of Persistence


The excellent book Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley) has popularized the concept of design patterns. They are recurring forms in software development that you can capture at a low level and reuse across dissimilar applications. Within any application scope are problems you have encountered ; patterns are the result of recognizing common problems and leveraging a common solution.

People have been writing database applications for nearly three decades. Over that time, many best practices have evolved into design patterns. As we explore different modes of persistence in this book, we will see many of these patterns over and over again.

4.1.1 Division of Labor

Perhaps the most essential element of good persistence design is a clear separation of application logic into the following areas:


View logic

The view logic is responsible for displaying the user interface. It is the user 's window into the control and business logic.


Control logic

The control logic handles user actions and decides what should happen based on those actions. It handles data validation and triggers the appropriate business logic on behalf of the user.


Business logic

Business logic [1] encapsulates the basic business concepts behind your application. They provide the view with getter methods to access business data and provide the interface for creating, searching, modifying, and deleting the business objects they support.

[1] You do not need to be writing a business application to have business logic. Business logic is a generic term that refers to any of the basic concepts in your problem domain. If you are building a first-person shooter game, your "business objects" are monsters, weapons, hazards, and the like.


Data access logic

Data access logic maps business objects to the data storage layer. They are the heart of persistence.


Data storage logic

The database engine provides you with this type of logic, which simply ensures your data is not lost at application shutdown.

Separation of logic with dependencies based on simple interfaces is a core principle of object-oriented software engineering. When you capture the essence of a business concept in a business object without burdening it with other logic, you enable it to be reused in other environments. For example, a bank account object that does not contain display or data access logic can be reused with JSP, Swing, and other kinds of frontends. It can also persist against different database engines.

BEST PRACTICE : Divide application functionality into different logical components to facilitate component reuse.

This same principle extends beyond the business object layer. It also makes it easier to divide the work of building software among developers with different skills. With a good tag library, the view developer needs to know only XHTML and your tag library to write the view. The more difficult work of JDBC programming can be easily handed off to an experienced JDBC programmer without having to hand the entire application to a JDBC programmer.

BEST PRACTICE : Divide application logic into multiple tiers to match the complexity of your application.

4.1.2 Sequence Generation

In almost any database application, you need to generate unique identifiers to serve as primary keys in your database. Most databases have some sort of proprietary mechanism to help you generate sequences. Unfortunately, you cannot port a database application that relies on these proprietary schemes to other databases without changing the code that relies on those schemes.

I always recommend the use of a database-independent approach to sequence generation. Later in this chapter, I develop a sequence generator that will work for most database applications. It stores sequence seeds in the database. When an application needs a new unique number, it requests the unique number from the sequence generator. If the sequence generator has the seed in memory, it uses the following formula to create a new unique number:

 unique = (seed * 1000000) + last;  last++; 

If the seed is not in memory, it is loaded from the database, incremented, and the incremented value is stored back in the database. When the seed runs out of unique values ”when last reaches 1000000 [2] ”it loads a new seed from the database, increments that new seed, and saves the incremented seed back to the database.

[2] The value 1,000,000 depends on the system. You will want lower numbers for systems with short uptimes and larger numbers for systems with long uptimes.

This approach has several important features:

  • It generates unique values in a distributed environment. Multiple application servers can save business objects to multiple databases and still have the guarantee that the sequences being generated are unique across the entire system.

  • You do not need to go to the database every single time you generate a sequence. You go to the database only every million sequences.

  • The sequences are not tied to a specific table. You can create a sequence that is shared among several tables or even across the entire database. Similarly, you can have multiple values in the same table rely on different sequences.

BEST PRACTICE : Use database-independent sequence generation.

4.1.3 Mementos

In the division of labor discussed earlier, the data access object needs to know about the state of the object it is persisting . You could pass the business object to the data access object, but doing so would require the data access object to know the intimate details of how the business object is implemented. The memento design pattern from the Design Patterns book comes to the rescue here.

A memento enables one object to share its state with another without either object needing to know anything about the other. Consider a common situation in which you have one class (class A) that references the values of another (class B). If you delete an attribute in class B, class A will no longer compile if it has direct references to the deleted attribute of class B. In general, this behavior is exactly what you want.

Sometimes ” especially in mapping objects to a database ”you want a looser coupling between two classes. The memento pattern creates this independence. It specifically enables you to make code changes to the business objects and data access objects independently of each other. A change to the business object will not require any changes to the data access object unless you are adding new data elements or removing obsolete ones. The data access object knows that the only changes it will care about come in the form of changes in the data contained in the memento. Similarly, any change to the underlying tables in the database is hidden from the business object. It always passes its state to the data access object and lets the data access object worry about persistence issues.

BEST PRACTICE : Use mementos to pass component state between application tiers.

4.1.4 Object Caching

A database application must use the database as a persistent store ”not as a memory store. In other words, you need to pull data from the database and hold it in memory in business objects. If you go to the database every time you want to display some data about a business object, your database application will perform terribly and fail to scale at all.

On the other hand, you don't want to load the entire database in memory and keep it there. If you have a large amount of data, you will quickly run out of memory. It is therefore important to develop an object caching mechanism that strikes a solid balance between memory usage and database access.

In architectures like the EJB architecture, the application server automatically manages caching for you. The Guest Book later in this chapter, however, does not use EJBs. It therefore needs something else to manage caching. It leverages a Cache class that uses a SoftReference to cache objects loaded from the database.

BEST PRACTICE : If you are building your own persistence system, implement an efficient caching scheme to prevent exhausting system resources.

A SoftReference is a special kind of object in java.lang.ref that creates a soft reference to the object it stores. In Java, references between objects are generally strong references . For example:

 StringBuffer buffer = new StringBuffer( ); 

The reference to buffer is a strong reference. The strong reference is in force as long as the reference is in scope. If the references fall out of scope, then the object is said to be no longer strongly reachable . It is thus potentially available for garbage collection.

A soft reference is a reference via a SoftReference object. By storing an object indirectly through a SoftReference instead of directly, you make the object available for potential garbage collection while still maintaining the ability to access the object until it is garbage collected.

The Cache class implements the Java Collection interface. Internally, it even uses a HashMap internally to store data. When an application loads an object from the database, it can put it in the cache using the cache( ) method:

 public void cache(Object key, Object val) {     cache.put(key, new SoftReference(val)); } 

This method creates a soft reference around the business object and then stores the soft reference in the internal HashMap . As time goes by and the business object is no longer in use, the soft reference will expire and the memory the business object occupies will be freed. The code that checks for the existence of a specific business object in the cache thus needs to verify that the soft reference has not expired :

 public boolean contains(Object ob) {     Iterator it = cache.values( ).iterator( );         while( it.hasNext( ) ) {         SoftReference ref = (SoftReference)it.next( );         Object item = ref.get( );             if( item != null && ob.equals(item) ) {             return true;         }     }     return false; } 

The get( ) method has to perform similar checks:

 public Object get(Object key) {     SoftReference ref = (SoftReference)cache.get(key);     Object ob;         if( ref =  = null ) {         return null;     }     ob = ref.get( );     if( ob =  = null ) {         release(key);     }     return ob; } 


Java Database Best Practices
Java Database Best Practices
ISBN: 0596005229
EAN: 2147483647
Year: 2003
Pages: 102
Authors: George Reese

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net