Yair Zaslavsky has uploaded a new change for review. Change subject: core: Introducing Batch updates to DAOs ......................................................................
core: Introducing Batch updates to DAOs The following patch introduces the ability to perform a batch update Due to the fact spring-jdbc does not "natively" support postgresql we had to query the stored procedures meta-data own our own. The patch is based on Liran Zelkha's work - see http://gerrit.ovirt.org/#/c/14188/ Change-Id: If5ee0aa90bca3b5c257beb7b0eaa236f02f0206f Signed-off-by: Yair Zaslavsky <yzasl...@redhat.com> --- M backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dal/dbbroker/SimpleJdbcCallsHandler.java 1 file changed, 193 insertions(+), 28 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/39/15039/1 diff --git a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dal/dbbroker/SimpleJdbcCallsHandler.java b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dal/dbbroker/SimpleJdbcCallsHandler.java index 2e8bb61..6bed2aa 100644 --- a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dal/dbbroker/SimpleJdbcCallsHandler.java +++ b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dal/dbbroker/SimpleJdbcCallsHandler.java @@ -1,11 +1,21 @@ package org.ovirt.engine.core.dal.dbbroker; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.ovirt.engine.core.dao.BaseDAODbFacade; +import org.ovirt.engine.core.utils.log.Log; +import org.ovirt.engine.core.utils.log.LogFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -13,13 +23,18 @@ import org.springframework.jdbc.core.simple.SimpleJdbcCall; public class SimpleJdbcCallsHandler { + private static final Log log = LogFactory + .getLog(SimpleJdbcCallsHandler.class); - private ConcurrentMap<String, SimpleJdbcCall> callsMap = - new ConcurrentHashMap<String, SimpleJdbcCall>(); + private ConcurrentMap<String, SimpleJdbcCall> callsMap = new ConcurrentHashMap<String, SimpleJdbcCall>(); + + private ConcurrentMap<String, StoredProcedureMetaData> storedProceduresMap = new ConcurrentHashMap<String, StoredProcedureMetaData>(); private DbEngineDialect dialect; private JdbcTemplate template; + + // private Map<String, List<MapSqlParameterSource>> batchSqls; public void setDbEngineDialect(DbEngineDialect dialect) { this.dialect = dialect; @@ -33,19 +48,154 @@ SimpleJdbcCall createCall(); } - public Map<String, Object> executeModification(final String procedureName, final MapSqlParameterSource paramSource) { - return executeImpl(procedureName, paramSource, createCallForModification(procedureName)); + /** + * Runs a set of stored procedure calls in a batch. Only useful for update + * procedures that return no value + * @param procedureName + * The procedure name + * @param executions + * A list of parameter maps + * @return + */ + public void runBatch(final String procName, + final List<MapSqlParameterSource> executions) + throws DataAccessException { + + template.execute(new ConnectionCallback<Object>() { + + @Override + public Object doInConnection(Connection con) throws SQLException, + DataAccessException { + StoredProcedureMetaData procMetaData = getStoredProcedureMetaData( + procName, con); + + CallableStatement stmt = con.prepareCall(procMetaData + .getSqlCommand()); + + for (MapSqlParameterSource execution : executions) { + mapParams(stmt, execution, + procMetaData.getParamatersMetaData()); + stmt.addBatch(); + } + + try { + stmt.executeBatch(); + } catch (SQLException e) { + log.fatal("Can't execute batch: ", e); + log.fatal("Can't execute batch. Next exception is: ", + e.getNextException()); + throw e; + } + + stmt.close(); + + return null; + } + + private StoredProcedureMetaData getStoredProcedureMetaData( + String procName, Connection con) throws SQLException { + + if (!storedProceduresMap.containsKey(procName)) { + StoredProcedureMetaData procMetaData = new StoredProcedureMetaData(); + fillProcMetaData(procName, con, procMetaData); + storedProceduresMap.putIfAbsent(procName, procMetaData); + } + return storedProceduresMap.get(procName); + } + + private void fillProcMetaData(String procName, Connection con, StoredProcedureMetaData procMetaData) + throws SQLException { + SimpleJdbcCall call = getCall(procName, + createCallForModification(procName)); + + Map<String, SqlCallParameter> paramOrder = new HashMap<String, SqlCallParameter>(); + + ResultSet rs2 = con.getMetaData().getProcedureColumns(null, + null, call.getProcedureName().toLowerCase(), "%"); + + StringBuffer params = new StringBuffer(); + + String procNameFromDB = null; + String procSchemaFromDB = null; + int internalCounter = 1; + while (rs2.next()) { + + String colName = rs2.getString("COLUMN_NAME"); + int ordinal; + + try { + ordinal = rs2.getInt("ORDINAL_POSITION"); + } catch (SQLException e) { + // For some reason, some postgres drivers don't provide + // ORDINAL_POSITION + ordinal = internalCounter; + } + int dataType = rs2.getInt("DATA_TYPE"); + procSchemaFromDB = rs2.getString("PROCEDURE_SCHEM"); + procNameFromDB = rs2.getString("PROCEDURE_NAME"); + + if (!colName.equalsIgnoreCase("returnValue")) { + paramOrder.put(colName, new SqlCallParameter(ordinal, + colName, dataType)); + + String typeName = rs2.getString("TYPE_NAME"); + + params.append("CAST (? AS ").append(typeName) + .append("),"); + ++internalCounter; + } + } + + if (params.length() > 0) { + params.deleteCharAt(params.length() - 1); + } + rs2.close(); + + StringBuffer sqlCommand = new StringBuffer(); + sqlCommand.append("{call ").append(procSchemaFromDB) + .append(".").append(procNameFromDB).append("(") + .append(params).append(")}"); + + procMetaData.setSqlCommand(sqlCommand.toString()); + procMetaData.setDbName(procNameFromDB); + procMetaData.setParamatersMetaData(paramOrder); + procMetaData.setSchemaName(procSchemaFromDB); + } + + private void mapParams(PreparedStatement stmt, + MapSqlParameterSource paramSource, + Map<String, SqlCallParameter> paramOrder) + throws SQLException { + + Map<String, Object> values = paramSource.getValues(); + for (String paramName : paramOrder.keySet()) { + Object value = values.get(paramName); + SqlCallParameter sqlParam = paramOrder.get(paramName); + int ordinal = sqlParam.getOrdinal(); + stmt.setObject(ordinal, value); + } + } + }); } - public int executeModificationReturnResult(final String procedureName, final MapSqlParameterSource paramSource) { + public Map<String, Object> executeModification(final String procedureName, + final MapSqlParameterSource paramSource) { + return executeImpl(procedureName, paramSource, + createCallForModification(procedureName)); + } + + public int executeModificationReturnResult(final String procedureName, + final MapSqlParameterSource paramSource) { Integer procedureResult = null; - Map<String, Object> result = executeImpl(procedureName, paramSource, createCallForModification(procedureName)); + Map<String, Object> result = executeImpl(procedureName, paramSource, + createCallForModification(procedureName)); if (!result.isEmpty()) { List<?> resultArray = (List<?>) result.values().iterator().next(); if (resultArray != null && !resultArray.isEmpty()) { Map<?, ?> resultMap = (Map<?, ?>) resultArray.get(0); if (!resultMap.isEmpty()) { - procedureResult = (Integer) resultMap.values().iterator().next(); + procedureResult = (Integer) resultMap.values().iterator() + .next(); } } } @@ -55,7 +205,8 @@ public <T> T executeRead(final String procedureName, final RowMapper<T> mapper, final MapSqlParameterSource parameterSource) { - List<T> results = executeReadList(procedureName, mapper, parameterSource); + List<T> results = executeReadList(procedureName, mapper, + parameterSource); return results.isEmpty() ? null : results.get(0); } @@ -63,17 +214,19 @@ public <T> List<T> executeReadList(final String procedureName, final RowMapper<T> mapper, final MapSqlParameterSource parameterSource) { - Map<String, Object> resultsMap = executeReadAndReturnMap(procedureName, mapper, parameterSource); + Map<String, Object> resultsMap = executeReadAndReturnMap(procedureName, + mapper, parameterSource); List<?> resultList = (List<?>) (resultsMap .get(BaseDAODbFacade.RETURN_VALUE_PARAMETER)); return (resultList != null) ? (List<T>) resultList : null; } - public <T> Map<String, Object> executeReadAndReturnMap(final String procedureName, - final RowMapper<T> mapper, + public <T> Map<String, Object> executeReadAndReturnMap( + final String procedureName, final RowMapper<T> mapper, final MapSqlParameterSource parameterSource) { - Map<String, Object> resultsMap = - executeImpl(procedureName, parameterSource, createCallForRead(procedureName, mapper, parameterSource)); + Map<String, Object> resultsMap = executeImpl(procedureName, + parameterSource, + createCallForRead(procedureName, mapper, parameterSource)); return resultsMap; } @@ -81,18 +234,23 @@ final RowMapper<?> mapper, final MapSqlParameterSource parameterSource) { return new CallCreator() { - @Override - public SimpleJdbcCall createCall() { - SimpleJdbcCall call = - (SimpleJdbcCall) dialect.createJdbcCallForQuery(template).withProcedureName(procedureName); - call.returningResultSet(BaseDAODbFacade.RETURN_VALUE_PARAMETER, mapper); - // Pass mapper information (only parameter names) in order to supply all the needed - // metadata information for compilation. - call.getInParameterNames().addAll( - SqlParameterSourceUtils.extractCaseInsensitiveParameterNames(parameterSource).keySet()); - return call; - } - }; + @Override + public SimpleJdbcCall createCall() { + SimpleJdbcCall call = (SimpleJdbcCall) dialect + .createJdbcCallForQuery(template).withProcedureName( + procedureName); + call.returningResultSet(BaseDAODbFacade.RETURN_VALUE_PARAMETER, + mapper); + // Pass mapper information (only parameter names) in order to + // supply all the needed + // metadata information for compilation. + call.getInParameterNames().addAll( + SqlParameterSourceUtils + .extractCaseInsensitiveParameterNames( + parameterSource).keySet()); + return call; + } + }; } private CallCreator createCallForModification(final String procedureName) { @@ -104,8 +262,15 @@ }; } + private Map<String, Object> executeImpl(String procedureName, MapSqlParameterSource paramsSource, CallCreator callCreator) { + SimpleJdbcCall call = getCall(procedureName, callCreator); + + return call.execute(paramsSource); + } + + private SimpleJdbcCall getCall(String procedureName, CallCreator callCreator) { SimpleJdbcCall call = callsMap.get(procedureName); if (call == null) { // Creates a simple jdbc call object, and @@ -118,9 +283,9 @@ call = callCreator.createCall(); call.compile(); - callsMap.putIfAbsent(procedureName, - call); + callsMap.putIfAbsent(procedureName, call); } - return call.execute(paramsSource); + return call; } + } -- To view, visit http://gerrit.ovirt.org/15039 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If5ee0aa90bca3b5c257beb7b0eaa236f02f0206f Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Yair Zaslavsky <yzasl...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches