/** * Copyright 2014 Duan Bingnan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.pinus4j.datalayer.query.jdbc; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import javax.transaction.Transaction; import javax.transaction.xa.XAResource; import org.pinus4j.api.SQL; import org.pinus4j.api.query.IQuery; import org.pinus4j.api.query.impl.DefaultQueryImpl; import org.pinus4j.api.query.impl.Order; import org.pinus4j.api.query.impl.DefaultQueryImpl.OrderBy; import org.pinus4j.cluster.beans.IShardingKey; import org.pinus4j.cluster.enums.EnumDBMasterSlave; import org.pinus4j.cluster.resources.IDBResource; import org.pinus4j.cluster.resources.ShardingDBResource; import org.pinus4j.datalayer.query.IShardingQuery; import org.pinus4j.entity.meta.EntityPK; import org.pinus4j.exceptions.DBClusterException; import org.pinus4j.exceptions.DBOperationException; import org.pinus4j.utils.BeansUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * jdbc sharding query implements. * * @author duanbn * @since 1.1.1 */ public class ShardingJdbcQueryImpl extends AbstractJdbcQuery implements IShardingQuery { public static final Logger LOG = LoggerFactory.getLogger(ShardingJdbcQueryImpl.class); @Override public Number getCount(Class<?> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { Transaction tx = null; List<IDBResource> dbResources = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; String clusterName = entityMetaManager.getClusterName(clazz); if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(clusterName)) { dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); } else { dbResources = this.dbCluster.getAllSlaveShardingDBResource(clazz, masterSlave); isFromSlave = true; } long count = 0; for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } count += selectCountWithCache(dbResource, clazz, useCache).longValue(); } // query from master again if (count == 0 && isFromSlave) { for (IDBResource dbResource : dbResources) { dbResource.close(); } dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } count += selectCountWithCache((ShardingDBResource) dbResource, clazz, useCache).longValue(); } } return count; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResources != null) { for (IDBResource dbResource : dbResources) { dbResource.close(); } } } } @Override public Number getCount(IShardingKey<?> shardingKey, Class<?> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { Transaction tx = null; ShardingDBResource dbResource = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { dbResource = _getDbFromMaster(clazz, shardingKey); } else { dbResource = _getDbFromSlave(clazz, shardingKey, masterSlave); isFromSlave = true; } if (tx != null) { tx.enlistResource(dbResource); } long count = selectCountWithCache(dbResource, clazz, useCache).longValue(); // quer from master again if (count == 0 && isFromSlave) { dbResource.close(); dbResource = _getDbFromMaster(clazz, shardingKey); if (tx != null) { tx.enlistResource(dbResource); } selectCountWithCache(dbResource, clazz, useCache); } return count; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } @Override public <T> Number getCountByQuery(IQuery<T> query, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { Transaction tx = null; List<IDBResource> dbResources = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; String clusterName = entityMetaManager.getClusterName(clazz); if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(clusterName)) { dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); } else { dbResources = this.dbCluster.getAllSlaveShardingDBResource(clazz, masterSlave); isFromSlave = true; } long count = 0; for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } count += selectCountByQuery(query, (ShardingDBResource) dbResource, clazz).longValue(); } // query from master again if (count == 0 && isFromSlave) { for (IDBResource dbResource : dbResources) { dbResource.close(); } dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } count += selectCountByQuery(query, (ShardingDBResource) dbResource, clazz).longValue(); } } return count; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResources != null) { for (IDBResource dbResource : dbResources) { dbResource.close(); } } } } @Override public <T> Number getCountByQuery(IQuery<T> query, IShardingKey<?> shardingKey, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { Transaction tx = null; ShardingDBResource dbResource = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { dbResource = _getDbFromMaster(clazz, shardingKey); } else { dbResource = _getDbFromSlave(clazz, shardingKey, masterSlave); isFromSlave = true; } if (tx != null) { tx.enlistResource(dbResource); } long count = selectCountByQuery(query, dbResource, clazz).longValue(); // query from master again if (count == 0 && isFromSlave) { dbResource.close(); dbResource = _getDbFromMaster(clazz, shardingKey); if (tx != null) { tx.enlistResource(dbResource); } count = selectCountByQuery(query, dbResource, clazz).longValue(); } return count; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } @Override public <T> T findByPk(EntityPK pk, IShardingKey<?> shardingKey, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { Transaction tx = null; ShardingDBResource dbResource = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { dbResource = _getDbFromMaster(clazz, shardingKey); } else { dbResource = _getDbFromSlave(clazz, shardingKey, masterSlave); isFromSlave = true; } if (tx != null) { tx.enlistResource(dbResource); } Map<EntityPK, T> data = selectByPksWithCache(dbResource, clazz, new EntityPK[] { pk }, null, useCache); // query from master again if ((data == null || data.isEmpty()) && isFromSlave) { dbResource.close(); dbResource = _getDbFromMaster(clazz, shardingKey); if (tx != null) { tx.enlistResource(dbResource); } data = selectByPksWithCache(dbResource, clazz, new EntityPK[] { pk }, null, useCache); } return data.get(pk); } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } @Override public <T> List<T> findByPkList(List<EntityPK> pkList, Class<T> clazz, List<OrderBy> order, boolean useCache, EnumDBMasterSlave masterSlave) { List<T> result = Lists.newArrayList(); Transaction tx = null; List<IDBResource> dbResources = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; String clusterName = entityMetaManager.getClusterName(clazz); if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(clusterName)) { dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); } else { dbResources = this.dbCluster.getAllSlaveShardingDBResource(clazz, masterSlave); isFromSlave = true; } EntityPK[] entityPkList = pkList.toArray(new EntityPK[pkList.size()]); Map<EntityPK, T> data = Maps.newLinkedHashMap(); for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } data.putAll(selectByPksWithCache((ShardingDBResource) dbResource, clazz, entityPkList, order, useCache)); } // query from master again if (data.isEmpty() && isFromSlave) { for (IDBResource dbResource : dbResources) { dbResource.close(); } dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); for (IDBResource dbResource : dbResources) { if (tx != null) { tx.enlistResource((ShardingDBResource) dbResource); } data.putAll(selectByPksWithCache((ShardingDBResource) dbResource, clazz, entityPkList, order, useCache)); } } result.addAll(data.values()); return result; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResources != null) { for (IDBResource dbResource : dbResources) { dbResource.close(); } } } } @Override public <T> List<T> findByPkList(List<EntityPK> pkList, IShardingKey<?> shardingKey, Class<T> clazz, List<OrderBy> order, boolean useCache, EnumDBMasterSlave masterSlave) { List<T> result = Lists.newArrayList(); Transaction tx = null; ShardingDBResource dbResource = null; try { tx = txManager.getTransaction(); boolean isFromSlave = false; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { dbResource = _getDbFromMaster(clazz, shardingKey); } else { dbResource = _getDbFromSlave(clazz, shardingKey, masterSlave); isFromSlave = true; } if (tx != null) { tx.enlistResource(dbResource); } EntityPK[] entityPkList = pkList.toArray(new EntityPK[pkList.size()]); Map<EntityPK, T> data = selectByPksWithCache(dbResource, clazz, entityPkList, order, useCache); if (data.isEmpty() && isFromSlave) { dbResource.close(); dbResource = _getDbFromMaster(clazz, shardingKey); if (tx != null) { tx.enlistResource(dbResource); } data = selectByPksWithCache(dbResource, clazz, entityPkList, order, useCache); } result.addAll(data.values()); return result; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } @Override public <T> List<T> findByQuery(IQuery<T> query, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { boolean isFromSlave = false; List<IDBResource> dbResources = null; DefaultQueryImpl<T> internalQuery = (DefaultQueryImpl<T>) query; try { String clusterName = entityMetaManager.getClusterName(clazz); if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(clusterName)) { dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); } else { dbResources = this.dbCluster.getAllSlaveShardingDBResource(clazz, masterSlave); isFromSlave = true; } int start = internalQuery.getStart(); int limit = internalQuery.getLimit(); int sum = start + limit; if (start >= 0 && limit > 0) internalQuery.limit(0, sum); final List<OrderBy> orderList = internalQuery.getOrderList(); boolean isOrderQuery = false; if (orderList != null && !orderList.isEmpty()) { isOrderQuery = true; } List<T> mergeResult = Lists.newArrayList(); for (IDBResource dbResource : dbResources) { if (isOrderQuery) { mergeResult.addAll(_findByQuery(internalQuery, dbResource, clazz, useCache, masterSlave)); } else { if (mergeResult.size() < sum) { mergeResult.addAll(_findByQuery(internalQuery, dbResource, clazz, useCache, masterSlave)); } else { dbResource.close(); } } } // query from master again if (mergeResult.isEmpty() && isFromSlave) { for (IDBResource dbResource : dbResources) { dbResource.close(); } dbResources = this.dbCluster.getAllMasterShardingDBResource(clazz); for (IDBResource dbResource : dbResources) { if (isOrderQuery) { mergeResult.addAll(_findByQuery(internalQuery, dbResource, clazz, useCache, masterSlave)); } else { if (mergeResult.size() < sum) { mergeResult.addAll(_findByQuery(internalQuery, dbResource, clazz, useCache, masterSlave)); } else { dbResource.close(); } } } } // if order by exists, sort by order. if (orderList != null && !orderList.isEmpty()) { Collections.sort(mergeResult, new Comparator<T>() { @Override public int compare(T o1, T o2) { Object v1 = null, v2 = null; Class<?> fieldType = null; int compareVal = 0; for (OrderBy order : orderList) { v1 = BeansUtil.getProperty(o1, order.getField()); v2 = BeansUtil.getProperty(o2, order.getField()); fieldType = order.getFieldType(); if (fieldType == Boolean.class || fieldType == Boolean.TYPE) { compareVal = ((Boolean) v1).compareTo((Boolean) v2); } else if (fieldType == Character.class || fieldType == Character.TYPE) { compareVal = ((Character) v1).compareTo((Character) v2); } else if (fieldType == Byte.class || fieldType == Byte.TYPE) { compareVal = ((Byte) v1).compareTo((Byte) v2); } else if (fieldType == Short.class || fieldType == Short.TYPE) { compareVal = ((Short) v1).compareTo((Short) v2); } else if (fieldType == Integer.class || fieldType == Integer.TYPE) { compareVal = ((Integer) v1).compareTo((Integer) v2); } else if (fieldType == Long.class || fieldType == Long.TYPE) { compareVal = ((Long) v1).compareTo((Long) v2); } else if (fieldType == Float.class || fieldType == Float.TYPE) { compareVal = ((Float) v1).compareTo((Float) v2); } else if (fieldType == Double.class || fieldType == Double.TYPE) { compareVal = ((Double) v1).compareTo((Double) v2); } else if (fieldType == String.class) { compareVal = ((String) v1).compareTo((String) v2); } else if (fieldType == Date.class) { compareVal = ((Date) v1).compareTo((Date) v2); } else if (fieldType == Timestamp.class) { compareVal = ((Timestamp) v1).compareTo((Timestamp) v2); } else { throw new RuntimeException("无法排序的类型" + order); } if (order.getOrder() == Order.DESC) { compareVal *= -1; } if (compareVal != 0) { break; } } return compareVal; } }); } // get result List<T> result = null; if (start > -1 && limit > -1) { int fromIndex = start; int endIndex = sum > mergeResult.size() ? mergeResult.size() : sum; result = mergeResult.subList(fromIndex, endIndex); } else if (limit > -1) { result = mergeResult.subList(0, limit - 1); } else { result = mergeResult; } return result; } catch (Exception e) { throw new DBOperationException(e); } } @Override public <T> List<T> findByQuery(IQuery<T> query, IShardingKey<?> shardingKey, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { boolean isFromSlave = false; ShardingDBResource dbResource = null; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { dbResource = _getDbFromMaster(clazz, shardingKey); } else { dbResource = _getDbFromSlave(clazz, shardingKey, masterSlave); isFromSlave = true; } List<T> data = _findByQuery(query, dbResource, clazz, useCache, masterSlave); // query from master againe if (data.isEmpty() && isFromSlave) { dbResource.close(); dbResource = _getDbFromMaster(clazz, shardingKey); data = _findByQuery(query, dbResource, clazz, useCache, masterSlave); } return data; } @SuppressWarnings("unchecked") private <T> List<T> _findByQuery(IQuery<T> query, IDBResource dbResource, Class<T> clazz, boolean useCache, EnumDBMasterSlave masterSlave) { List<T> result = Lists.newArrayList(); Transaction tx = null; try { tx = txManager.getTransaction(); if (tx != null) { tx.enlistResource((XAResource) dbResource); } Map<EntityPK, T> data = null; if (isSecondCacheAvailable(clazz, useCache)) { String sCacheKey = ((DefaultQueryImpl<T>) query).getWhereSql().getSecondCacheKey(); List<T> sCacheData = (List<T>) secondCache.get(sCacheKey, (ShardingDBResource) dbResource); if (sCacheData != null && !sCacheData.isEmpty()) { result.addAll(sCacheData); } } if (result == null || result.isEmpty()) { if (isCacheAvailable(clazz, useCache)) { EntityPK[] entityPks = selectPksByQuery((ShardingDBResource) dbResource, query, clazz); data = selectByPksWithCache(dbResource, clazz, entityPks, ((DefaultQueryImpl<T>) query).getOrderList(), useCache); result.addAll(data.values()); } else { result = selectByQuery((ShardingDBResource) dbResource, query, clazz); } if (isSecondCacheAvailable(clazz, useCache)) { String sCacheKey = ((DefaultQueryImpl<T>) query).getWhereSql().getSecondCacheKey(); secondCache.put(sCacheKey, (ShardingDBResource) dbResource, result); } } // 过滤从缓存结果, 将没有指定的字段设置为默认值. List<T> filteResult = new ArrayList<T>(result.size()); if (((DefaultQueryImpl<T>) query).hasQueryFields()) { for (T obj : result) { try { filteResult.add((T) BeansUtil.cloneWithGivenField(obj, ((DefaultQueryImpl<T>) query).getFields())); } catch (Exception e) { throw new DBOperationException(e); } } result = filteResult; } return result; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } @Override public List<Map<String, Object>> findBySql(SQL sql, EnumDBMasterSlave masterSlave) { throw new UnsupportedOperationException("not support"); } @Override public List<Map<String, Object>> findBySql(SQL sql, IShardingKey<?> shardingKey, EnumDBMasterSlave masterSlave) { Transaction tx = null; ShardingDBResource dbResource = _getDbBySQL(sql, shardingKey, masterSlave); try { tx = txManager.getTransaction(); if (tx != null) { tx.enlistResource(dbResource); } boolean isFromSlave = false; if (EnumDBMasterSlave.MASTER != masterSlave && this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { isFromSlave = true; } List<Map<String, Object>> result = selectBySql(dbResource, sql); // query from master againe if (result.isEmpty() && isFromSlave) { dbResource = _getDbBySQL(sql, shardingKey, EnumDBMasterSlave.MASTER); result = selectBySql(dbResource, sql); } return result; } catch (Exception e) { if (tx != null) { try { tx.rollback(); } catch (Exception e1) { throw new DBOperationException(e1); } } throw new DBOperationException(e); } finally { if (tx == null && dbResource != null) { dbResource.close(); } } } private ShardingDBResource _getDbBySQL(SQL sql, IShardingKey<?> shardingKey, EnumDBMasterSlave masterSlave) { ShardingDBResource next = null; for (String tableName : sql.getTableNames()) { ShardingDBResource cur = null; if (EnumDBMasterSlave.MASTER == masterSlave || !this.dbCluster.isShardingSlaveExist(shardingKey.getClusterName())) { cur = _getDbFromMaster(tableName, shardingKey); } else { cur = _getDbFromSlave(tableName, shardingKey, masterSlave); } if (next != null && (cur != next)) { throw new DBOperationException("the tables in sql maybe not at the same database"); } next = cur; } return next; } /** * 路由选择. * * @param clazz 数据对象 * @param shardingKey 路由因子 */ private ShardingDBResource _getDbFromMaster(Class<?> clazz, IShardingKey<?> shardingKey) { String tableName = entityMetaManager.getTableName(clazz); return _getDbFromMaster(tableName, shardingKey); } private ShardingDBResource _getDbFromMaster(String tableName, IShardingKey<?> shardingKey) { ShardingDBResource shardingDBResource = null; try { shardingDBResource = (ShardingDBResource) this.dbCluster.selectDBResourceFromMaster(tableName, shardingKey); if (LOG.isDebugEnabled()) { LOG.debug("[" + shardingDBResource + "]"); } } catch (DBClusterException e) { throw new DBOperationException(e); } return shardingDBResource; } /** * 路由选择. * * @param clazz 数据对象 * @param shardingKey 路由因子 */ private ShardingDBResource _getDbFromSlave(Class<?> clazz, IShardingKey<?> shardingKey, EnumDBMasterSlave masterSlave) { String tableName = entityMetaManager.getTableName(clazz); ShardingDBResource shardingDBResource = null; try { shardingDBResource = (ShardingDBResource) this.dbCluster.selectDBResourceFromSlave(tableName, shardingKey, masterSlave); if (LOG.isDebugEnabled()) { LOG.debug("[" + shardingDBResource + "]"); } } catch (DBClusterException e) { throw new DBOperationException(e); } return shardingDBResource; } private ShardingDBResource _getDbFromSlave(String tableName, IShardingKey<?> shardingKey, EnumDBMasterSlave slave) { ShardingDBResource shardingDBResource = null; try { shardingDBResource = (ShardingDBResource) this.dbCluster.selectDBResourceFromSlave(tableName, shardingKey, slave); if (LOG.isDebugEnabled()) { LOG.debug("[" + shardingDBResource + "]"); } } catch (DBClusterException e) { throw new DBOperationException(e); } return shardingDBResource; } }