天天看點

問題:Spring如何支援JDBC

問題:Spring如何支援JDBC 設計師L常常發現在項目中經常會出現冗長易錯的JDBC代碼,Spring為了改善這種情況,提供了一系列強大的API使用者簡化傳統JDBC的編碼。 6.4.1  什麼是JdbcTemplate Spring提供了一個JdbcTemplate模闆類,該類是一個助手類。它在JDBC API之上做一層小小的抽象封裝,大大降低了JDBC API使用冗繁和易錯,并且JdbcTemplate還保障了資源建立、釋放、關閉等易錯操作的正确執行,提供了處理JDBC異常的統一方式。 說明  在使用JdbcTemplate時,需要和一個實作javax.sql.DataSource接口的資料源對象進行合作。資料源可以看作是一個通用的連接配接工廠,它允許容器或架構在應用程式代碼中,隐藏連接配接池和事務的管理操作。 6.4.2  如何使用JdbcTemplate 這裡将給出一個簡單的示例,展示如何通過程式設計方式來使用JdbcTemplate,該例實作了JDBC最基本的CRUD操作(新增、查找、更新、删除)。其中的DB Schema是Spring寵物店的。下面首先給出一個用以測試的實體類User和jdbc資料源的屬性檔案,見例6.26和例6.27。 例6.26:User.java package jdbcsupport; public class User {      private String userName;      private String password;      public User(String userName, String password) {           this.userName = userName;           this.password = password;      }      public String getPassword() {           return password;      }      public void setPassword(String password) {           this.password = password;      }      public String getUserName() {           return userName;      }      public void setUserName(String userName) {           this.userName = userName;      } } 例6.27:jdbc.properties jdbc.driverClassName = org.postgresql.Driver jdbc.url = jdbc:postgresql://127.0.0.1:5432/jpetstore jdbc.username = postgres jdbc.password = 1111 接着給出完整的JdbcTemplate測試類,見例6.28。 例6.28:JdbcTemplateTest.java package jdbcsupport; import java.io.FileInputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.commons.dbcp.BasicDataSource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; public class JdbcTemplateTest {   public static final String CREATE  USER =     "INSERT INTO SIGNON (USERNAME, PASSWORD) VALUES (?, ?)";   public static final String UPDATE_USER =     "UPDATE SIGNON SET PASSWORD = ? WHERE USERNAME = ?";   public static final String DELETE_USER =     "DELETE FROM SIGNON WHERE USERNAME = ?";   public static final String RETRIEVE_USER_COUNT =     "SELECT COUNT(*) FROM SIGNON";   public static final String RETRIEVE_USER_BY_NAME =     "SELECT * FROM SIGNON WHERE USERNAME = ?";   public static final String RETRIEVE_ALL_USER =     "SELECT * FROM SIGNON";   public static final User INIT_USER1 =     new User("j2ee", "j2ee");   public static final User TEST_USER1 =     new User("L1", "1111");   public static final User TEST_USER2 =     new User("L2", "2222");   public static final String STATMENT1 =     "DELETE FROM signon";   public static final String STATMENT2 =     "INSERT INTO signon VALUES ('j2ee','j2ee')";   public static final String STATMENT3 =     "INSERT INTO signon VALUES ('ACID','ACID')";   private JdbcTemplate template;   public void init() throws Exception {     BasicDataSource ds = new BasicDataSource();     Properties prop = new Properties();     prop.load(new FileInputStream("jdbcsupport/jdbc.properties"));     ds.setDriverClassName(prop.getProperty("jdbc.driverClassName"));     ds.setUrl(prop.getProperty("jdbc.url"));     ds.setUsername(prop.getProperty("jdbc.username"));     ds.setPassword(prop.getProperty("jdbc.password"));     template = new JdbcTemplate(ds);     //批量更新,執行靜态SQL STATEMENT     template.batchUpdate(         new String[]{STATMENT1, STATMENT2, STATMENT3});   }     public void testCreate() {     //使用匿名内部類進行SQL語句的條件指派     template.update(CREATE_USER,         new PreparedStatementSetter() {       public void setValues(PreparedStatement ps)         throws SQLException {         ps.setString(1, TEST_USER1.getUserName());         ps.setString(2, TEST_USER1.getPassword());       }     });         //通過對象數組,對SQL語句條件進行指派     template.update(CREATE_USER,         new String[]{TEST_USER2.getUserName(),                      TEST_USER2.getPassword()});   }         public void testUpdate() {     template.update(UPDATE_USER,         new String[]{"5555",                      TEST_USER2.getUserName()});   }         public void testDelete() {     template.update(DELETE_USER,         new String[]{TEST_USER1.getUserName()});   }       public void testRetrieveBasicType() {     template.queryForInt(RETRIEVE_USER_COUNT);   }         public void testRetrieveObject() {        //通過RowMapper進行對象的手工映射     template.queryForObject(RETRIEVE_USER_BY_NAME,         new String[]{INIT_USER1.getUserName()},         new RowMapper() {           public Object mapRow(ResultSet rs, int rowNum)             throws SQLException {             return new User(rs.getString(1),                             rs.getString(2));           }     });   }           public void testRetrieveList() {         final List list = new ArrayList();     template.query(RETRIEVE_ALL_USER, new RowCallbackHandler() {       public void processRow(ResultSet rs) throws SQLException {         User user = new User(rs.getString(1), rs.getString(2));         list.add(user);       }     });   }     public static void main(String[] args) {     JdbcTemplateTest test = new JdbcTemplateTest();     test.init();     test.testCreate();     test.testUpdate();     test.testDelete();     test.testRetrieveBasicType();     test.testRetrieveObject();     test.testRetrieveList();   } } 6.4.3  如何使用JdbcDaoSupport 設計師L發現,除了支援基本JDBC操作的JdbcTemplate,Spring還提供了一個JdbcDaoSupport類,它是JDBC資料通路對象(DAO)的抽象基類。本小節将展示JdbcDaoSupport的使用方法。L依然沿用上小節中的User類,首先為User類專門撰寫一個UserDao類,使該DAO繼承JdbcDaoSupport類,見例6.29。 例6.29:UserDao.java package jdbcsupport; public class UserDao extends JdbcDaoSupport {        public static final String CREATE_USER =     "INSERT INTO SIGNON (USERNAME, PASSWORD) VALUES (?, ?)";       public static final String UPDATE_USER =     "UPDATE SIGNON SET PASSWORD = ? WHERE USERNAME = ?";      public void createUser(final User user) {     getJdbcTemplate().update(CREATE_USER,         new PreparedStatementSetter() {       public void setValues(PreparedStatement ps)         throws SQLException {         ps.setString(1, user.getUserName());         ps.setString(2, user.getPassword());       }     });   }     public void updateUser(final User user) {     getJdbcTemplate().update(UPDATE_USER,         new String[]{user.getPassword(),                      user.getUserName()}); } 為了使該示例更加真實,L又撰寫了一個業務對象,Spring可以向該業務對象注入DAO執行個體,見例6.30。 例6.30:BusinessObject.java package jdbcsupport; public class BusinessObject {   private UserDao userDao;     public static final User TEST_USER =     new User("L", "1111");       public void setUserDao(UserDao userDao) {     this.userDao = userDao;   }     public void errorCreateAndUpdateUser() {     userDao.createUser(TEST_USER);         userDao.updateUser(         new User(TEST_USER.getUserName(), "2222"));                 userDao.createUser(INIT_USER);   } } 接着給出該DAO的上下文配置,見例6.31。 例6.31:dao-context.xml <beans>      <bean id="propertyConfigurer"     class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">           <property name="locations">                <list>         <value> jdbcsupport/jdbc.properties</value>                </list>           </property>      </bean>     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">     <property name="driverClassName" value="${jdbc.driverClassName}"/>     <property name="url" value="${jdbc.url}"/>     <property name="username" value="${jdbc.username}"/>     <property name="password" value="${jdbc.password}"/>   </bean>     <bean id="userDao" class="jdbcsupport.UserDao">     <property name="dataSource">       <ref local="dataSource"/>     </property>   </bean> </beans> 至此,一個基本的DAO編碼和配置就完成了,然而有些小組的成員對DAO發生異常處理時事務如何復原的問題産生了疑問,L将這個問題留到了下一小節中。 6.4.4  如何聲明配置JDBC事務 順着上一小節中留下的事務疑問,L将在本小節中給出解答,和傳統的處理方式不同,Spring的事務配置非常靈活,它不需要強迫客戶去更改程式代碼,而隻是使用一種外部聲明配置的方式來給業務對象加載事務特性,于是L寫下了這個事務配置檔案,見例6.32。 例6.32:transaction-context.xml <beans>   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     <property name="dataSource" >       <ref bean="dataSource" />     </property>   </bean>   <bean id="businessBean" class="jdbcsupport.BusinessObject">     <property name="userDao">       <ref bean="userDao"/>     </property>   </bean>   <bean id="businessProxy"     class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">     <property name="transactionManager">       <ref bean="transactionManager" />     </property>     <property name="target">       <ref bean="businessBean" />     </property>     <property name="transactionAttributes">       <props>         <prop key="errorCreateAndUpdate*">PROPAGATION_REQUIRED</prop>       </props>     </property>   </bean> </beans> 有了事務聲明後,L給出了最後的測試代碼,如果調用了業務對象上的方法errorCreateAndUpdateUser (),由于它被聲明為具有事務處理能力的方法。是以執行它一旦産生異常,将導緻事務復原,抛出一個Spring運作時異常,見例6.33。 例6.33:BusinessObjectTest.java package jdbcsupport; public class BusinessObjectTest {     public static void main(String[] args) {     ApplicationContext ctx = new ClassPathXmlApplicationContext(         new String[]{"daosupport/transaction-context.xml",                      "daosupport/dao-context.xml"});     BusinessObject bo = (BusinessObject)ctx.getBean("businessBeanProxy");         try {       bo.errorCreateAndUpdateUser();     } catch (Exception e) {       System.out.println(e);     }       } } 6.4.5  什麼是RDBMS Operation 設計師L在研究了JdbcTemplate和JdbcSupport之後,又發現了一些Spring自身提供的一些非常有趣實用的JDBC抽象輔助API類階層,這些類階層的基類是一個名為RdbmsOperation的類。通過研究,L發現這些類其實是對RDBMS查詢、更新甚至存儲過程的抽象,Spring将這些RDBMS操作過程單獨地抽象成對象的形式。L仔細想了一下,終于發現了RdbmsOperation類階層的優點:它們為RDBMS操作的重用提供了可能,由于這些RDBMS操作對象執行個體隻需要被建立一次,是以在程式中提升了性能。通常,這些執行個體會被儲存在DAO的執行個體變量中,它們都經過了預編譯并且是線程安全的。L将在下文中對其中的兩個類進行介紹,它們分别是MappingSqlQuery和SqlUpdate。 6.4.6  如何使用MappingSqlQuery MappingSqlQuery是一個查詢對象,它的子類需要實作mapRow(ResultSet, int) 方法,該方法會将JDBC ResultSet行集轉換成Java對象。接着,L将依然改寫上文中的UserDao以展示MappingSqlQuery的使用,見例6.34。 例6.34:UserDao.java package jdbcsupport.rdbms; import java.util.List; import org.springframework.jdbc.object.MappingSqlQuery; import org.springframework.jdbc.object.SqlUpdate; import jdbcsupport.User; public class UserDao {   private MappingSqlQuery sqlQuery;   public void setMappingSqlQuery(MappingSqlQuery sqlQuery) {     this.sqlQuery = sqlQuery;   }   public User retrieveUserByName(String name) {     List userList = sqlQuery.execute(new String[]{name});     return (userList != null) ? (User)userList.get(0) : null;   } } 可以發現,該DAO中沒有任何SQL語句,取而代之的是一個SQL對象,該對象是一個MappingSqlQuery類的執行個體,見例6.35。 例6.35:UserMappingQuery.java package jdbcsupport.rdbms; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.MappingSqlQuery; import jdbcsupport.User; public class UserMappingQuery extends MappingSqlQuery {     public static final String RETRIEVE_USER_BY_NAME =     "SELECT * FROM SIGNON WHERE USERNAME = ?";     public UserMappingQuery(DataSource ds) {     super(ds, RETRIEVE_USER_BY_NAME);     super.declareParameter(         new SqlParameter(Types.VARCHAR));     compile();   }     protected Object mapRow(ResultSet rs, int rowNum)     throws SQLException {     User user = new User(rs.getString(1),                          rs.getString(2));     return user;   } } 接着給出相關的Spring配置檔案,首先是DAO的上下文配置,見例6.36。 例6.36:dao-context.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">     <property name="locations">       <list>         <value>classpath:jdbc.properties</value>       </list>     </property>   </bean>   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">     <property name="driverClassName" value="${jdbc.driverClassName}"/>     <property name="url" value="${jdbc.url}"/>     <property name="username" value="${jdbc.username}"/>     <property name="password" value="${jdbc.password}"/>   </bean>   <bean id="userDao" class="jdbcsupport.rdbms.UserDao">     <property name="mappingSqlQuery">       <ref bean="userQuery"/>     </property>   </bean> </beans> 為了清晰地分離,L單獨給出了一個RDBMS操作對象的配置,見例6.37。 例6.37:sql-operation.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>   <bean id="userQuery"         class="jdbcsupport.rdbms.UserMappingQuery">     <constructor-arg>       <ref bean="dataSource"/>     </constructor-arg>   </bean> </beans> 最後,給出客戶測試代碼,見例6.38。 例6.38:RdbmsOpTest.java package jdbcsupport.rdbms; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import jdbcsupport.User; //該類main函數運作前,需要先導入寵物店的DB Schema public class RdbmsOpTest {     private static final User INIT_USER =     new User("j2ee", "j2ee");     public static void main(String[] args) {     ApplicationContext ctx = new ClassPathXmlApplicationContext(         new String[]{"jdbcsupport/rdbms/dao-context.xml",                      "jdbcsupport/rdbms/sql-operation.xml"});     UserDao dao = (UserDao)ctx.getBean("userDao");         User user = dao.retrieveUserByName(INIT_USER.getUserName());         System.out.println(user.getUserName().equals(INIT_USER.getUserName()));   } } 6.4.7  如何使用SqlUpdate 在了解了查詢對象MappingSqlQuery後,本小節将展示SQL更新對象SqlUpdate的使用方法。回到例6.34的UserDao,L向其中添加了相應的更新方法,見例6.39。 例6.39:UserDao.java … public class UserDao { …   private SqlUpdate sqlUpdate;   public void setSqlUpdate(SqlUpdate sqlUpdate) {     this.sqlUpdate = sqlUpdate;   }   public void updateUser(User user) {     sqlUpdate.update(user.getPassword(), user.getUserName());   } } 接着給出SqlUpdate的實作類,見例6.40。 例6.40:UserSqlUpdate.java package jdbcsupport.rdbms; import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.SqlUpdate; public class UserSqlUpdate extends SqlUpdate {   public static final String UPDATE_USER =     "UPDATE SIGNON SET PASSWORD = ? WHERE USERNAME = ?";       public UserSqlUpdate(DataSource ds) {     super(ds, UPDATE_USER);     super.declareParameter(new SqlParameter(Types.VARCHAR));     super.declareParameter(new SqlParameter(Types.VARCHAR));     compile();   } } 同樣,回到例6.36的dao-context.xml,在id為userDao的Bean屬性清單中,添加如下配置:     <property name="sqlUpdate">       <ref bean="userUpdate"/>     </property> 回到例6.37的sql-operation.xml,添加如下的bean定義:   <bean id="userUpdate"         class="jdbcsupport.rdbms. UserSqlUpdate">     <constructor-arg>       <ref bean="dataSource"/>     </constructor-arg>   </bean> 最後,回到例6.38的RdbmsOpTest、Java的main函數中,添加如下測試代碼:     String timeSave = String.valueOf(System.currentTimeMillis());           dao.updateUser(         new User(INIT_USER.getUserName(), timeSave));             User user = dao.retrieveUserByName(INIT_USER.getUserName());         System.out.println(user.getPassword(), timeSave));