[ Team LiB ] |
4.1 Patterns of PersistenceThe 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 LaborPerhaps the most essential element of good persistence design is a clear separation of application logic into the following areas:
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.
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.
4.1.2 Sequence GenerationIn 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.
This approach has several important features:
4.1.3 MementosIn 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.
4.1.4 Object CachingA 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.
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; } |
[ Team LiB ] |