mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
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:
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user