< Day Day Up > |
8.4 Adding PersistenceThe CartItem object does not necessarily need to be persistent. On the other hand, you'd expect to pull products and categories from a database. J2EE application developers have long searched for a clean approach to persistence without much success. The best persistence frameworks allow transparency and do not invade the domain model. Spring lets you separate your transparent object from the data access layer. Spring then makes it easy to layer on persistence. You can use a JDBC abstraction layer, which abstracts away many of the tedious and error-prone aspects of JDBC, such as connection management and error handling. The Spring JDBC layer uses a feature called callback templates to pass control from your application to the framework. With this strategy, Spring removes the need to manage connections, result sets, and RDBMS-specific errors. This framework is useful when you want to use JDBC directly to process relational queries. Often, you'd rather deal with objects instead of relations. Spring also has an appropriate model for transparent persistence. The jPetStore uses Spring's OR mapping layer, which provides a variety of prepackaged choices. Spring now supports mapping layers for basic JDBC DAO, Hibernate, and JDO. This example uses a DAO framework called iBATIS SQL Maps to implement a Spring DAO layer. 8.4.1 The ModelEach of the Spring solutions starts with a transparent domain model. Example 8-3 starts with the transparent model object, a product. Example 8-3. Product.javapublic class Product implements Serializable { private String productId; private String categoryId; private String name; private String description; public String getProductId( ) { return productId; } public void setProductId(String productId) { this.productId = productId.trim( ); } public String getCategoryId( ) { return categoryId; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public String getName( ) { return name; } public void setName(String name) { this.name = name; } public String getDescription( ) { return description; } public void setDescription(String description) { this.description = description; } public String toString( ) { return getName( ); } } There's nothing special here. It consists purely of properties, accessed through getters and setters, and one utility method, toString. When you look into the jPetStore application, you'll find similar classes for each of the other persistent objects in the domain: Account, Order, Category, Item, and LineItem. 8.4.2 The MappingAs with Hibernate, the iBATIS SQL Maps framework has a mapping file. In it, each persistent property in your Java bean maps onto a single database column. Using SQL Maps, create all of your SQL within that mapping file as well, isolating all SQL to your XML mapping files. Example 8-4 shows the XML mapping support for Product. Example 8-4. Product.xml[1] <sql-map name="Product"> [2] <cache-model name="oneDayProduct" reference-type="WEAK" <flush-interval hours="24"/> </cache-model> [3] <result-map name="result" class="jpetstore.domain.Product"> <property name="productId" column="PRODUCTID" columnIndex="1"/> <property name="name" column="NAME" columnIndex="2"/> <property name="description" column="DESCN" columnIndex="3"/> <property name="categoryId" column="CATEGORY" columnIndex="4"/> </result-map> [4] <mapped-statement name="getProduct" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where PRODUCTID = #value# </mapped-statement> [5] <mapped-statement name="getProductListByCategory" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where CATEGORY = #value# </mapped-statement> [6] <dynamic-mapped-statement name="searchProductList" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT <dynamic prepend="WHERE"> <iterate property="keywordList" open="(" close=")" conjunction="OR"> lower(name) like #keywordList[]# OR lower(category) like #keywordList[]# OR lower(descn) like #keywordList[]# </iterate> </dynamic> </dynamic-mapped-statement> </sql-map> Here's what the annotations mean:
So far, you've seen the domain model for Product and its mapping, which contains queries. You're most of the way home. 8.4.3 The DAO InterfaceSomehow, the application must integrate with both Spring and SQL Maps. The application ties the two concepts together with a DAO interface, and a concrete implementation. Example 8-5 is the interface. Example 8-5. ProductDAO.javapublic interface ProductDao { List getProductListByCategory(String categoryId) throws DataAccessException; List searchProductList(String keywords) throws DataAccessException; Product getProduct(String productId) throws DataAccessException; } That's simple enough. You can see an interface for each of the queries defined in the mapping. Specifically, you can see an interface getProduct that finds a product by ID, one for getProductListByCategory that returns all products in a category, and one for the dynamic query based on keywords. Now, the DAO throws Spring exceptions; any logic that uses the DAO will have consistent exceptions, even if you later decide to change implementations. 8.4.4 The DAO ImplementationAll that remains is to implement the interface with SQL Map. Example 8-6 is the SQL Map implementation for Product. Example 8-6. SqlMapProductDao.javapublic class SqlMapProductDao extends SqlMapDaoSupport implements ProductDao { [1] public List getProductListByCategory(String categoryId) throws DataAccessException { return getSqlMapTemplate( ).executeQueryForList("getProductListByCategory", } [1] public Product getProduct(String productId) throws DataAccessException { return (Product) getSqlMapTemplate( ).executeQueryForObject("getProduct", productId); } [1] public List searchProductList(String keywords) throws DataAccessException { Object parameterObject = new ProductSearch(keywords); return getSqlMapTemplate( ).executeQueryForList("searchProductList", parameterObject); } /* Inner Classes */ [2] public static class ProductSearch { private List keywordList = new ArrayList( ); public ProductSearch(String keywords) { StringTokenizer splitter = new StringTokenizer(keywords, " ", false); while (splitter.hasMoreTokens( )) { this.keywordList.add("%" + splitter.nextToken( ) + "%"); } } public List getKeywordList( ) { return keywordList; } } } Here's what the annotations mean:
Now you've seen the mapping, the model, and the DAO. You have a fully persistent model. Next, access the DAO layer with code. jPetStore funnels all DAO access through a façade layer. 8.4.5 Using the Model Through a FaçadeJust as in Chapter 3, it often makes sense to have a higher-level interface for a model, called the façade. In this case, the jPetStore façade serves three purposes:
In this case, the façade is a very thin layer around all of the DAO. Through configuration and method interceptors, Spring attaches declarative transaction support to the façade. In this case, the façade is in two parts: the interface and the implementation. The interface allows you to change the implementation of the façade without impacting the rest of the code. Example 8-7 shows the interface. Example 8-7. PetStoreFacade.javapublic interface PetStoreFacade { Account getAccount(String username); Account getAccount(String username, String password); void insertAccount(Account account); void updateAccount(Account account); List getUsernameList( ); List getCategoryList( ); Category getCategory(String categoryId); List getProductListByCategory(String categoryId); List searchProductList(String keywords); Product getProduct(String productId); List getItemListByProduct(String productId); Item getItem(String itemId); boolean isItemInStock(String itemId); void insertOrder(Order order); Order getOrder(int orderId); List getOrdersByUsername(String username); } Think of this interface as a consolidated list of every method that creates, reads, updates, or deletes any Pet Store object. Notice that you do not see every method from all of the DAO. You see only the methods that we wish to expose to the rest of the world. Also, notice the naming consistency within the interface. This is important because within our configuration file, you saw the transaction support configured to propagate methods beginning with get, search, update, or insert. The implementation simply calls the underlying DAO to do the appropriate job. It must implement all of the methods in the interface. Example 8-8 is the implementation of the methods related to the ProductDAO. Example 8-8. Excerpt fromPetStoreImpl.java[1] private ProductDao productDao; .... public void setProductDao(ProductDao productDao) { this.productDao = productDao; } ... [2]public List getProductListByCategory(String categoryId) { return this.productDao.getProductListByCategory(categoryId); } public List searchProductList(String keywords) { return this.productDao.searchProductList(keywords); } ... Here's what the annotations mean:
Of course, I haven't shown the implementation of all of the interface's methods. These are only the methods related to product. They come in two parts. First, the application context wired each DAO to the façade. Spring uses reflection and the bean factory to create the product DAO and set it using the setProductDAO API. To support this, the façade needs a variable to hold the DAO and a set method to access it through reflection. Second, the implementation is simple. The façade merely passes the request through to the model layer underneath. The ultimate implementation is much more powerful, though. The façade functions like an EJB session bean with respect to declarative transaction support. Through configuration, the POJO becomes a declarative transaction coordinator! It's also a central point of control for the entire database layer. All that remains is to configure the DAO layer. 8.4.6 Configuration for the DAO LayerRecall that you have seen only the configuration for the model. Example 8-9 shows the configuration of the data layer for a single database with simple transaction management. As you'd expect, you'll see the configuration of the JDBC driver and the declaration of all of the DAO beans. Example 8-9. dataAccessContext-local.xml<beans> [1] <bean id="propertyConfigurer" class="org.springframework.beans.factory. config.PropertyPlaceholderConfigurer"> <property name="location"><value>/WEB-INF/jdbc.properties</value></property> </bean> [2] <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"><value>${jdbc.driverClassName}</value></property> <property name="url"><value>${jdbc.url}</value></property> <property name="username"><value>${jdbc.username}</value></property> <property name="password"><value>${jdbc.password}</value></property> </bean> [3] <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"><ref local="dataSource"/></property> </bean> [4] <bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean"> <property name="configLocation"> <value>classpath:/sql-map-config.xml</value></property> </bean> [5] <bean id="accountDao" class=" jpetstore.dao.ibatis.SqlMapAccountDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="categoryDao" class="jpetstore.dao.ibatis.SqlMapCategoryDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="productDao" class=" jpetstore.dao.ibatis.SqlMapProductDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="itemDao" class=" jpetstore.dao.ibatis.SqlMapItemDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="orderDao" class=" jpetstore.dao.ibatis.SqlMapOrderDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> <property name="sequenceDao"><ref local="sequenceDao"/></property> </bean> <bean id="sequenceDao" class="jpetstore.dao.ibatis.SqlMapSequenceDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> </beans> Here's what the annotations mean:
This configuration accomplishes more than just decoupling the persistence tier from the model or the view. We've also decoupled transaction management from the persistence layer, separated the transaction policy from the implementation, and isolated the data source. Take a look at the broader benefits that have been gained beyond configuration. 8.4.7 The BenefitsThat's all of the persistence code for the Product. The code for the rest of jPetStore is similar. The application effectively isolates the entire domain model within a single layer. The domain has no dependencies on any services, including the data layer. You've also encapsulated all data access into a clean and concise DAO layer, which is independent of data store. Notice what you don't see:
The end result of what we've done so far is pretty cool. We have a clean, transparent domain model and a low-maintenance service layer that's independent of our database. Each layer is neatly encapsulated. Now that we have looked at the backend logic, it's time to put a user interface on this application. |
< Day Day Up > |