package com.ctrip.platform.dal.dao.helper; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.ctrip.platform.dal.dao.DalClientFactory; import com.ctrip.platform.dal.dao.DalHints; import com.ctrip.platform.dal.dao.StatementParameters; import com.ctrip.platform.dal.dao.client.DalTransactionManager; import com.ctrip.platform.dal.dao.configure.DalConfigure; import com.ctrip.platform.dal.dao.configure.DatabaseSet; import com.ctrip.platform.dal.dao.strategy.DalShardingStrategy; public class DalShardingHelper { public static boolean isShardingEnabled(String logicDbName) { return getDatabaseSet(logicDbName).isShardingSupported(); } public static boolean isTableShardingEnabled(String logicDbName, String tableName) { return getDatabaseSet(logicDbName).isTableShardingSupported(tableName); } public static String buildShardStr(String logicDbName, String shardId) throws SQLException { String separator = getDatabaseSet(logicDbName).getStrategy().getTableShardSeparator(); return separator == null? shardId: separator + shardId; } public static DatabaseSet getDatabaseSet(String logicDbName) { return DalClientFactory.getDalConfigure().getDatabaseSet(logicDbName); } /** * Try to locate DB shard id by hints. If can not be located, return false. * @param logicDbName * @param hints * @return true if shard id can be located * @throws SQLException */ private static boolean locateShardId(String logicDbName, DalHints hints) throws SQLException { DalConfigure config = DalClientFactory.getDalConfigure(); DatabaseSet dbSet = config.getDatabaseSet(logicDbName); String shardId = dbSet.getStrategy().locateDbShard(config, logicDbName, hints); if(shardId == null) return false; // Fail fast asap dbSet.validate(shardId); hints.inShard(shardId); return true; } /** * Locate table shard id by hints. * @param logicDbName * @param hints * @return * @throws SQLException */ private static boolean locateTableShardId(String logicDbName, DalHints hints) throws SQLException { DalConfigure config = DalClientFactory.getDalConfigure(); DalShardingStrategy strategy = config.getDatabaseSet(logicDbName).getStrategy(); // First check if we can locate the table shard id with the original hints String tableShardId = strategy.locateTableShard(config, logicDbName, hints); if(tableShardId == null) return false; hints.inTableShard(tableShardId); return true; } /** * Locate table shard id by hints. * @param logicDbName * @param hints * @return * @throws SQLException */ public static String locateTableShardId(String logicDbName, DalHints hints, StatementParameters parameters, Map<String, ?> fields) throws SQLException { DalConfigure config = DalClientFactory.getDalConfigure(); DalShardingStrategy strategy = config.getDatabaseSet(logicDbName).getStrategy(); // First check if we can locate the table shard id with the original hints String shard = strategy.locateTableShard(config, logicDbName, hints); if(shard != null) return shard; shard = strategy.locateTableShard(config, logicDbName, new DalHints().setParameters(parameters).setFields(fields)); if(shard != null) return shard; throw new SQLException("Can not locate table shard for " + logicDbName); } /** * Group pojos by shard id. Should be only used for DB that support sharding. * * @param logicDbName * @param pojos * @return Grouped pojos * @throws SQLException In case locate shard id faild */ public static Map<String, Map<Integer, Map<String, ?>>> shuffle(String logicDbName, String shardId, List<Map<String, ?>> daoPojos) throws SQLException { Map<String, Map<Integer, Map<String, ?>>> shuffled = new HashMap<>(); DalConfigure config = DalClientFactory.getDalConfigure(); DatabaseSet dbSet = config.getDatabaseSet(logicDbName); DalShardingStrategy strategy = dbSet.getStrategy(); DalHints tmpHints = new DalHints(); for (int i = 0; i < daoPojos.size(); i++) { Map<String, ?> pojo = daoPojos.get(i); String tmpShardId = shardId == null ? strategy.locateDbShard(config, logicDbName, tmpHints.setFields(pojo)) : shardId; dbSet.validate(tmpShardId); Map<Integer, Map<String, ?>> pojosInShard = shuffled.get(tmpShardId); if(pojosInShard == null) { pojosInShard = new LinkedHashMap<>(); shuffled.put(tmpShardId, pojosInShard); } pojosInShard.put(i, pojo); } detectDistributedTransaction(shuffled.keySet()); return shuffled; } /** * Shuffle by given values like id list for DB shard * @param logicDbName * @param parameters * @return * @throws SQLException */ public static Map<String, List<?>> shuffle(String logicDbName, List<?> parameters) throws SQLException { Map<String, List<?>> shuffled = new HashMap<>(); DalConfigure config = DalClientFactory.getDalConfigure(); DatabaseSet dbSet = config.getDatabaseSet(logicDbName); DalShardingStrategy strategy = dbSet.getStrategy(); DalHints tmpHints = new DalHints(); for (int i = 0; i < parameters.size(); i++) { Object value = parameters.get(i); String tmpShardId = strategy.locateDbShard(config, logicDbName, tmpHints.setShardValue(value)); // If this can not be located if(tmpShardId == null) throw new NullPointerException("Can not locate shard id for " + value); dbSet.validate(tmpShardId); List pojosInShard = shuffled.get(tmpShardId); if(pojosInShard == null) { pojosInShard = new LinkedList(); shuffled.put(tmpShardId, pojosInShard); } pojosInShard.add(value); } detectDistributedTransaction(shuffled.keySet()); return shuffled; } /** * Shuffle by table shard id. * @param logicDbName * @param pojos * @return * @throws SQLException */ public static Map<String, Map<Integer, Map<String, ?>>> shuffleByTable(String logicDbName, String tableShardId, Map<Integer, Map<String, ?>> pojos) throws SQLException { Map<String, Map<Integer, Map<String, ?>>> shuffled = new HashMap<>(); DalConfigure config = DalClientFactory.getDalConfigure(); DatabaseSet dbSet = config.getDatabaseSet(logicDbName); DalShardingStrategy strategy = dbSet.getStrategy(); DalHints tmpHints = new DalHints(); for (Integer index: pojos.keySet()) { Map<String, ?> fields = pojos.get(index); String shardId = tableShardId == null ? strategy.locateTableShard(config, logicDbName, tmpHints.setFields(fields)) : tableShardId; Map<Integer, Map<String, ?>> pojosInShard = shuffled.get(shardId); if(pojosInShard == null) { pojosInShard = new LinkedHashMap<>(); shuffled.put(shardId, pojosInShard); } pojosInShard.put(index, fields); } return shuffled; } /** * Verify if shard id is already set for potential corss shard batch operation. * This includes combined insert, batch insert and batch delete. * It will first check if sharding is enabled. Then detect if necessary sharding id can be located. * This applies to both db and table shard. * If all meet, then allow the operation * TODO do more analyze of the logic here * @param logicDbName * @param hints * @param message * @throws SQLException * @return if all sharding id can be located. */ public static boolean isAlreadySharded(String logicDbName, String tableName, DalHints hints) throws SQLException { // For normal case, both DB and table sharding are not enabled if(!(isShardingEnabled(logicDbName) || isTableShardingEnabled(logicDbName, tableName))) return true; // Assume the out transaction already handle sharding logic // This may have potential issue if PD think they can do cross DB operation // TOD check here if(DalTransactionManager.isInTransaction()) return true; hints.cleanUp(); // Verify if DB shard is defined if(isShardingEnabled(logicDbName) && !locateShardId(logicDbName, hints)) return false; // Verify if table shard is defined if(isTableShardingEnabled(logicDbName, tableName) && !locateTableShardId(logicDbName, hints)) return false; return true; } public static void detectDistributedTransaction(String logicDbName, DalHints hints, List<Map<String, ?>> daoPojos) throws SQLException { if(!isShardingEnabled(logicDbName)) return; if(!DalTransactionManager.isInTransaction()) return; String shardId = null; if(locateShardId(logicDbName, hints)) { shardId = hints.getShardId(); isSameShard(shardId); } else { // Shuffle will call detectDistributedTransaction(Set) shuffle(logicDbName, shardId, daoPojos).keySet(); } } public static void detectDistributedTransaction(Set<String> shardIds) throws SQLException { if(!DalTransactionManager.isInTransaction()) return; if(shardIds == null) return; // Not allowed for distributed transaction if(shardIds.size() > 1) throw new SQLException("Potential distributed operation detected in shards: " + shardIds); String shardId = shardIds.iterator().next(); isSameShard(shardId); } private static void isSameShard(String shardId) throws SQLException { if(!shardId.equals(DalTransactionManager.getCurrentDbMeta().getShardId())) throw new SQLException("Operation is not allowed in different database shard within current transaction. Current shardId: " + DalTransactionManager.getCurrentDbMeta().getShardId() + ". Requeted shardId: " + shardId); } }