上一次咱们讨论了Dbutils的用法,其实现原理很简单,就是对JDBC的原始操做进行封装。可是不管什么操做,首先得建立Connection或者DataSource对象。在业务项目的开发中,手动地建立和销毁Connection比较繁琐,且不能充分地利用资源。因而有了链接池DBCP和C3P0两个框架的出现,可是业务开发过程当中,对链接资源的获取和释放同业务是彻底无关的,那能不能就不关心链接的获取和释放,所以Spring将JDBC和IOC结合在一块儿,能够无感知地对数据库进行操做。java
无感知操做不表明不须要配置数据库链接相关属性,咱们使用xml方式配置基于DBCP的DataSource,而后注入到Spring提供的jdbc操做模板类JdbcTemplate中,这样在业务操做中只要持有这个模板对象便可与数据库交互。mysql
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- DBCP的DataSource对象 --> <bean id="basicDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.1.160:3306/lichao" /> <property name="username" value="wms_dev" /> <property name="password" value="OL2kfZ4s" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="basicDataSource"></property> </bean> </beans>
只要在spring启动时加载这个xml文件,便可得到能够与数据库交互的JdbcTemplate对象。这里咱们使用spring junit的方式。spring
@RunWith(SpringRunner.class) @ContextConfiguration(locations={"classpath:com/lntea/jdbc/spring/jdbc-template.xml"}) public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; @Test public void testQueryForObject(){ // query number of rows Integer count = jdbcTemplate.queryForObject("select count(*) from t_wms_goods_stock", Integer.class); System.out.println("count:" + count); } }
执行junit测试方法testQueryForObject,经过JdbcTemplate的queryForObject方法,查询商品库存的总记录条数。queryForObject方法,支持一条sql查询,返回指定Class type的结果,也支持sql中?占位符,经过可变数组进行绑定。sql
// query using a bind variable Integer countOfWarehouseId = jdbcTemplate.queryForObject("select count(*) from t_wms_goods_stock where warehouse_id = ?", Integer.class, 1L); System.out.println("countOfWarehouseId:" + countOfWarehouseId);
若是须要将查询结果映射成java对象,使用参数RowMapper。数据库
// query and populate a single domain object GoodsStock gs = jdbcTemplate.queryForObject("select * from t_wms_goods_stock where id = ?", new RowMapper<GoodsStock>() { public GoodsStock mapRow(ResultSet rs, int rowNum) throws SQLException { GoodsStock gs = new GoodsStock(); gs.setId(rs.getInt("id")); gs.setWarehouseId(rs.getLong("warehouse_id")); return gs; } }, 1L); System.out.println(gs);
还可使用query方法查询一组返回值apache
// query and populate a number of domain objects List<GoodsStock> gsList = jdbcTemplate.query("select * from t_wms_goods_stock limit 10", new RowMapper<GoodsStock>() { public GoodsStock mapRow(ResultSet rs, int rowNum) throws SQLException { GoodsStock gs = new GoodsStock(); gs.setId(rs.getInt("id")); gs.setWarehouseId(rs.getLong("warehouse_id")); return gs; } }); System.out.println(gsList);
看完查询,咱们来试试更新操做,update方法能够极方便地完成api
@Test public void testUpdate(){ int rowUpdateNum = jdbcTemplate.update("update t_wms_goods_stock set warehouse_id = ? where id = ?", 1L, 1); System.out.println("rowUpdateNum:" + rowUpdateNum); }
那么,JdbcTemplate在底层是如何完成这些简便的api和jdbc的交互呢,其实spring jdbc对jdbc不一样的操做作了模块的抽象,来看一个有参数查询的Demo。数组
// PreparedStatement的建立器,将sql转化成PreparedStatement PreparedStatementCreator psc = new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { return con.prepareStatement("select count(*) from t_wms_goods_stock where warehouse_id = ?"); } }; // PreparedStatement参数组装器,将sql参数设置到PreparedStatement中 PreparedStatementSetter pss = new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { ps.setLong(1, 1L); } }; // 结果解析器,将sql查询结果转换成指定的对象类型 ResultSetExtractor<Integer> rse = new ResultSetExtractor<Integer>() { public Integer extractData(ResultSet rs) throws SQLException, DataAccessException { while(rs.next()){ return rs.getInt(1); } return null; } }; // 执行query操做 int count = jdbcTemplate.query(psc, pss, rse); System.out.println("count:" + count);
spring将jdbc中PreparedStatement操做中可变的部分抽象成三个模块app
如上文中的queryForObject的例子框架
Integer countOfWarehouseId = jdbcTemplate.queryForObject("select count(*) from t_wms_goods_stock where warehouse_id = ?", Integer.class, 1L);
在实现时,转换成spring自带的上述三个模块的实现类,sql对应SimplePreparedStatementCreator,查询参数对应newArgPreparedStatementSetter,而对结果集的Class类型为Integer的要求封装成getSingleColumnRowMapper,并最终由RowMapperResultSetExtractor完成结果集的转换。
三个模块完成后,又怎么完成jdbc的底层操做呢?query方法调用的是execute方法
public <T> T query( PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException { return execute(psc, new PreparedStatementCallback<T>() { @Override @Nullable public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps); } rs = ps.executeQuery(); return rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
在execute方法中,建立了PreparedStatementCallback对象,实现doInPreparedStatement方法,封装了PreparedStatement的参数设置,查询执行以及结果提取转换。execute方法的执行就回到了最基础的jdbc操做。
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException { if (logger.isDebugEnabled()) { // 获取sql并输出日志 String sql = getSql(psc); logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } // 获取数据库链接 Connection con = DataSourceUtils.getConnection(obtainDataSource()); PreparedStatement ps = null; try { // 建立PreparedStatement ps = psc.createPreparedStatement(con); // statement配置 applyStatementSettings(ps); // 执行jdbc查询操做 T result = action.doInPreparedStatement(ps); // 输出warning日志 handleWarnings(ps); return result; } catch (SQLException ex) { // 发生Sql异常,提早释放资源,防止链接池死锁 // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw translateException("PreparedStatementCallback", sql, ex); } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
对于获取数据库链接,调用的是DataSourceUtils的getConnection方法,对于非事务管理的jdbc操做,起做用的就是一句代码。
Connection con = fetchConnection(dataSource); private static Connection fetchConnection(DataSource dataSource) throws SQLException { Connection con = dataSource.getConnection(); if (con == null) { throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource); } return con; }
由DataSource建立Connection链接。而同事务相关的部分,等到之后谈到spring 事务管理时再详细介绍吧。
咱们深刻的探究了spring JdbcTemplate处理预编译sql的原理,对于普通的Statement操做,实现过程同PreparedStatement类似,这里就再也不介绍了,有兴趣本身查看源码。这篇文章中介绍了query和update的例子,delete操做其实雷同于update,而insert操做,由于其涉及到自增主键的返回,单独一章介绍spring的处理方式。