mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Fixed ALF-10964: Add back cache for getChildByName
- Originally removed as part of the 'reverse lookup' of parentAssocsCache - This cache is NOT clustered; the child target version is checked; requery if necessary - NB: Cache misses are NOT cached. Do do so would mean making the cache clustered. It is better to avoid querying for random files that don't exist over and over. Add a higher level cache (as is done in CIFS) for that case. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31417 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -309,6 +309,39 @@
|
|||||||
<property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" />
|
<property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<!-- ===================================== -->
|
||||||
|
<!-- Child by cm:name lookup for nodes -->
|
||||||
|
<!-- ===================================== -->
|
||||||
|
|
||||||
|
<!-- The cross-transaction shared cache for Child-by-name -->
|
||||||
|
|
||||||
|
<bean name="node.childByNameSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
|
||||||
|
<property name="cache">
|
||||||
|
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
|
||||||
|
<property name="cacheManager">
|
||||||
|
<ref bean="internalEHCacheManager" />
|
||||||
|
</property>
|
||||||
|
<property name="cacheName">
|
||||||
|
<value>org.alfresco.cache.node.childByNameCache</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- The transactional cache for Child-by-name -->
|
||||||
|
|
||||||
|
<bean name="node.childByNameCache" class="org.alfresco.repo.cache.TransactionalCache">
|
||||||
|
<property name="sharedCache">
|
||||||
|
<ref bean="node.parentAssocsSharedCache" />
|
||||||
|
</property>
|
||||||
|
<property name="name">
|
||||||
|
<value>org.alfresco.cache.node.childByNameTransactionalCache</value>
|
||||||
|
</property>
|
||||||
|
<property name="maxCacheSize" value="65000" />
|
||||||
|
<property name="mutable" value="true" />
|
||||||
|
<property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
<!-- ===================================== -->
|
<!-- ===================================== -->
|
||||||
<!-- Rules lookup for nodes -->
|
<!-- Rules lookup for nodes -->
|
||||||
<!-- ===================================== -->
|
<!-- ===================================== -->
|
||||||
|
@@ -120,6 +120,7 @@
|
|||||||
<property name="aspectsCache" ref="node.aspectsCache"/>
|
<property name="aspectsCache" ref="node.aspectsCache"/>
|
||||||
<property name="propertiesCache" ref="node.propertiesCache"/>
|
<property name="propertiesCache" ref="node.propertiesCache"/>
|
||||||
<property name="parentAssocsCache" ref="node.parentAssocsCache"/>
|
<property name="parentAssocsCache" ref="node.parentAssocsCache"/>
|
||||||
|
<property name="childByNameCache" ref="node.childByNameCache"/>
|
||||||
</bean>
|
</bean>
|
||||||
<bean id="nodeDAO.org.hibernate.dialect.Dialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl" parent="nodeDAObase" />
|
<bean id="nodeDAO.org.hibernate.dialect.Dialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl" parent="nodeDAObase" />
|
||||||
<bean id="nodeDAO.org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.hibernate.dialect.Dialect" />
|
<bean id="nodeDAO.org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.hibernate.dialect.Dialect" />
|
||||||
|
@@ -35,6 +35,9 @@
|
|||||||
overflowToDisk="false"
|
overflowToDisk="false"
|
||||||
statistics="false"
|
statistics="false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Node caches -->
|
||||||
|
|
||||||
<cache
|
<cache
|
||||||
name="org.alfresco.cache.node.rootNodesCache"
|
name="org.alfresco.cache.node.rootNodesCache"
|
||||||
maxElementsInMemory="500"
|
maxElementsInMemory="500"
|
||||||
@@ -77,6 +80,13 @@
|
|||||||
overflowToDisk="false"
|
overflowToDisk="false"
|
||||||
statistics="false"
|
statistics="false"
|
||||||
/>
|
/>
|
||||||
|
<cache
|
||||||
|
name="org.alfresco.cache.node.childByNameCache"
|
||||||
|
maxElementsInMemory="130000"
|
||||||
|
eternal="true"
|
||||||
|
overflowToDisk="false"
|
||||||
|
statistics="false"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- AVM caches -->
|
<!-- AVM caches -->
|
||||||
|
|
||||||
|
@@ -197,6 +197,16 @@
|
|||||||
replicateAsynchronously = false"/>
|
replicateAsynchronously = false"/>
|
||||||
</cache>
|
</cache>
|
||||||
|
|
||||||
|
<cache
|
||||||
|
name="org.alfresco.cache.node.childByNameCache"
|
||||||
|
maxElementsInMemory="130000"
|
||||||
|
eternal="true"
|
||||||
|
overflowToDisk="false"
|
||||||
|
statistics="false"
|
||||||
|
>
|
||||||
|
<!-- Not clustered -->
|
||||||
|
</cache>
|
||||||
|
|
||||||
<cache
|
<cache
|
||||||
name="org.alfresco.cache.avm.avmEntityCache"
|
name="org.alfresco.cache.avm.avmEntityCache"
|
||||||
maxElementsInMemory="10000"
|
maxElementsInMemory="10000"
|
||||||
|
@@ -42,6 +42,7 @@ import org.alfresco.ibatis.BatchingDAO;
|
|||||||
import org.alfresco.ibatis.RetryingCallbackHelper;
|
import org.alfresco.ibatis.RetryingCallbackHelper;
|
||||||
import org.alfresco.ibatis.RetryingCallbackHelper.RetryingCallback;
|
import org.alfresco.ibatis.RetryingCallbackHelper.RetryingCallback;
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.cache.NullCache;
|
||||||
import org.alfresco.repo.cache.SimpleCache;
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||||
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
|
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
|
||||||
@@ -173,6 +174,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
*/
|
*/
|
||||||
private EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable> parentAssocsCache;
|
private EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable> parentAssocsCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for fast lookups of child nodes by <b>cm:name</b>.
|
||||||
|
*/
|
||||||
|
private SimpleCache<ChildByNameKey, ChildAssocEntity> childByNameCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Set up various instance-specific members such as caches and locks.
|
* Constructor. Set up various instance-specific members such as caches and locks.
|
||||||
*/
|
*/
|
||||||
@@ -187,6 +193,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO());
|
aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO());
|
||||||
propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO());
|
propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO());
|
||||||
parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable>(new ParentAssocsCallbackDAO());
|
parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable>(new ParentAssocsCallbackDAO());
|
||||||
|
childByNameCache = new NullCache<ChildByNameKey, ChildAssocEntity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,6 +353,16 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
new ParentAssocsCallbackDAO());
|
new ParentAssocsCallbackDAO());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cache that maintains lookups by child <b>cm:name</b>
|
||||||
|
*
|
||||||
|
* @param childByNameCache the cache
|
||||||
|
*/
|
||||||
|
public void setChildByNameCache(SimpleCache<ChildByNameKey, ChildAssocEntity> childByNameCache)
|
||||||
|
{
|
||||||
|
this.childByNameCache = childByNameCache;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize
|
* Initialize
|
||||||
*/
|
*/
|
||||||
@@ -2018,6 +2035,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
// Touch to bring into current transaction
|
// Touch to bring into current transaction
|
||||||
if (updated)
|
if (updated)
|
||||||
{
|
{
|
||||||
|
// We have to explicitly update the node (sys:locale or cm:auditable)
|
||||||
updateNodeImpl(node, nodeUpdate);
|
updateNodeImpl(node, nodeUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3023,9 +3041,59 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks a cache and then queries.
|
||||||
|
* <p/>
|
||||||
|
* Note: If we were to cach misses, then we would have to ensure that the cache is
|
||||||
|
* kept up to date whenever any affection association is changed. This is actually
|
||||||
|
* not possible without forcing the cache to be fully clustered. So to
|
||||||
|
* avoid clustering the cache, we instead watch the node child version,
|
||||||
|
* which relies on a cache that is already clustered.
|
||||||
|
*/
|
||||||
public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName)
|
public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName)
|
||||||
{
|
{
|
||||||
ChildAssocEntity assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName);
|
ChildByNameKey key = new ChildByNameKey(parentNodeId, assocTypeQName, childName);
|
||||||
|
ChildAssocEntity assoc = childByNameCache.get(key);
|
||||||
|
boolean query = false;
|
||||||
|
if (assoc == null)
|
||||||
|
{
|
||||||
|
query = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check that the resultant child node has not moved on
|
||||||
|
Node childNode = assoc.getChildNode();
|
||||||
|
Long childNodeId = childNode.getId();
|
||||||
|
NodeVersionKey childNodeVersionKey = childNode.getNodeVersionKey();
|
||||||
|
Pair<Long, Node> childNodeFromCache = nodesCache.getByKey(childNodeId);
|
||||||
|
if (childNodeFromCache == null)
|
||||||
|
{
|
||||||
|
// Child node no longer exists (or never did)
|
||||||
|
query = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NodeVersionKey childNodeFromCacheVersionKey = childNodeFromCache.getSecond().getNodeVersionKey();
|
||||||
|
if (!childNodeFromCacheVersionKey.equals(childNodeVersionKey))
|
||||||
|
{
|
||||||
|
// The child node has moved on. We don't know why, but must query again.
|
||||||
|
query = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query)
|
||||||
|
{
|
||||||
|
assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName);
|
||||||
|
if (assoc != null)
|
||||||
|
{
|
||||||
|
childByNameCache.put(key, assoc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We do not cache misses. See javadoc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now return, checking the assoc's ID for null
|
||||||
return assoc == null ? null : assoc.getPair(qnameDAO);
|
return assoc == null ? null : assoc.getPair(qnameDAO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -500,4 +500,39 @@ public class NodeServiceTest extends TestCase
|
|||||||
List<ChildAssociationRef> parentAssocsPost = nodeService.getParentAssocs(secondaryNodeRef);
|
List<ChildAssociationRef> parentAssocsPost = nodeService.getParentAssocs(secondaryNodeRef);
|
||||||
assertEquals("Incorrect number of parent assocs", 1, parentAssocsPost.size());
|
assertEquals("Incorrect number of parent assocs", 1, parentAssocsPost.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that file renames are handled when getting children
|
||||||
|
*/
|
||||||
|
public void testCaches_RenameNode()
|
||||||
|
{
|
||||||
|
final NodeRef[] nodeRefs = new NodeRef[2];
|
||||||
|
final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||||
|
buildNodeHierarchy(workspaceRootNodeRef, nodeRefs);
|
||||||
|
|
||||||
|
// What is the name of the first child?
|
||||||
|
String name = (String) nodeService.getProperty(nodeRefs[1], ContentModel.PROP_NAME);
|
||||||
|
// Now query for it
|
||||||
|
NodeRef nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
|
||||||
|
assertNotNull("Did not find node by name", nodeRefCheck);
|
||||||
|
assertEquals("Node found was not correct", nodeRefs[1], nodeRefCheck);
|
||||||
|
|
||||||
|
// Rename the node
|
||||||
|
nodeService.setProperty(nodeRefs[1], ContentModel.PROP_NAME, "New Name");
|
||||||
|
// Should find nothing
|
||||||
|
nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
|
||||||
|
assertNull("Should not have found anything", nodeRefCheck);
|
||||||
|
|
||||||
|
// Add another child with the same original name
|
||||||
|
NodeRef newChildNodeRef = nodeService.createNode(
|
||||||
|
nodeRefs[0],
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QName.createQName(NAMESPACE, name),
|
||||||
|
ContentModel.TYPE_FOLDER,
|
||||||
|
Collections.singletonMap(ContentModel.PROP_NAME, (Serializable)name)).getChildRef();
|
||||||
|
// We should find this new node when looking for the name
|
||||||
|
nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
|
||||||
|
assertNotNull("Did not find node by name", nodeRefCheck);
|
||||||
|
assertEquals("Node found was not correct", newChildNodeRef, nodeRefCheck);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user