package com.taobao.yugong.applier; import java.sql.PreparedStatement; import java.sql.SQLException; 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.base.Function; import com.google.common.collect.Lists; 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.Record; import com.taobao.yugong.common.utils.YuGongUtils; import com.taobao.yugong.exception.YuGongException; /** * 全量同步appiler * * <pre> * 1. 行记录同步,目标库存在则更新,没有则插入 * </pre> * * @author agapple 2013-9-23 下午5:27:02 */ public class FullRecordApplier extends AbstractRecordApplier { protected static final Logger logger = LoggerFactory.getLogger(FullRecordApplier.class); protected Map<List<String>, TableSqlUnit> applierSqlCache; protected YuGongContext context; protected DbType dbType; protected boolean useMerge = false; public FullRecordApplier(YuGongContext context){ this.context = context; } public void start() { super.start(); dbType = YuGongUtils.judgeDbType(context.getTargetDs()); applierSqlCache = MigrateMap.makeMap(); } public void stop() { super.stop(); } /** * default batch insert */ public void apply(List<Record> records) throws YuGongException { // no one,just return if (YuGongUtils.isEmpty(records)) { return; } doApply(records); } protected void doApply(List<Record> records) { Map<List<String>, List<Record>> buckets = MigrateMap.makeComputingMap(new Function<List<String>, List<Record>>() { public List<Record> apply(List<String> names) { return Lists.newArrayList(); } }); // 根据目标库的不同,划分为多个bucket for (Record record : records) { buckets.get(Arrays.asList(record.getSchemaName(), record.getTableName())).add(record); } JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getTargetDs()); for (final List<Record> batchRecords : buckets.values()) { TableSqlUnit sqlUnit = getSqlUnit(batchRecords.get(0)); if (context.isBatchApply()) { applierByBatch(jdbcTemplate, batchRecords, sqlUnit); } else { applyOneByOne(jdbcTemplate, batchRecords, sqlUnit); } } } /** * batch处理支持 */ protected void applierByBatch(JdbcTemplate jdbcTemplate, final List<Record> batchRecords, TableSqlUnit sqlUnit) { boolean redoOneByOne = false; try { final Map<String, Integer> indexs = sqlUnit.applierIndexs; jdbcTemplate.execute(sqlUnit.applierSql, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { for (Record record : batchRecords) { // 先加字段,后加主键 List<ColumnValue> cvs = record.getColumns(); for (ColumnValue cv : cvs) { ps.setObject(getIndex(indexs, cv), cv.getValue(), cv.getColumn().getType()); } // 添加主键 List<ColumnValue> pks = record.getPrimaryKeys(); for (ColumnValue pk : pks) { ps.setObject(getIndex(indexs, pk), pk.getValue(), pk.getColumn().getType()); } ps.addBatch(); } ps.executeBatch(); return null; } }); } catch (Exception e) { // catch the biggest exception,no matter how, rollback it; redoOneByOne = true; // conn.rollback(); } // batch cannot pass the duplicate entry exception,so // if executeBatch throw exception,rollback it, and // redo it one by one if (redoOneByOne) { applyOneByOne(jdbcTemplate, batchRecords, sqlUnit); } } /** * 一条条记录串行处理 */ protected void applyOneByOne(JdbcTemplate jdbcTemplate, final List<Record> records, TableSqlUnit sqlUnit) { final Map<String, Integer> indexs = sqlUnit.applierIndexs; jdbcTemplate.execute(sqlUnit.applierSql, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { for (Record record : records) { List<ColumnValue> pks = record.getPrimaryKeys(); // 先加字段,后加主键 List<ColumnValue> cvs = record.getColumns(); for (ColumnValue cv : cvs) { ps.setObject(getIndex(indexs, cv), cv.getValue(), cv.getColumn().getType()); } // 添加主键 for (ColumnValue pk : pks) { ps.setObject(getIndex(indexs, pk), pk.getValue(), pk.getColumn().getType()); } try { ps.execute(); } catch (SQLException e) { if (context.isSkipApplierException()) { logger.error("skiped record data : " + record.toString(), e); } else { if (e.getMessage().contains("Duplicate entry") || e.getMessage().startsWith("ORA-00001: 违反唯一约束条件")) { logger.error("skiped record data ,maybe transfer before,just continue:" + record.toString()); } else { throw new SQLException("failed Record Data : " + record.toString(), e); } } } } return null; } }); } /** * 基于当前记录生成sqlUnit */ protected TableSqlUnit getSqlUnit(Record record) { List<String> names = Arrays.asList(record.getSchemaName(), record.getTableName()); TableSqlUnit sqlUnit = applierSqlCache.get(names); if (sqlUnit == null) { synchronized (names) { sqlUnit = applierSqlCache.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) { 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 (dbType == DbType.MYSQL) { // 如果mysql,全主键时使用insert ignore applierSql = SqlTemplates.MYSQL.getInsertNomalSql(meta.getSchema(), meta.getName(), primaryKeys, columns); } else { applierSql = SqlTemplates.ORACLE.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; applierSqlCache.put(names, sqlUnit); } } } return sqlUnit; } }