Merged V2.0 to HEAD

5447: (From V1.4 5278, 5279, 5280, 5285, 5298, 5299, 5304): Hibernate session size management for large transactions


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5481 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-04-11 23:03:02 +00:00
parent f43d8864cb
commit d3e08db677
11 changed files with 895 additions and 30 deletions

View File

@@ -25,9 +25,11 @@
package org.alfresco.repo.domain.hibernate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -44,6 +46,7 @@ import org.alfresco.repo.domain.Store;
import org.alfresco.repo.domain.StoreKey;
import org.alfresco.repo.domain.Transaction;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
@@ -51,6 +54,7 @@ import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.GenericJDBCException;
@@ -64,7 +68,6 @@ import org.hibernate.exception.GenericJDBCException;
public class HibernateNodeTest extends BaseSpringTest
{
private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/HibernateNodeTest";
private static int i = 0;
private Store store;
private Server server;
@@ -360,6 +363,120 @@ public class HibernateNodeTest extends BaseSpringTest
txn.rollback();
}
}
/**
* This test demonstrates how entities are effectively rendered useless when the session
* is cleared. The object itself will appear to behave properly, but it is only when
* it comes to retrieving the associated values that one discovers that they were not
* persisted at all. Uncomment at <b>UNCOMMENT FOR FAILURE</b> to see the effect in action.
*/
public void testPostCommitClearIssue() throws Exception
{
// commit the transaction
setComplete();
endTransaction();
// Start a transaction explicitly
TransactionService transactionService = (TransactionService) applicationContext.getBean("transactionComponent");
UserTransaction txn = transactionService.getUserTransaction();
// We need a listener
TestPostCommitClearIssueHelper listener = new TestPostCommitClearIssueHelper();
try
{
txn.begin();
// Bind the listener
AlfrescoTransactionSupport.bindListener(listener);
// Bind a list of node IDs into the transaction
List<Long> nodeIds = new ArrayList<Long>(100);
AlfrescoTransactionSupport.bindResource("node_ids", nodeIds);
// Bind the session in, too
Session session = getSession();
AlfrescoTransactionSupport.bindResource("session", session);
// Make a whole lot of nodes with aspects and properties
for (int i = 0; i < 100; i++)
{
// make a node
Node node = new NodeImpl();
node.setStore(store);
node.setUuid(GUID.generate());
node.setTypeQName(ContentModel.TYPE_CONTENT);
Long nodeId = (Long) getSession().save(node);
// Record the ID
nodeIds.add(nodeId);
// Now flush and clear
/* UNCOMMENT FOR FAILURE */
/* flushAndClear(); */
// add some aspects to the node
Set<QName> aspects = node.getAspects();
aspects.add(ContentModel.ASPECT_AUDITABLE);
// add some properties
Map<QName, PropertyValue> properties = node.getProperties();
properties.put(ContentModel.PROP_NAME, new PropertyValue(DataTypeDefinition.TEXT, "ABC"));
}
// Commit the transaction
txn.commit();
}
catch (Throwable e)
{
try { txn.rollback(); } catch (Throwable ee) {}
}
// Did the listener find any issues?
if (listener.err != null)
{
fail(listener.err);
}
}
/** Helper class to test entities during transaction wind-down */
private class TestPostCommitClearIssueHelper extends TransactionListenerAdapter
{
public String err = null;
@SuppressWarnings("unchecked")
@Override
public void beforeCommit(boolean readOnly)
{
// Get the session
Session session = (Session) AlfrescoTransactionSupport.getResource("session");
// Get the node IDs
List<Long> nodeIds = (List<Long>) AlfrescoTransactionSupport.getResource("node_ids");
// Check each node for the aspects and properties required
int incorrectAspectCount = 0;
int incorrectPropertyCount = 0;
for (Long nodeId : nodeIds)
{
Node node = (Node) session.get(NodeImpl.class, nodeId);
Set<QName> aspects = node.getAspects();
Map<QName, PropertyValue> properties = node.getProperties();
if (!aspects.contains(ContentModel.ASPECT_AUDITABLE))
{
// Missing the aspect
incorrectAspectCount++;
}
if (!properties.containsKey(ContentModel.PROP_NAME))
{
// Missing property
incorrectPropertyCount++;
}
}
// What is the outcome?
if (incorrectAspectCount > 0 || incorrectPropertyCount > 0)
{
this.err =
"Checked " + nodeIds.size() + " nodes and found: \n" +
" " + incorrectAspectCount + " missing aspects and \n" +
" " + incorrectPropertyCount + " missing properties.";
}
// Force a rollback anyway, just to stop an explosion of data
throw new RuntimeException("ROLLBACK");
}
}
/**
* Create some simple parent-child relationships and flush them. Then read them back in without

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing
*/
package org.alfresco.repo.domain.hibernate;
import java.lang.reflect.Method;
import java.util.Map;
import org.alfresco.util.resource.MethodResourceManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.stat.SessionStatistics;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/**
* A Hibernate-specific resource manager that ensures that the current <code>Session</code>'s
* entity count doesn't exceed a given threshold.
* <p/>
* <b>NOTE: VERY IMPORTANT</b><br/>
* Do not, under any circumstances, attach an instance of this class to an API that
* passes stateful objects back and forth. There must be no <code>Session</code>-linked
* objects up the stack from where this instance resides. Failure to observe this will
* most likely result in data loss of a sporadic nature.
*
* @see org.alfresco.repo.domain.hibernate.HibernateNodeTest#testPostCommitClearIssue()
*
* @author Derek Hulley
*/
public class SessionSizeResourceManager extends HibernateDaoSupport implements MethodResourceManager
{
private static Log logger = LogFactory.getLog(SessionSizeResourceManager.class);
/** Default 1000 */
private int threshold = 1000;
/**
* Set the {@link Session#clear()} threshold. If the number of entities and collections in the
* current session exceeds this number, then the session will be cleared. Have you read the
* disclaimer?
*
* @param threshold the maximum number of entities and associations to keep in memory
*
* @see #threshold
*/
public void setThreshold(int threshold)
{
this.threshold = threshold;
}
public void manageResources(
Map<Method, MethodStatistics> methodStatsByMethod,
long transactionElapsedTimeNs,
Method currentMethod)
{
Session session = getSession(false);
SessionStatistics stats = session.getStatistics();
int entityCount = stats.getEntityCount();
int collectionCount = stats.getCollectionCount();
if ((entityCount + collectionCount) > threshold)
{
session.flush();
session.clear();
if (logger.isDebugEnabled())
{
String msg = String.format(
"Cleared %5d entities and %5d collections from Hibernate Session",
entityCount,
collectionCount);
logger.debug(msg);
}
}
}
}