(1)在Spring应用中使用JDBC
Spring对JDBC进行了良好的封装,通过提供相应的模板和辅助类,在相当程度上降低了JDBC操作的复杂性。并且得益于Spring良好的隔离设计,JDBC封装类库可以脱离Spring Context独立使用,也就是说,即使系统并没有采用Spring作为结构性框架,我们也可以单独使用Spring的JDBC部分来改善我们的代码。
<1>回顾JDBC方式进行数据访问的步骤:
1>指定数据库连接参数
2>打开数据库连接
3>声明SQL语句
4>预编译并执行SQL语句
5>遍历查询结果(如果需要的话)
6>处理每一次遍历操作
7>处理抛出的任何异常
8>处理事务
9>关闭数据库连接
综上所述,开发人员在使用JDBC进行数据访问时需要不断重复以上单调乏味的底层细节处理工作。Spring JDBC抽象框架所带来的价值将大大减少开发人员的编码工作。
<2>Spring JDBC简介
Spring提供的JDBC抽象框架由core, datasource,object和 support四个不同的包组成。
org.springframework.jdbc.core包里定义了提供核心功能的类。
org.springframework.jdbc.datasource包里有一个用以简化数据源访问的工具类, 以及各种数据源的简单实现,以被用来在J2EE容器之外不经修改地测试JDBC代码。 这个工具类提供了从JNDI获得连接和可能用到的关闭连接的静态方法。 它支持绑定线程的连接,比如被用于DataSourceTransactionManager。
org.springframework.jdbc.object包里是把关系数据库的查询, 更新和存储过程封装为线程安全并可重用对象的类。尽管查询返回的对象理所当然的“脱离”了数据库连接。 这个JDBC的高层抽象依赖于org.springframework.jdbc.core包中所实现的底层抽象。
org.springframework.jdbc.support包中你可以找到 SQLException的转换功能和一些工具类。
<3>JdbcTemplate类
JdbcTemplate类在JDBC核心包中最重要的类。它简化了JDBC的使用, 因为它处理了资源的建立和释放。 它帮助你避免一些常见的错误,比如忘了总要关闭连接。它运行核心的JDBC工作流, 如Statement的建立和执行,而只需要应用程序代码提供SQL和提取结果。这个类执行SQL查询, 更新或者调用存储过程,模拟结果集的迭代以及提取返回参数值。它还捕捉JDBC的异常并将它们转换成 org.springframework.dao包中定义的通用的,能够提供更多信息的异常体系。
<4>在Spring中配置数据源
示例配置文件:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/test</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value>root</value> </property> </bean> |
<5>执行Statement
要执行一个SQL,几乎不需要代码。你所需要的全部仅仅是一个数据源和一个JdbcTemplate。 一旦你得到了它们,你将可以使用JdbcTemplate提供的大量方便的方法。 下面是一个例子,它显示了建立一张表的最小的但有完整功能的类。
部分代码示例
public class ExecuteAStatement { private JdbcTemplate jt; private DataSource dataSource;
public void doExecute() { jt = new JdbcTemplate(dataSource); jt.execute("create table mytable (id int, name varchar(100))"); }
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } |
<6>执行查询
除了execute方法,还有大量的查询方法。其中的一些被用来执行那些只返回单个值的查询。 也许你需要得到合计或者某一行中的一个特定的值。如果是这种情况,你可以使用queryForInt, queryForLong或者queryForObject。 后者将会把返回的JDBC类型转换成参数中指定的Java类。如果类型转换无效,那么将会抛出一个InvalidDataAccessApiUsageException。 下面的例子有两个查询方法,一个查询得到int,另一个查询得到String。
部分代码示例:
public class RunAQuery { private JdbcTemplate jt; private DataSource dataSource;
public int getCount() { jt = new JdbcTemplate(dataSource); int count = jt.queryForInt("select count(*) from mytable"); return count; }
public String getName() { jt = new JdbcTemplate(dataSource); String name = (String) jt.queryForObject("select name from mytable", java.lang.String.class); return name; }
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } |
除了得到单一结果的查询方法之外,还有一些方法,可以得到一个包含查询返回的数据的List。 其中最通用的一个方法是queryForList,它返回一个List, 其中每一项都是一个表示字段值的Map。 你用下面的代码在上面的例子中增加一个方法来得到一个所有记录行的List:
部分代码示例:
public List getList() { jt = new JdbcTemplate(dataSource); List rows = jt.queryForList("select * from mytable"); return rows; }
|
返回的List会以下面的形式: [{name=Bob, id=1}, {name=Mary, id=2}]
<7>更新数据
还有很多更新的方法可以供你使用。我将展示一个例子,说明通过某一个主键更新一个字段。 在这个例子里,我用了一个使用榜定参数的SQL Statement。大多数查询和更新的方法都有这个功能。 参数值通过对象数组传递。
更新数据示例:
public class ExecuteAnUpdate { private JdbcTemplate jt; private DataSource dataSource;
public void setName(int id, String name) { jt = new JdbcTemplate(dataSource); jt.update ("update mytable set name =? where id = ?", new Object[] {name, new Integer(id)}); }
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } |
注:insert、delete操作与update基本相同,只需传入相应的SQL语句即可!
<8>JDBC操作的Java对象化
org.springframework.jdbc.object包由一些允许你以更面向对象的方式访问数据库的类组成。你可以执行查询并获得一个包含业务对象的List, 这些业务对象关系数据的字段值映射成它们的属性。你也可以执行存储过程,更新,删除和插入操作。
SqlQuery:
这是一个表示SQL查询的可重用的而且线程安全的对象。子类必须实现newResultReader()方法来提供一个对象,它能在循环处理ResultSet的时候保存结果。这个类很少被直接使用,而使用它的子类MappingSqlQuery,它提供多得多的方法 将数据行映射到Java类。
MappingSqlQuery:
是一个可以重用的查询对象,它的子类必须实现抽象方法mapRow(ResultSet, int)来把JDBC ResultSet的每一行转换成对象。在所有的SqlQuery实现中,这个类是最常使用并且也是最容易使用的。
MappingSqlQuery类使用示例:
public class CustomerMappingQuery extends MappingSqlQuery { public CustomerMappingQuery(DataSource ds) { super (ds, "SELECT id, name FROM customer WHERE id = ?"); super.declareParameter(new SqlParameter("id", Types.INTEGER)); compile(); } public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Customer cust = new Customer(); cust.setId((Integer) rs.getObject("id")); cust.setName(rs.getString("name")); return cust; } } |
我们为customer查询提供一个构建方法,它只有数据源这一个参数。 在构建方法中,我们调用超类的构建方法,并将数据源和将要用来查询取得数据的SQL作为参数。 因为这个SQL将被用来建立PreparedStatement,所以它可以包含?来绑定执行时会得到的参数。 每一个参数必须通过declareParameter方法并传递给它一个SqlParameter来声明。 SqlParameter有一个名字和一个在java.sql.Types定义的JDBC类型。 在所有的参数都定义完后,我们调用compile方法建立随后会执行的PreparedStatement。
实例化这个自定义查询对象并执行:
public Customer getCustomer(Integer id) { CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); Object[] parms = new Object[1]; parms[0] = id; List customers = custQry.execute(parms); if (customers.size() > 0) return (Customer) customers.get(0); else return null; } |
SqlUpdate:
表示一个SQL更新操作。就像查询一样, 更新对象是可重用的。更新可以有参数并定义在SQL中。类似于查询对象中的execute()方法,这个类提供很多update()的方法。这个类是具体的。通过SQL设定和参数声明,它可以很容易的参数化。
示例:
import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.SqlUpdate;
public class UpdateCreditRating extends SqlUpdate { public UpdateCreditRating(DataSource ds) { setDataSource(ds); setSql("update customer set credit_rating = ? where id =?"); declareParameter(new SqlParameter(Types.NUMERIC)); declareParameter(new SqlParameter(Types.NUMERIC)); compile(); }
public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); } } |
SqlFunction:
SQL "function"封装返回单一行结果的查询。默认的情况返回一个int,当然我们可以重载它, 通过额外返回参数得到其他类型。这和使用JdbcTemplate的 queryForXxx方法很相似。使用SqlFunction的好处是 不用必须在后台建立JdbcTemplate。这个类的目的是调用SQL function,使用像"select user()"或者"select sysdate from dual" 得到单一的结果。它不是用来调用复杂的存储功能也不是用来使用CallableStatement 来调用存储过程或者存储功能。对于这类的处理应当使用StoredProcedure或者SqlCall。这是一个具体的类,它通常不需要子类化。使用这个包的代码可以通过声明SQL和参数创建这个类型的对象, 然后能重复的使用run方法执行这个function。
得到一张表的行数的例子:
public int countRows() { SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable"); sf.compile(); return sf.run(); } |