问题: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;
/**
 * 该类主要用于 JdbcTemplate的测试,使用前需要先导入宠物店的DB Schema
 */
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() {
    /*
     * 这里使用RowCallbackHandler匿名内部类来进行回调
     * processRow方法会根据查询记录条数,循环向list中加载User对象
     */
    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"));
       
    /*
     * 这行代码将导致插入主键重复数据,并且抛出一个Spring运行时异常
     */
    userDao.createUser(INIT_USER);
  }
}
接着给出该DAO的上下文配置,见例6.31。
例6.31:dao-context.xml
    
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
         
               
        jdbcsupport/jdbc.properties
              
         
    
 
 
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   
   
   
   
 
 
 
   
     
   
 
至此,一个基本的DAO编码和配置就完成了,然而有些小组的成员对DAO发生异常处理时事务如何回滚的问题产生了疑问,L将这个问题留到了下一小节中。
6.4.4  如何声明配置JDBC事务
顺着上一小节中留下的事务疑问,L将在本小节中给出解答,和传统的处理方式不同,Spring的事务配置非常灵活,它不需要强迫客户去更改程序代码,而只是使用一种外部声明配置的方式来给业务对象加载事务特性,于是L写下了这个事务配置文件,见例6.32。
例6.32:transaction-context.xml
 
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   
     
   
 
 
   
     
   
 
 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
   
     
   
   
     
   
   
     
        PROPAGATION_REQUIRED
     
   
 
有了事务声明后,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 = ?";
  /**
   * 调用超类构造函数,传入DataSource和SQL语句
   * 该SQL语句的每一个参数需要通过declareParameter方法进行声明
   * SqlParameter声明了参数对应的由java.sql.Types定义的JDBC类型
   * compile()方法预建PreparedStatement
   */
  public UserMappingQuery(DataSource ds) {
    super(ds, RETRIEVE_USER_BY_NAME);
    super.declareParameter(
        new SqlParameter(Types.VARCHAR));
    compile();
  }
  /**
   * 覆写了mapRow()方法,ResultSet中的行集将被
   * 转换成Java对象
   */
  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
"http://www.springframework.org/dtd/spring-beans.dtd">
 
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   
     
        classpath:jdbc.properties
     
   
 
 
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   
   
   
   
 
 
   
     
   
 
为了清晰地分离,L单独给出了一个RDBMS操作对象的配置,见例6.37。
例6.37:sql-operation.xml
"http://www.springframework.org/dtd/spring-beans.dtd">
 
        class="jdbcsupport.rdbms.UserMappingQuery">
   
     
   
 
最后,给出客户测试代码,见例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属性列表中,添加如下配置:
   
     
   
回到例6.37的sql-operation.xml,添加如下的bean定义:
 
        class="jdbcsupport.rdbms. UserSqlUpdate">
   
     
   
 
最后,回到例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));
 

你可能感兴趣的:(spring,jdbc,user,string,import,bean)