深刻淺出JDBC(三) - Spring JdbcTemplate

上一次咱們討論了Dbutils的用法,其實現原理很簡單,就是對JDBC的原始操做進行封裝。可是不管什麼操做,首先得建立Connection或者DataSource對象。在業務項目的開發中,手動地建立和銷燬Connection比較繁瑣,且不能充分地利用資源。因而有了鏈接池DBCP和C3P0兩個框架的出現,可是業務開發過程當中,對鏈接資源的獲取和釋放同業務是徹底無關的,那能不能就不關心鏈接的獲取和釋放,所以Spring將JDBC和IOC結合在一塊兒,能夠無感知地對數據庫進行操做。java

query與update

無感知操做不表明不須要配置數據庫鏈接相關屬性,咱們使用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

  1. PreparedStatementCreator: PreparedStatement建立器
  2. PreparedStatementSetter :PreparedStatement參數組裝器
  3. ResultSetExtractor:結果集解析器

如上文中的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的處理方式。

相關文章
相關標籤/搜索