天天看點

batch執行流程 spring_Spring: 源碼分析之 JDBCTemplate.batchUpdate()

Spring JdbcTemplate的batch操作最後還是利用了JDBC提供的方法,Spring隻是做了一下改造

JDBC的batch操作:

String sql = "INSERT INTO CUSTOMER " +

"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";

List customers = getCustomersToInsert();

PreparedStatement pstmt = conn.prepareStatement(sql);

//預設情況下auto-commit=true,會認為一個statement就是一個transaction。批量操作中要執行多個statement,是以要設定為false

conn.setAutoCommit(false);

for (Customer customer : customers) {

pstmt.setLong(1, customer.getCustId());

pstmt.setString(2, customer.getName());

pstmt.setInt(3, customer.getAge() );

pstmt.addBatch();

}

int[] count = stmt.executeBatch();

conn.commit();

分析上述代碼可知,實際應用中隻有兩部分是會變的:一是sql語句,二是要插入的資料 Spring做的工作就是把“變”與“不變”的部分抽離開來 sql語句就作為一個String類型的參數傳遞好了,而插入資料的寫入提取為BatchPreparedStatementSetter接口:

class MyBatchPreparedStatementSetter implements BatchPreparedStatementSetter{

private List customers;

public MyBatchPreparedStatementSetter(List customers) {

this.customers = customers;

}

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

Customer customer = customers.get(i);

ps.setLong(1, customer.getCustId());

ps.setString(2, customer.getName());

ps.setInt(3, customer.getAge() );

}

@Override

public int getBatchSize() {

return customers.size();

}

}

BatchPreparedStatementSetter通常是以匿名内部類的形式出現:

String sql = ...;

List customers = ...;

getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter() {

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

Customer customer = customers.get(i);

ps.setLong(1, customer.getCustId());

ps.setString(2, customer.getName());

ps.setInt(3, customer.getAge() );

}

@Override

public int getBatchSize() {

return customers.size();

}

});

接下來就是“不變”的部分了,開啟PreparedStatement并執行batch操作: JdbcTemplate的batchUpdate方法:

public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {

return execute(sql, new PreparedStatementCallback() {

public int[] doInPreparedStatement(PreparedStatement ps) throws SQLException {

try {

int batchSize = pss.getBatchSize();

InterruptibleBatchPreparedStatementSetter ipss =

(pss instanceof InterruptibleBatchPreparedStatementSetter ?

(InterruptibleBatchPreparedStatementSetter) pss : null);

if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {

for (int i = 0; i < batchSize; i++) {

pss.setValues(ps, i);

if (ipss != null && ipss.isBatchExhausted(i)) {

break;

}

ps.addBatch();

}

return ps.executeBatch();

}

else {

List rowsAffected = new ArrayList();

for (int i = 0; i < batchSize; i++) {

pss.setValues(ps, i);

if (ipss != null && ipss.isBatchExhausted(i)) {

break;

}

rowsAffected.add(ps.executeUpdate());

}

int[] rowsAffectedArray = new int[rowsAffected.size()];

for (int i = 0; i < rowsAffectedArray.length; i++) {

rowsAffectedArray[i] = rowsAffected.get(i);

}

return rowsAffectedArray;

}

}

finally {

if (pss instanceof ParameterDisposer) {

((ParameterDisposer) pss).cleanupParameters();

}

}

}

});

}

可以看到pss.setValues(ps, i)、ps.addBatch() ps.executeBatch()等操作,是跟JDBC的一樣 而且它還判斷了如果不支援批量操作,則一條一條地執行 重點在PreparedStatementCallback:也是以匿名内部類的形式提供,它定義的doInPreparedStatement在execute方法中回調:

public T execute(String sql, PreparedStatementCallback action) throws DataAccessException {

return execute(new SimplePreparedStatementCreator(sql), action);

}

這一步,sql作為參數,利用PreparedStatementCreator來建立PreparedStatement SimplePreparedStatementCreator的createPreparedStatement方法:

public PreparedStatement createPreparedStatement(Connection con) throws SQLException {

return con.prepareStatement(this.sql);

public T execute(PreparedStatementCreator psc, PreparedStatementCallback action)

throws DataAccessException {

Connection con = DataSourceUtils.getConnection(getDataSource());

PreparedStatement ps = null;

try {

Connection conToUse = con;

if (this.nativeJdbcExtractor != null &&

this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {

conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

}

ps = psc.createPreparedStatement(conToUse);

applyStatementSettings(ps);

PreparedStatement psToUse = ps;

if (this.nativeJdbcExtractor != null) {

psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);

}

T result = action.doInPreparedStatement(psToUse);

handleWarnings(ps);

return result;

}

//omitted

}

這個execute方法主要就是建立PreparedStatement并回調PreparedStatementCallbackdoInPreparedStatement方法,簡單了解為:

Connection con = DataSourceUtils.getConnection(getDataSource());

PreparedStatement ps = null;

try {

ps = psc.createPreparedStatement(con);

T result = action.doInPreparedStatement(ps);

return result;

}

其中為什麼要用到nativeJdbcExtractor,官方文檔是這樣:

Sometimes you need to access vendor specific JDBC methods that differ from the standard JDBC API. This can be problematic if you are running in an application server or with a DataSource that wraps the Connection, Statement and ResultSet objects with its own wrapper objects. To gain access to the native objects you can configure yourJdbcTemplate or OracleLobHandler with a NativeJdbcExtractor.

是以,主要是為了取得原始的、标準的Connection, Statement and ResultSet(而不是經過包裝之後的) 最後梳理一下思路,以sql語句和待插入資料(customers)這兩個變量為線索:

首先,sql語句,最後會通過它建立一個PreparedStatement

其次,把資料寫入的設定提取為一個接口,使用時建立匿名内部類,也就是說資料由BatchPreparedStatementSetter持有

再者,PreparedStatementCallback持有BatchPreparedStatementSetter(也就持有了資料),那它還需要有一個PreparedStatement 來執行batch操作。那這個PreparedStatement怎麼提供呢?在execute方法裡面回調時提供 還有一個問題,為什麼在Spring JdbcTemplate的batchUpdate中,沒有看到conn.setAutoCommit(false)的操作?

這是因為Spring有它自己的事務管理機制

如果你配置了JDBC的事務管理,那麼DataSourceTransactionManager會自動設定

DataSourceTransactionManagerr的doBegin方法:

if (con.getAutoCommit()) {

txObject.setMustRestoreAutoCommit(true);

if (logger.isDebugEnabled()) {

logger.debug("Switching JDBC Connection [" + con + "] to manual commit");

}

con.setAutoCommit(false);

}

batch執行流程 spring_Spring: 源碼分析之 JDBCTemplate.batchUpdate()