[ Team LiB ] |
6.3 State ManagementAs a developer of the persistence logic for a bean-managed bean, your job is to make sure that the state of the bean matches the state of the database. The container's role is simply to let you know when interesting things relating to the bean state occur. 6.3.1 Lazy-LoadingLoading a simple bean is fairly straightforward using the tools we have discussed in this chapter and previous chapters. One of the drawbacks I mentioned for container-managed persistence, however, was the inability to perform lazy-loading. Lazy-loading is a tool that enables you to put off making queries to support complex bean relationships until that information is actually needed. An example of such a relationship includes a one-to-many relationship in which the many side requires a complex query or represents very many. Another example is an entity bean that stores a huge chunk of binary data. For binary data like a Video bean representing a video clip, you do not want to load that video into memory for several reasons. First, it is a lot of data to load into memory. In fact, you never really want the whole clip to be in memory—you want to stream it. The other reason not to load it is because it will take a long time to load and thus hold up simple queries against the video's metadata.
The following code shows the ejbLoad( ) method (without using data access objects) for a Video bean implementation: static private final String LOAD = "SELECT title, runningTime, size FROM Video WHERE videoID = ?"; public void ejbLoad( ) { PreparedStatement stmt = null; Connection conn = null; ResultSet rs = null; try { Context ctx = new InitialContext( ); DataSource ds = (DataSource)ctx.lookup("jdbc/ora"); Long id = (Long)context.getPrimaryKey( ); conn = ds.getConnection( ); stmt = conn.prepareStatement(LOAD); stmt.setLong(1, id.longValue( )); rs = stmt.executeQuery( ); if( !rs.next( ) ) { throw new EJBException("No matching value."); } videoID = id; title = rs.getString(1); runningTime = rs.getString(2); size = rs.getLong(3); if( rs.next( ) ) { throw new EJBException("Multiple matching values."); } } catch( NamingException e ) { throw new EJBException(e); } catch( SQLException e ) { throw new EJBException(e); } finally { if( rs != null ) { try { rs.close( ); } catch( SQLException e ) { } } if( stmt != null ) { try { stmt.close( ); } catch( SQLException e ) { } } if( conn != null ) { try { conn.close( ); } catch( SQLException e ) { } } } } The actual video is not loaded. The loading occurs when a client calls for it: static private final String STREAM = "SELECT dataStream FROM Video WHERE videoID = ?"; public DistributedDataInputStream getStream( ) { PreparedStatement stmt = null; Connection conn = null; ResultSet rs = null; try { Context ctx = new InitialContext( ); DataSource ds = (DataSource)ctx.lookup("jdbc/ora"); DistributedDataInputStream is ; Clob clob; conn = ds.getConnection( ); stmt = conn.prepareStatement(STREAM); stmt.setLong(1, videoID.longValue( )); rs = stmt.executeQuery( ); if( !rs.next( ) ) { throw new EJBException("No matching value."); } clob = rs.getClob(1); is = new DistributedDataInputStream(clob.getBinaryStream( )); if( rs.next( ) ) { throw new EJBException("Multiple matching values."); } return is; } catch( NamingException e ) { throw new EJBException(e); } catch( SQLException e ) { throw new EJBException(e); } finally { if( rs != null ) { try { rs.close( ); } catch( SQLException e ) { } } if( stmt != null ) { try { stmt.close( ); } catch( SQLException e ) { } } if( conn != null ) { try { conn.close( ); } catch( SQLException e ) { } } } } For this example to work, of course, you need some kind of special streaming class that will send an input stream across the network to be read by a distributed client. That class is represented in the example by the DistributedDataInputStream class. Depending on your database of choice, storing binary data in a database may be a bad idea. As a general rule of thumb, I recommend against storing any binary data in a database for a couple of reasons:
Moving binary data to the filesystem, however, has its drawbacks. Most notably, you risk data integrity when you move the binary data directly to the filesystem. Your EJB must handle both the deletion of the metadata in the database and the file on the filesystem. More problematic, however, is the fact that you cannot do both as an atomic transaction.
Regardless of the approach you choose, lazy-loading can help you gain the optimal efficiency for managing large binary and character attributes. 6.3.2 To Store or Not to StoreContainers can be a little ejbStore( ) happy. If the container thinks there is the slightest possibility that a bean could have been modified during the course of a transaction, it will trigger a call to ejbStore( ). This approach is great when all of the beans involved in a transaction actually had state changes. In a transaction that touches dozens of beans yet modifies only one or two, however, this can seriously harm system performance. A very common practice in EJB development with bean-managed persistence is to create a "dirty" flag and set it whenever a transaction actually modifies a bean. In our book example, we might have a setTitle( ) method that looks like this: public void setTitle(String ttl) { title = ttl; } We can add a simple call to: dirty = true; This call will indicate to ejbStore( ) that the bean has, in fact, undergone a state change. We can then write ejbStore( ) like this: public void ejbStore( ) { if( !dirty ) { return; } // perform the actual save } After a call to setTitle( ), ejbStore( ) saves the bean to the database. If the store-happy container, however, triggers ejbStore( ) without having modified the bean, nothing will happen. This trick thus saves at least one unnecessary trip to the database. |
[ Team LiB ] |