/* * @(#)ShardedSqlSessionImpl.java 2012-8-1 下午10:00:00 * * Copyright (c) 2011-2012 Makersoft.org all rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * */ package org.makersoft.shards.session.impl; import java.io.Serializable; import java.sql.Connection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.*; import org.makersoft.shards.Shard; import org.makersoft.shards.ShardId; import org.makersoft.shards.ShardImpl; import org.makersoft.shards.ShardOperation; import org.makersoft.shards.id.IdGenerator; import org.makersoft.shards.select.impl.AdHocSelectFactoryImpl; import org.makersoft.shards.select.impl.ShardSelectImpl; import org.makersoft.shards.session.ShardIdResolver; import org.makersoft.shards.session.ShardedSqlSession; import org.makersoft.shards.session.ShardedSqlSessionFactory; import org.makersoft.shards.strategy.ShardStrategy; import org.makersoft.shards.strategy.exit.impl.ExitOperationsSelectCollector; import org.makersoft.shards.strategy.exit.impl.FirstNonNullResultExitStrategy; import org.makersoft.shards.strategy.resolution.ShardResolutionStrategyData; import org.makersoft.shards.strategy.resolution.ShardResolutionStrategyDataImpl; import org.makersoft.shards.utils.Assert; import org.makersoft.shards.utils.Lists; import org.makersoft.shards.utils.Maps; import org.makersoft.shards.utils.ParameterUtil; import org.makersoft.shards.utils.Sets; /** * @author Feng Kuok */ public class ShardedSqlSessionImpl implements ShardedSqlSession, ShardIdResolver { private final Log log = LogFactory.getLog(getClass()); private static ThreadLocal<ShardId> currentSubgraphShardId = new ThreadLocal<ShardId>(); private final ShardedSqlSessionFactory shardedSqlSessionFactory; private final List<Shard> shards; private final Map<ShardId, Shard> shardIdsToShards; private final ShardStrategy shardStrategy; // constructor public ShardedSqlSessionImpl(ShardedSqlSessionFactory shardedSqlSessionFactory, ShardStrategy shardStrategy) { this.shardedSqlSessionFactory = shardedSqlSessionFactory; this.shards = buildShardListFromSqlSessionFactoryShardIdMap( shardedSqlSessionFactory.getSqlSessionFactoryShardIdMap(), this); this.shardIdsToShards = buildShardIdsToShardsMap(); this.shardStrategy = shardStrategy; } static List<Shard> buildShardListFromSqlSessionFactoryShardIdMap( Map<SqlSessionFactory, Set<ShardId>> sqlSessionFactoryShardIdMap, ShardIdResolver shardIdResolver) { List<Shard> list = Lists.newArrayList(); for (Map.Entry<SqlSessionFactory, Set<ShardId>> entry : sqlSessionFactoryShardIdMap .entrySet()) { Shard shard = new ShardImpl(entry.getValue(), entry.getKey()); list.add(shard); } return list; } private Map<ShardId, Shard> buildShardIdsToShardsMap() { Map<ShardId, Shard> map = Maps.newHashMap(); for (Shard shard : shards) { for (ShardId shardId : shard.getShardIds()) { map.put(shardId, shard); } } return map; } /** * */ private Shard getShardForStatement(String statement, List<Shard> shardsToConsider) { //TODO(fengkuok) 此处可做本地缓存 // 首先查找主分区?如果没有再找其他分区? for (Shard shard : shardsToConsider) { if (shard.getSqlSessionFactory() != null && shard.getMappedStatementNames().contains(statement)) { return shard; } } return null; } private List<Shard> getShardsForStatement(String statement, List<Shard> shardsToConsider) { //TODO(fengkuok) 此处可做本地缓存 List<Shard> shards = Lists.newArrayList(); // 首先查找主分区?如果没有再找其他分区? for (Shard shard : shardsToConsider) { if (shard.getSqlSessionFactory() != null && shard.getMappedStatementNames().contains(statement)) { shards.add(shard); } } return shards; } private SqlSession getSqlSessionForStatement(String statement, List<Shard> shardsToConsider) { Shard shard = getShardForStatement(statement, shardsToConsider); if (shard == null) { return null; } return shard.establishSqlSession(); } /** * 将虚拟分区转化为物理分区 */ private List<Shard> shardIdListToShardList(List<ShardId> shardIds) { Set<Shard> shards = Sets.newHashSet(); if(shardIds != null && !shardIds.isEmpty()){ for (ShardId shardId : shardIds) { shards.add(shardIdsToShards.get(shardId)); } } return Lists.newArrayList(shards); } /** * @return 所有物理分区 */ public List<Shard> getShards() { return Collections.unmodifiableList(shards); } @Override public SqlSession getSqlSessionForStatement(String statement) { return getSqlSessionForStatement(statement, shards); } @Override public ShardId getShardIdForStatementOrParameter(String statement, Object parameter) { return getShardIdForStatementOrParameter(statement, parameter, shards); } @Override public ShardId getShardIdForStatementOrParameter(String statement, Object parameter, List<Shard> shardsToConsider) { // TODO(fengkuok) optimize this by keeping an identity map of objects to shardId Shard shard = getShardForStatement(statement, shardsToConsider); if (shard == null) { return null; } else if (shard.getShardIds().size() == 1) { return shard.getShardIds().iterator().next(); } else { //TODO(fengkuok) 似乎从来不会走到这个逻辑 IdGenerator idGenerator = shardedSqlSessionFactory.getIdGenerator(); if (idGenerator != null) { return idGenerator.extractShardId(this.extractId(parameter)); } else { // TODO(tomislav): also use shard resolution strategy if it returns only 1 shard; // throw this error in config instead of here throw new RuntimeException( "Can not use virtual sharding with non-shard resolving id gen"); } } } /** * 通过分区选择策略为对象选择分区 * * @param obj * 对象 * @return 逻辑分区 */ private ShardId selectShardIdForNewObject(String statement, Object obj) { // if(lockedShardId != null) { // return lockedShardId; // } ShardId shardId = shardStrategy.getShardSelectionStrategy().selectShardIdForNewObject( statement, obj); // lock has been requested but shard has not yet been selected - lock it in // if(lockedShard) { // lockedShardId = shardId; // } log.debug(String.format("Selected shard %s for object of type %s", shardId, obj.getClass() .getName())); return shardId; } List<ShardId> selectShardIdsFromShardResolutionStrategyData(ShardResolutionStrategyData srsd) { IdGenerator idGenerator = shardedSqlSessionFactory.getIdGenerator(); if ((idGenerator != null) && (srsd.getId() != null)) { // return Collections.singletonList(idGenerator.extractShardId(srsd.getId())); } return shardStrategy.getShardResolutionStrategy() .selectShardIdsFromShardResolutionStrategyData(srsd); } private <T> T applyGetOperation(ShardOperation<T> shardOp, ShardResolutionStrategyData srsd) { List<ShardId> shardIds = selectShardIdsFromShardResolutionStrategyData(srsd); return shardStrategy.getShardAccessStrategy().<T> apply( this.shardIdListToShardList(shardIds), shardOp, new FirstNonNullResultExitStrategy<T>(), new ExitOperationsSelectCollector(new AdHocSelectFactoryImpl( srsd.getStatement(), srsd.getParameter(), null, RowBounds.DEFAULT), shardStrategy.getShardReduceStrategy())); } // implements from SqlSession @Override public <T> T selectOne(String statement) { return this.<T> selectOne(statement, null); } @Override public <T> T selectOne(final String statement, final Object parameter) { if (parameter != null && (statement.endsWith("getById") || statement.endsWith("findById"))) { ShardOperation<T> shardOp = new ShardOperation<T>() { public T execute(SqlSession session, ShardId shardId) { return session.<T> selectOne(statement, ParameterUtil.resolve(parameter, shardId)); } public String getOperationName() { return "selectOne(String statement, Object parameter)"; } }; Serializable id = this.extractId(parameter); Assert.notNull(id, "When get entity by Id, Id can not be null"); return this.<T> applyGetOperation(shardOp, new ShardResolutionStrategyDataImpl( statement, parameter, id)); } // 从Resolution策略获取 List<Shard> potentialShards = determineShardsViaResolutionStrategyWithReadOperation( statement, parameter); Assert.notNull(potentialShards, "ShardResolutionStrategy returnd value cann't be null"); return new ShardSelectImpl(potentialShards, new AdHocSelectFactoryImpl(statement, parameter, null, null), shardStrategy.getShardAccessStrategy(), shardStrategy.getShardReduceStrategy()).<T> getSingleResult(); } @Override public <E> List<E> selectList(String statement) { return this.<E> selectList(statement, null); } @Override public <E> List<E> selectList(String statement, Object parameter) { return this.<E> selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List<Shard> potentialShards = determineShardsViaResolutionStrategyWithReadOperation( statement, parameter); Assert.notNull(potentialShards, "ShardResolutionStrategy returnd value cann't be null"); return new ShardSelectImpl(potentialShards, new AdHocSelectFactoryImpl(statement, parameter, null, rowBounds), shardStrategy.getShardAccessStrategy(), shardStrategy.getShardReduceStrategy()).<E> getResultList(); } @Override public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.<K, V> selectMap(statement, null, mapKey); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { return this.<K, V> selectMap(statement, parameter, mapKey, RowBounds.DEFAULT); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return new ShardSelectImpl(shards, new AdHocSelectFactoryImpl(statement, parameter, mapKey, rowBounds), shardStrategy.getShardAccessStrategy(), shardStrategy.getShardReduceStrategy()).getResultMap(); } @Override public int insert(String statement) { return this.insert(statement, Maps.newHashMap()); } @Override public int insert(String statement, Object parameter) { ShardId shardId = this.selectShardIdForNewObject(statement, parameter); if (shardId == null) { shardId = this.getShardIdForStatementOrParameter(statement, parameter); } Assert.notNull(shardId); // 设置当前分区id setCurrentSubgraphShardId(shardId); log.debug(String.format("Inserting object of type %s to shard %s", parameter.getClass(), shardId)); SqlSession session = shardIdsToShards.get(shardId).establishSqlSession(); IdGenerator idGenerator = shardedSqlSessionFactory.getIdGenerator(); if (idGenerator != null) { //TODO(fengkuok) 生成主键 DB生成主键是用专有session? Serializable id = idGenerator.generate(session, parameter); log.debug(String .format("Generating id for object %s ,the type of IdGenerator is %s and generated Id is %s.", parameter.getClass(), idGenerator.getClass(), id)); ParameterUtil.generatePrimaryKey(parameter, id); } final Object params = ParameterUtil.resolve(parameter, shardId); final int rows = session.insert(statement, params); //fixed set keys if(params instanceof Map) { Map map = (Map) params; Configuration configuration = session.getConfiguration(); MappedStatement ms = configuration.getMappedStatement(statement); if (parameter != null && ms != null && ms.getKeyProperties() != null) { String keyProperty = ms.getKeyProperties()[0]; // just one key property is supported final MetaObject metaParam = configuration.newMetaObject(parameter); if (keyProperty != null && metaParam.hasSetter(keyProperty)) { metaParam.setValue(keyProperty, map.get(keyProperty)); } } } return rows; } @Override public int update(String statement) { return this.update(statement, Maps.newHashMap()); } @Override public int update(String statement, Object parameter) { List<ShardId> shardIds = Lists.newArrayList(); List<Shard> potentialShards = determineShardsViaResolutionStrategyWithWriteOperation( statement, parameter); if (potentialShards != null && potentialShards.size() > 0) { for (Shard shard : potentialShards) { shardIds.addAll(shard.getShardIds()); } } else { // ShardId shardId = this.getShardIdForStatementOrParameter(statement, parameter); shardIds = Lists.newArrayList(shardId); } Assert.isTrue(!shardIds.isEmpty()); int rows = 0; for (ShardId shardId : shardIds) { rows += shardIdsToShards.get(shardId).establishSqlSession() .update(statement, ParameterUtil.resolve(parameter, shardId)); log.debug(String.format("Updateing object of type %s to shard %s", parameter == null ? parameter : parameter.getClass(), shardId)); } return rows; } /** * 用于写相关操作 */ List<Shard> determineShardsViaResolutionStrategyWithWriteOperation(String statement, Object parameter) { Serializable id = this.extractId(parameter); return this.determineShardsObjectsViaResolutionStrategy(statement, parameter, id); } /** * 用于读相关操作 */ List<Shard> determineShardsViaResolutionStrategyWithReadOperation(String statement, Object parameter) { List<Shard> potentialShards = this.determineShardsObjectsViaResolutionStrategy(statement, parameter, null); //策略返回为空集合则采用全部分片 potentialShards = potentialShards.isEmpty() ? shards : potentialShards; return this.getShardsForStatement(statement, potentialShards); } /** * 通过statement和parameter确定分区 如果parameter中可以提取出主键ID,首先通过ID去确定唯一分区 */ private List<Shard> determineShardsObjectsViaResolutionStrategy(String statement, Object parameter, Serializable id) { ShardResolutionStrategyData srsd = new ShardResolutionStrategyDataImpl(statement, parameter, id); List<ShardId> shardIds = this.selectShardIdsFromShardResolutionStrategyData(srsd); return shardIdListToShardList(shardIds); } /** * 获取对象主键值 */ Serializable extractId(Object obj) { if (obj != null) { if (obj instanceof String || obj instanceof Number) { // 当参数为Number/String类型时是否可以认为是主键? return (Serializable) obj; } return ParameterUtil.extractPrimaryKey(obj); } return null; } @Override public int delete(String statement) { return delete(statement, Maps.newHashMap()); } @Override public int delete(String statement, Object parameter) { List<ShardId> shardIds = Lists.newArrayList(); List<Shard> potentialShards = determineShardsViaResolutionStrategyWithWriteOperation( statement, parameter); if (potentialShards != null && potentialShards.size() > 0) { for (Shard shard : potentialShards) { shardIds.addAll(shard.getShardIds()); } } else { // 此种情况下按先从主分区查询statement如果不存在则查询全部分区来定位 ShardId shardId = this.getShardIdForStatementOrParameter(statement, parameter); shardIds = Lists.newArrayList(shardId); } Assert.isTrue(!shardIds.isEmpty()); int rows = 0; for (ShardId shardId : shardIds) { rows += shardIdsToShards.get(shardId).establishSqlSession() .delete(statement, ParameterUtil.resolve(parameter, shardId)); log.debug(String.format("Deleting object of type %s to shard %s", parameter, shardId)); } return rows; } @Override public void commit() { commit(false); } @Override public void commit(boolean force) { // throw new UnsupportedOperationException( // "Manual commit is not allowed over a Spring managed SqlSession"); // for (Shard shard : this.getShards()) { // SqlSession session = shard.getSqlSession(); // if (session != null) { // session.commit(force); // } // } } @Override public void rollback() { rollback(false); } @Override public void rollback(boolean force) { // for (Shard shard : this.getShards()) { // SqlSession session = shard.getSqlSession(); // if (session != null) { // session.rollback(force); // } // } } @Override public List<BatchResult> flushStatements() { return null; } @Override public void close() { // for (Shard shard : this.getShards()) { // SqlSession session = shard.getSqlSession(); // if (session != null) { // session.close(); // } // } } @Override public void clearCache() { for (Shard shard : this.getShards()) { SqlSession session = shard.establishSqlSession(); if (session != null) { session.clearCache(); } } } @Override public <T> T getMapper(Class<T> type) { for (Shard shard : this.getShards()) { if (shard.hasMapper(type)) { return shard.establishSqlSession().getMapper(type); } } throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } @Override public void select(String statement, ResultHandler handler) { throw new UnsupportedOperationException( "opration select is not allowed over a ShardedSqlSession"); } @Override public void select(String statement, Object parameter, ResultHandler handler) { throw new UnsupportedOperationException( "opration select is not allowed over a ShardedSqlSession"); } @Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { throw new UnsupportedOperationException( "opration select is not allowed over a ShardedSqlSession"); } @Override public Configuration getConfiguration() { throw new UnsupportedOperationException( "Manual get configuration is not allowed over a Spring managed SqlSession"); } @Override public Connection getConnection() { throw new UnsupportedOperationException( "Manual get connection is not allowed over a Spring managed SqlSession"); } // ~~~~~~~~~~~~~~~ public static ShardId getCurrentSubgraphShardId() { return currentSubgraphShardId.get(); } public static void setCurrentSubgraphShardId(ShardId shardId) { currentSubgraphShardId.set(shardId); } }