package com.taobao.yugong.applier; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; import com.google.common.collect.MigrateMap; import com.taobao.yugong.common.db.meta.ColumnValue; import com.taobao.yugong.common.db.meta.Table; import com.taobao.yugong.common.db.meta.TableMetaGenerator; import com.taobao.yugong.common.db.sql.SqlTemplates; import com.taobao.yugong.common.model.DbType; import com.taobao.yugong.common.model.YuGongContext; import com.taobao.yugong.common.model.record.IncrementRecord; import com.taobao.yugong.common.model.record.Record; import com.taobao.yugong.common.utils.YuGongUtils; import com.taobao.yugong.exception.YuGongException; /** * 增量数据同步 * * <pre> * 1. 不支持主键变更 * 2. 行记录同步,简单处理 * </pre> * * @author agapple 2013-9-17 下午2:15:58 * @since 5.1.0 */ public class IncrementRecordApplier extends AbstractRecordApplier { protected static final Logger logger = LoggerFactory.getLogger(IncrementRecordApplier.class); protected Map<List<String>, TableSqlUnit> insertSqlCache; protected Map<List<String>, TableSqlUnit> updateSqlCache; protected Map<List<String>, TableSqlUnit> deleteSqlCache; protected boolean useMerge = true; protected YuGongContext context; protected DbType dbType; public IncrementRecordApplier(YuGongContext context){ this.context = context; } public void start() { super.start(); dbType = YuGongUtils.judgeDbType(context.getTargetDs()); insertSqlCache = MigrateMap.makeMap(); updateSqlCache = MigrateMap.makeMap(); deleteSqlCache = MigrateMap.makeMap(); } public void stop() { super.stop(); } public void apply(List<Record> records) throws YuGongException { // no one,just return if (YuGongUtils.isEmpty(records)) { return; } doApply(records); } protected void doApply(List records) { JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getTargetDs()); // 增量处理,为保证顺序,只能串行处理 applyOneByOne(records, jdbcTemplate); } /** * 一条条记录串行处理 */ protected void applyOneByOne(List<IncrementRecord> incRecords, JdbcTemplate jdbcTemplate) { for (final IncrementRecord incRecord : incRecords) { TableSqlUnit sqlUnit = getSqlUnit(incRecord); String applierSql = sqlUnit.applierSql; final Map<String, Integer> indexs = sqlUnit.applierIndexs; jdbcTemplate.execute(applierSql, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { int count = 0; // 字段 List<ColumnValue> cvs = incRecord.getColumns(); for (ColumnValue cv : cvs) { Integer index = getIndex(indexs, cv, true); // 考虑delete的目标库主键,可能在源库的column中 if (index != null) { ps.setObject(index, cv.getValue(), cv.getColumn().getType()); count++; } } // 添加主键 List<ColumnValue> pks = incRecord.getPrimaryKeys(); for (ColumnValue pk : pks) { Integer index = getIndex(indexs, pk, true);// 考虑delete的目标库主键,可能在源库的column中 if (index != null) { ps.setObject(index, pk.getValue(), pk.getColumn().getType()); count++; } } if (count != indexs.size()) { processMissColumn(incRecord, indexs); } try { ps.execute(); } catch (SQLException e) { if (context.isSkipApplierException()) { logger.error("skiped Record Data : " + incRecord.toString(), e); } else { throw new SQLException("failed Record Data : " + incRecord.toString(), e); } } return null; } }); } } protected TableSqlUnit getSqlUnit(IncrementRecord incRecord) { switch (incRecord.getOpType()) { case I: return getInsertSqlUnit(incRecord); case U: return getUpdateSqlUnit(incRecord); case D: return getDeleteSqlUnit(incRecord); default: break; } throw new YuGongException("unknow opType " + incRecord.getOpType()); } protected TableSqlUnit getInsertSqlUnit(IncrementRecord record) { List<String> names = Arrays.asList(record.getSchemaName(), record.getTableName()); TableSqlUnit sqlUnit = insertSqlCache.get(names); if (sqlUnit == null) { synchronized (names) { sqlUnit = insertSqlCache.get(names); if (sqlUnit == null) { // double-check sqlUnit = new TableSqlUnit(); String applierSql = null; Table meta = TableMetaGenerator.getTableMeta(context.getTargetDs(), context.isIgnoreSchema() ? null : names.get(0), names.get(1)); String[] primaryKeys = getPrimaryNames(record); String[] columns = getColumnNames(record); if (useMerge && YuGongUtils.isNotEmpty(meta.getColumns())) { // merge sql必须不是全主键 if (dbType == DbType.MYSQL) { applierSql = SqlTemplates.MYSQL.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns, true); } else if (dbType == DbType.DRDS) { applierSql = SqlTemplates.MYSQL.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns, false); } else if (dbType == DbType.ORACLE) { applierSql = SqlTemplates.ORACLE.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } } else { if (YuGongUtils.isEmpty(meta.getColumns()) && dbType == DbType.MYSQL) { // 如果mysql,全主键时使用insert ignore applierSql = SqlTemplates.MYSQL.getInsertSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } else { applierSql = SqlTemplates.COMMON.getInsertSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } } int index = 1; Map<String, Integer> indexs = new HashMap<String, Integer>(); for (String column : columns) { indexs.put(column, index); index++; } for (String column : primaryKeys) { indexs.put(column, index); index++; } // 检查下是否少了列 checkColumns(meta, indexs); sqlUnit.applierSql = applierSql; sqlUnit.applierIndexs = indexs; insertSqlCache.put(names, sqlUnit); } } } return sqlUnit; } protected TableSqlUnit getUpdateSqlUnit(IncrementRecord record) { List<String> names = Arrays.asList(record.getSchemaName(), record.getTableName()); TableSqlUnit sqlUnit = updateSqlCache.get(names); if (sqlUnit == null) { synchronized (names) { sqlUnit = updateSqlCache.get(names); if (sqlUnit == null) { // double-check sqlUnit = new TableSqlUnit(); String applierSql = null; Table meta = TableMetaGenerator.getTableMeta(context.getTargetDs(), context.isIgnoreSchema() ? null : names.get(0), names.get(1)); String[] primaryKeys = getPrimaryNames(record); String[] columns = getColumnNames(record); if (useMerge && YuGongUtils.isNotEmpty(meta.getColumns())) { // merge sql必须不是全主键 if (dbType == DbType.MYSQL) { applierSql = SqlTemplates.MYSQL.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns, true); } else if (dbType == DbType.DRDS) { applierSql = SqlTemplates.MYSQL.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns, false); } else if (dbType == DbType.ORACLE) { applierSql = SqlTemplates.ORACLE.getMergeSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } } else { applierSql = SqlTemplates.COMMON.getUpdateSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } int index = 1; Map<String, Integer> indexs = new HashMap<String, Integer>(); for (String column : columns) { indexs.put(column, index); index++; } for (String column : primaryKeys) { indexs.put(column, index); index++; } // 检查下是否少了列 checkColumns(meta, indexs); sqlUnit.applierSql = applierSql; sqlUnit.applierIndexs = indexs; updateSqlCache.put(names, sqlUnit); } } } return sqlUnit; } protected TableSqlUnit getDeleteSqlUnit(IncrementRecord record) { List<String> names = Arrays.asList(record.getSchemaName(), record.getTableName()); TableSqlUnit sqlUnit = deleteSqlCache.get(names); if (sqlUnit == null) { synchronized (names) { sqlUnit = deleteSqlCache.get(names); if (sqlUnit == null) { // double-check sqlUnit = new TableSqlUnit(); String applierSql = null; Table meta = TableMetaGenerator.getTableMeta(context.getTargetDs(), context.isIgnoreSchema() ? null : names.get(0), names.get(1)); String[] primaryKeys = getPrimaryNames(record); applierSql = SqlTemplates.COMMON.getDeleteSql(meta.getSchema(), meta.getName(), primaryKeys); int index = 1; Map<String, Integer> indexs = new HashMap<String, Integer>(); for (String column : primaryKeys) { indexs.put(column, index); index++; } // 检查下是否少了列 checkColumns(meta, indexs); sqlUnit.applierSql = applierSql; sqlUnit.applierIndexs = indexs; deleteSqlCache.put(names, sqlUnit); } } } return sqlUnit; } protected void processMissColumn(final IncrementRecord incRecord, final Map<String, Integer> indexs) { // 如果数量不同,则认为缺少主键 List<String> allNames = new ArrayList<String>(indexs.keySet()); for (ColumnValue cv : incRecord.getColumns()) { Integer index = getIndex(indexs, cv, true); if (index != null) { allNames.remove(cv.getColumn().getName()); } } for (ColumnValue pk : incRecord.getPrimaryKeys()) { Integer index = getIndex(indexs, pk, true); if (index != null) { allNames.remove(pk.getColumn().getName()); } } throw new YuGongException("miss columns" + allNames + " and failed Record Data : " + incRecord.toString()); } }