Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.landawn.abacus.core.SQLQueryCache Maven / Gradle / Ivy
/*
* Copyright (c) 2015, Haiyang Li.
*
* 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 com.landawn.abacus.core;
import java.lang.ref.SoftReference;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.landawn.abacus.annotation.Internal;
import com.landawn.abacus.cache.AbstractQueryCache;
import com.landawn.abacus.cache.Cache;
import com.landawn.abacus.cache.DataGrid;
import com.landawn.abacus.core.AbacusConfiguration.EntityManagerConfiguration.QueryCacheConfiguration;
import com.landawn.abacus.core.command.Command;
import com.landawn.abacus.core.command.SQLCommandFactory;
import com.landawn.abacus.core.command.SQLOperationCommand;
import com.landawn.abacus.exception.UncheckedSQLException;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.metadata.EntityDefinition;
import com.landawn.abacus.metadata.Property;
import com.landawn.abacus.util.AsyncExecutor;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Objectory;
import com.landawn.abacus.util.OperationType;
import com.landawn.abacus.util.Options;
import com.landawn.abacus.util.Throwables;
import com.landawn.abacus.util.WD;
// TODO: Auto-generated Javadoc
/**
*
* @author Haiyang Li
* @since 0.8
*/
@Internal
class SQLQueryCache extends AbstractQueryCache {
private static final Logger logger = LoggerFactory.getLogger(SQLQueryCache.class);
private static final String WHERE = WD.SPACE + WD.WHERE + WD.SPACE;
private static final String WHERE_PARENTHESES_L = WHERE + WD._PARENTHESES_L;
private static final String PARENTHESES_R_AND_PARENTHESES_L = WD.PARENTHESES_R_SPACE + WD.AND + WD.SPACE_PARENTHESES_L;
private static final AsyncExecutor ASYNC_EXECUTOR = new AsyncExecutor();
private static final Map CACHED_DATA_GRID_ID_IN_EXECUTING = new ConcurrentHashMap<>();
private final Cache> dataGridCache;
private final String dataGridCacheId;
private SoftReference> softDataGrid;
private Executant executant;
private SQLOperationCommand queryCmd;
public SQLQueryCache() {
this(QueryCacheConfiguration.DEFAULT_LIVE_TIME, QueryCacheConfiguration.DEFAULT_MAX_IDLE_TIME, null);
}
public SQLQueryCache(long liveTime, long maxIdleTime, Cache> dataGridCache) {
super(liveTime, maxIdleTime);
this.dataGridCache = dataGridCache;
this.dataGridCacheId = dataGridCache == null ? null : N.uuid();
}
/**
*
* @param queryResult
* @param cachePropNames
* @param cacheCond
* @param range
*/
@Override
public void cacheResult(SQLResult queryResult, Collection cachePropNames, Options.Cache.Condition cacheCond, Options.Cache.Range range) {
assertNotClosed();
try {
synchronized (queryResult) {
if ((queryResult.getExecutionTime() >= cacheCond.getMinQueryTime())
|| ((queryResult.size() >= cacheCond.getMinCount()) && (queryResult.size() <= cacheCond.getMaxCount()))) {
cacheResult(queryResult, range.getRangeBitSet(), cachePropNames);
lastUpdatedTime = System.currentTimeMillis();
}
queryCmd = (SQLOperationCommand) queryResult.getSQLCommand();
executant = queryResult.getExecutant();
}
} catch (Exception e) {
logger.error("Failed to cache result by condition: " + cacheCond, e);
}
}
/**
* Async cache result.
*
* @param queryResult
* @param cachePropNames
* @param cacheCond
* @param range
* @param closeResult
*/
@Override
public void asyncCacheResult(final SQLResult queryResult, final Collection cachePropNames, final Options.Cache.Condition cacheCond,
final Options.Cache.Range range, final boolean closeResult) {
assertNotClosed();
ASYNC_EXECUTOR.execute(new Throwables.Runnable() {
@Override
public void run() {
try {
cacheResult(queryResult, cachePropNames, cacheCond, range);
} finally {
if (closeResult) {
queryResult.close();
}
}
}
});
}
/**
*
* @param queryResult
* @param rowRange
* @param selectPropNames
* @throws UncheckedSQLException the unchecked SQL exception
*/
private void cacheResult(SQLResult queryResult, BitSet rowRange, Collection selectPropNames) throws UncheckedSQLException {
DataGrid dataGrid = null;
rwLock.writeLock().lock();
try {
if (isClosed()) {
return;
}
int[] propPositionInThisCache = getPropPositionInCache(selectPropNames);
dataGrid = init(queryResult, selectPropNames);
int propCount = selectPropNames.size();
for (int fromIndex = rowRange.nextSetBit(0), endIndex = 0; fromIndex > -1; fromIndex = rowRange.nextSetBit(endIndex)) {
endIndex = rowRange.nextClearBit(fromIndex);
if (queryResult.absolute(fromIndex)) {
int rowNum = fromIndex;
do {
for (int propIndex = 0; propIndex < propCount; propIndex++) {
if (dataGrid.isClean(propPositionInThisCache[propIndex], rowNum)) {
dataGrid.put(propPositionInThisCache[propIndex], rowNum, queryResult.get(propIndex));
}
}
rowNum++;
} while (queryResult.next() && (rowNum < endIndex));
}
}
if ((dataGridCache != null) && (dataGrid != null)) {
boolean isToCache = false;
synchronized (CACHED_DATA_GRID_ID_IN_EXECUTING) {
if (!CACHED_DATA_GRID_ID_IN_EXECUTING.containsKey(dataGridCacheId)) {
CACHED_DATA_GRID_ID_IN_EXECUTING.put(dataGridCacheId, dataGridCacheId);
isToCache = true;
}
}
if (isToCache) {
try {
dataGridCache.put(dataGridCacheId, dataGrid, activityPrint().getLiveTime(), activityPrint().getMaxIdleTime());
} finally {
CACHED_DATA_GRID_ID_IN_EXECUTING.remove(dataGridCacheId);
}
}
}
} catch (SQLException e) {
throw new UncheckedSQLException(e);
} finally {
rwLock.writeLock().unlock();
}
//
// if (dataGridCache != null) {
// synchronized (CACHED_DATA_GRID_ID_IN_EXECUTING) {
// if (!CACHED_DATA_GRID_ID_IN_EXECUTING.containsKey(dataGridIdInCache)) {
// CACHED_DATA_GRID_ID_IN_EXECUTING.put(dataGridIdInCache, dataGridIdInCache);
//
// Runnable command = new Runnable() {
// /**
// * Method run.
// *
// * @see java.lang.Runnable#run()
// */
// @Override
// public void run() {
// rwLock.writeLock().lock();
//
// try {
// if (!isClosed()) {
// final DataGrid dataGrid = getDataGrid();
//
// if (dataGrid != null) {
// dataGridCache.put(dataGridIdInCache, dataGrid, getActivityPrint().getLiveTime(), getActivityPrint().getMaxIdleTime());
// }
// }
// } finally {
// CACHED_DATA_GRID_ID_IN_EXECUTING.remove(dataGridIdInCache);
// rwLock.writeLock().unlock();
// }
// }
// };
//
// ASYNC_EXECUTOR.execute(command);
// }
// }
// }
}
/**
*
* @param command
* @param options
* @return true, if successful
*/
@Override
public synchronized boolean update(Command command, Map options) {
assertNotClosed();
if (command.getOperationType() == OperationType.QUERY) {
return true;
}
SQLOperationCommand updateCmd = (SQLOperationCommand) command;
if ((queryCmd == null) || (queryCmd.getWhereBeginIndex() < 0) || (updateCmd.getWhereBeginIndex() < 0)) {
return false;
}
for (String updateTableName : updateCmd.getTargetTables()) {
if (N.notNullOrEmpty(queryCmd.getSubQueryTables())) {
for (String tableName : queryCmd.getSubQueryTables()) {
if (tableName.equalsIgnoreCase(updateTableName)) {
if (logger.isInfoEnabled()) {
logger.info("Remove query cache because the table in sub query is updated. Cache query SQL: " + queryCmd.getSql() + ". Update SQL: "
+ updateCmd.getParameterCount());
}
return false;
}
}
}
boolean isUpdated = false;
if (N.notNullOrEmpty(queryCmd.getTargetTables())) {
for (String tableName : queryCmd.getTargetTables()) {
if (tableName.equalsIgnoreCase(updateTableName)) {
isUpdated = true;
break;
}
}
}
if (!isUpdated && N.notNullOrEmpty(queryCmd.getJoinTables())) {
for (String tableName : queryCmd.getJoinTables()) {
if (tableName.equalsIgnoreCase(updateTableName)) {
isUpdated = true;
break;
}
}
}
//
// // TODO it's too complicate to check update and just return false.
// if (isUpdated) {
// return false;
// }
//
if (isUpdated) {
final EntityDefinition updateEntityDef = updateCmd.getEntityDef();
if (updateCmd.getOperationType() == OperationType.UPDATE && queryCmd.getWhereBeginIndex() > 0) {
String lowerCaseQuerySql = queryCmd.getSql().toLowerCase();
int querySqlWhereBeginIndex = queryCmd.getWhereBeginIndex();
int querySqlWhereEndIndex = queryCmd.getWhereEndIndex();
Property prop = null;
int index = -1;
for (String propName : updateCmd.getTargetPropNames()) {
prop = updateEntityDef.getProperty(propName);
index = lowerCaseQuerySql.indexOf(prop.getColumnName().toLowerCase(), querySqlWhereBeginIndex);
if ((index >= 0) && (index < querySqlWhereEndIndex)) {
if (logger.isInfoEnabled()) {
logger.info("Remove query cache because the property in query condition is updated. Cache query SQL: " + queryCmd.getSql()
+ ". Update SQL: " + updateCmd.getParameterCount());
}
return false;
}
}
}
SQLOperationCommand checkCmd = createCheckSqlCmd(updateCmd);
SQLResult checkSQLResult = null;
try {
checkSQLResult = executant.internalExecuteQuery(checkCmd, false, true, options);
if (checkSQLResult.next()) {
if (updateCmd.getOperationType() == OperationType.UPDATE) {
Collection cachedPropNames = new ArrayList<>(getCachedPropNames());
Property prop = null;
for (String cachedPropName : cachedPropNames) {
prop = queryCmd.getEntityDef().getProperty(cachedPropName);
if (prop == null || updateCmd.getTargetPropNames().contains(prop.getName())) {
if (logger.isInfoEnabled()) {
logger.info("Remove cached property from query cache" + cachedPropName + " because it's updated. Cache query SQL: "
+ queryCmd.getSql() + ". update SQL: " + updateCmd.getParameterCount());
}
removeResult(cachedPropName);
}
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Remove query cache because some cached record is deleted. Cache query SQL: " + queryCmd.getSql() + ". update SQL: "
+ updateCmd.getParameterCount());
}
return false;
}
}
} catch (Exception e) {
if (logger.isInfoEnabled()) {
logger.info("Remove query cache because of error: " + e.getMessage() + ". Cache query SQL: " + queryCmd.getSql() + ". update SQL: "
+ updateCmd.getParameterCount());
}
return false;
} finally {
if (checkSQLResult != null) {
checkSQLResult.close();
}
}
}
}
return true;
}
/**
* Removes the result.
*
* @param propName
*/
@Override
public void removeResult(String propName) {
assertNotClosed();
rwLock.writeLock().lock();
try {
DataGrid dataGrid = getDataGrid();
if (dataGrid != null) {
int propIndex = checkPropIndex(propName);
dataGrid.clearX(propIndex);
if (dataGridCache != null) {
dataGridCache.remove(dataGridCacheId);
dataGridCache.put(dataGridCacheId, dataGrid, activityPrint().getLiveTime(), activityPrint().getMaxIdleTime());
}
lastUpdatedTime = System.currentTimeMillis();
}
} catch (Exception e) {
if (logger.isInfoEnabled()) {
logger.info("Failed to remove cache result by property name: " + propName, e);
}
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Gets the data grid.
*
* @return
*/
@Override
public DataGrid getDataGrid() {
assertNotClosed();
DataGrid dataGrid = null;
if (softDataGrid != null) {
dataGrid = softDataGrid.get();
}
if (dataGrid == null) {
// TODO, is possible. it's too slow to transfer huge array to/from
// remote.
if (dataGridCache != null) {
try {
dataGrid = dataGridCache.get(dataGridCacheId).orElse(null);
} catch (Exception e) {
if (logger.isInfoEnabled()) {
logger.info("Failed to get DataGrid by id: " + dataGridCacheId, e);
}
}
if (dataGrid != null) {
softDataGrid = new SoftReference<>(dataGrid);
}
}
}
return dataGrid;
}
/**
* Zip.
*/
@Override
public void zip() {
assertNotClosed();
rwLock.writeLock().lock();
try {
if (softDataGrid != null) {
DataGrid dataGrid = softDataGrid.get();
if (dataGrid != null) {
dataGrid.zip();
}
}
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Close.
*/
@Override
public void close() {
rwLock.writeLock().lock();
try {
if (isClosed) {
return;
}
DataGrid dataGrid = getDataGrid();
if (dataGrid != null) {
dataGrid.clear();
}
if (softDataGrid != null) {
softDataGrid.clear();
}
if (dataGridCache != null) {
try {
dataGridCache.remove(dataGridCacheId);
} catch (Exception e) {
if (logger.isInfoEnabled()) {
logger.info("Failed to remove DataGrid by id: " + dataGridCacheId, e);
}
}
}
propNameIndexMap.clear();
lastUpdatedTime = System.currentTimeMillis();
isClosed = true;
} finally {
rwLock.writeLock().unlock();
}
// Runtime.getRuntime().gc();
}
/**
* Creates the check sql cmd.
*
* @param updateCmd
* @return
*/
private SQLOperationCommand createCheckSqlCmd(SQLOperationCommand updateCmd) {
String checkSql = null;
int queryWhereBeginIndex = queryCmd.getWhereBeginIndex();
int queryWhereEndIndex = queryCmd.getWhereEndIndex();
String queryBeforeWhere = queryCmd.getSql().substring(0, queryWhereBeginIndex);
String queryWhere = queryCmd.getSql().substring((queryCmd.getWhereBeginIndex() + 7), queryCmd.getWhereEndIndex());
String udpateWhere = updateCmd.getSql().substring((updateCmd.getWhereBeginIndex() + 7), updateCmd.getWhereEndIndex());
if (updateCmd.isBatch()) {
int batchSize = updateCmd.getBatchParameters().size();
final StringBuilder sb = Objectory.createStringBuilder(batchSize * 32);
for (int i = 0; i < batchSize; i++) {
if (i > 0) {
sb.append(WD._SPACE);
sb.append(WD.OR);
sb.append(WD._SPACE);
}
sb.append(WD._PARENTHESES_L);
sb.append(udpateWhere);
sb.append(WD._PARENTHESES_R);
}
udpateWhere = sb.toString();
Objectory.recycle(sb);
}
checkSql = queryBeforeWhere + WHERE_PARENTHESES_L + udpateWhere + PARENTHESES_R_AND_PARENTHESES_L + queryWhere + WD._PARENTHESES_R
+ queryCmd.getSql().substring(queryWhereEndIndex);
SQLOperationCommand checkSqlCmd = SQLCommandFactory.createSqlCommand(OperationType.QUERY, queryCmd.getEntityDef(), checkSql, queryCmd.getOptions());
int parameterFromIndex = (updateCmd.getOperationType() == OperationType.UPDATE) ? updateCmd.getTargetPropNames().size() : 0;
if (updateCmd.isBatch()) {
List parametersList = updateCmd.getBatchParameters();
for (Object[] parameters : parametersList) {
for (int parameterIndex = parameterFromIndex; parameterIndex < parameters.length; parameterIndex++) {
checkSqlCmd.setParameter(checkSqlCmd.getParameterCount(), parameters[parameterIndex], updateCmd.getParameterType(parameterIndex));
}
}
} else {
checkSqlCmd.appendParameters(updateCmd, parameterFromIndex, updateCmd.getParameterCount());
}
checkSqlCmd.appendParameters(queryCmd);
return checkSqlCmd;
}
/**
* Gets the prop position in cache.
*
* @param cachingPropNames
* @return
* @throws UncheckedSQLException the unchecked SQL exception
*/
private int[] getPropPositionInCache(Collection cachingPropNames) throws UncheckedSQLException {
int[] propPositionInCache = new int[cachingPropNames.size()];
int arrayIndex = 0;
for (String propName : cachingPropNames) {
if (propNameIndexMap.get(propName) != null) {
propPositionInCache[arrayIndex] = propNameIndexMap.get(propName);
} else {
propPositionInCache[arrayIndex] = propNameIndexMap.size();
propNameIndexMap.put(propName, propPositionInCache[arrayIndex]);
}
arrayIndex++;
}
return propPositionInCache;
}
/**
*
* @param queryResult
* @param cachingPropNames
* @return
* @throws UncheckedSQLException the unchecked SQL exception
*/
private DataGrid init(SQLResult queryResult, Collection cachingPropNames) throws UncheckedSQLException {
DataGrid dataGrid = getDataGrid();
if (dataGrid == null) {
dataGrid = new DataGrid<>(cachingPropNames.size(), queryResult.size());
} else if (propNameIndexMap.size() > dataGrid.getX()) {
dataGrid.extendX(propNameIndexMap.size());
}
if ((softDataGrid == null) || (softDataGrid.get() == null)) {
softDataGrid = new SoftReference<>(dataGrid);
}
return dataGrid;
}
}