diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 34e3259493..cded220fcb 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -54,6 +54,7 @@ + diff --git a/config/alfresco/dbscripts/upgrade/2.1/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-2.1-VersionColumns.sql b/config/alfresco/dbscripts/upgrade/2.1/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-2.1-VersionColumns.sql new file mode 100644 index 0000000000..e4b0a3f472 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.1/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-2.1-VersionColumns.sql @@ -0,0 +1,32 @@ +-- +-- Title: Fill 'version' columns with data +-- Database: Generic +-- Since: V2.1 Schema 54 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +UPDATE alf_store SET version = 1 WHERE version IS NULL; +UPDATE alf_node SET version = 1 WHERE version IS NULL; +UPDATE alf_child_assoc SET version = 1 WHERE version IS NULL; +UPDATE alf_node_assoc SET version = 1 WHERE version IS NULL; +UPDATE alf_node_status SET version = 1 WHERE version IS NULL; +UPDATE alf_transaction SET version = 1 WHERE version IS NULL; +UPDATE alf_server SET version = 1 WHERE version IS NULL; +UPDATE alf_access_control_list SET version = 1 WHERE version IS NULL; +UPDATE alf_access_control_entry SET version = 1 WHERE version IS NULL; +UPDATE alf_permission SET version = 1 WHERE version IS NULL; +UPDATE alf_authority SET version = 1 WHERE version IS NULL; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.1-VersionColumns'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V2.1-VersionColumns', 'Manually executed script upgrade V2.1: Created initial version number for ADM entities', + 0, 53, -1, 54, null, 'UNKOWN', 1, 1, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index a3072e86c7..7d2ac5eb62 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -177,7 +177,7 @@ - 2000 + 5000 diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 30fa729fda..157f6ec0d1 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -753,4 +753,16 @@ + + + patch.db-V2.1-VersionColumns + patch.schemaUpgradeScript.description + 0 + 53 + 54 + + classpath:alfresco/dbscripts/upgrade/2.1/${db.script.dialect}/AlfrescoSchemaUpdate-2.1-VersionColumns.sql + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 634797814f..05f7fb9b83 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=53 +version.schema=54 diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTest.java b/source/java/org/alfresco/repo/avm/AVMServiceTest.java index 738faeebb6..cdc50e3709 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTest.java @@ -5247,7 +5247,7 @@ public class AVMServiceTest extends AVMServiceTestBase try { setupBasicTree(); - class TxnCallback implements RetryingTransactionHelper.Callback + class TxnCallback implements RetryingTransactionHelper.RetryingTransactionCallback { public Object execute() { diff --git a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceImpl.java b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceImpl.java index 1ecb873441..5f8742d696 100644 --- a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceImpl.java @@ -34,7 +34,7 @@ import org.alfresco.repo.attributes.ListAttributeValue; import org.alfresco.repo.attributes.MapAttributeValue; import org.alfresco.repo.attributes.StringAttributeValue; import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.Callback; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.attributes.AttrQueryEquals; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.avm.AVMExistsException; @@ -88,7 +88,7 @@ public class AVMLockingServiceImpl implements AVMLockingService public void init() { - Callback callback = new Callback() + RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Object execute() { diff --git a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java index f20f75d711..94597ef6f6 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java +++ b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java @@ -38,6 +38,7 @@ import java.util.Locale; import org.alfresco.error.StackTraceUtil; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; @@ -251,8 +252,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor // nothing to do return; } - RetryingTransactionHelper.Callback cb = - new RetryingTransactionHelper.Callback() + RetryingTransactionCallback cb = new RetryingTransactionCallback() { public Object execute() { @@ -274,7 +274,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor { cb.execute(); } - catch (Exception e) + catch (Throwable e) { throw new ContentIOException("Failed to executed channel close callbacks", e); } @@ -347,8 +347,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor } // We're now doing this in a retrying transaction, which means // that the body of execute() must be idempotent. - RetryingTransactionHelper.Callback cb = - new RetryingTransactionHelper.Callback() + RetryingTransactionCallback cb = new RetryingTransactionCallback() { public Object execute() { @@ -372,7 +371,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor { cb.execute(); } - catch (Exception e) + catch (Throwable e) { throw new ContentIOException("Failed to executed channel close callbacks", e); } diff --git a/source/java/org/alfresco/repo/domain/ChildAssoc.java b/source/java/org/alfresco/repo/domain/ChildAssoc.java index 31c210b162..af2562c710 100644 --- a/source/java/org/alfresco/repo/domain/ChildAssoc.java +++ b/source/java/org/alfresco/repo/domain/ChildAssoc.java @@ -59,6 +59,11 @@ public interface ChildAssoc extends Comparable public Long getId(); + /** + * @return Return the current version number + */ + public Long getVersion(); + public Node getParent(); public Node getChild(); diff --git a/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java b/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java index 4c597d2e8b..9b362542e6 100644 --- a/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java +++ b/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java @@ -37,7 +37,12 @@ public interface DbAccessControlEntry /** * @return Returns the identifier for this object */ - public long getId(); + public Long getId(); + + /** + * @return Returns the version number for optimistic locking + */ + public Long getVersion(); /** * @return Returns the containing access control list diff --git a/source/java/org/alfresco/repo/domain/DbAccessControlList.java b/source/java/org/alfresco/repo/domain/DbAccessControlList.java index 7da765a627..ae25a1b476 100644 --- a/source/java/org/alfresco/repo/domain/DbAccessControlList.java +++ b/source/java/org/alfresco/repo/domain/DbAccessControlList.java @@ -36,8 +36,13 @@ import org.alfresco.repo.domain.hibernate.DbAccessControlEntryImpl; */ public interface DbAccessControlList { - public long getId(); + public Long getId(); + /** + * @return Returns the version number for optimistic locking + */ + public Long getVersion(); + /** * * @return Returns the access control entries for this access control list diff --git a/source/java/org/alfresco/repo/domain/DbAuthority.java b/source/java/org/alfresco/repo/domain/DbAuthority.java index f896898588..fb56aac80d 100644 --- a/source/java/org/alfresco/repo/domain/DbAuthority.java +++ b/source/java/org/alfresco/repo/domain/DbAuthority.java @@ -33,6 +33,11 @@ import java.util.Set; */ public interface DbAuthority extends Serializable { + /** + * @return Returns the version number for optimistic locking + */ + public Long getVersion(); + /** * @return Returns the recipient */ diff --git a/source/java/org/alfresco/repo/domain/DbPermission.java b/source/java/org/alfresco/repo/domain/DbPermission.java index 37ced150e9..374857541d 100644 --- a/source/java/org/alfresco/repo/domain/DbPermission.java +++ b/source/java/org/alfresco/repo/domain/DbPermission.java @@ -38,7 +38,12 @@ public interface DbPermission extends Serializable /** * @return Returns the automatically assigned ID */ - public long getId(); + public Long getId(); + + /** + * @return Returns the version number for optimistic locking + */ + public Long getVersion(); /** * @return Returns the qualified name of this permission diff --git a/source/java/org/alfresco/repo/domain/Node.java b/source/java/org/alfresco/repo/domain/Node.java index 824c0e8744..5847c3fb47 100644 --- a/source/java/org/alfresco/repo/domain/Node.java +++ b/source/java/org/alfresco/repo/domain/Node.java @@ -52,6 +52,11 @@ public interface Node */ public Long getId(); + /** + * @return Returns the current version number + */ + public Long getVersion(); + public Store getStore(); public void setStore(Store store); diff --git a/source/java/org/alfresco/repo/domain/NodeAssoc.java b/source/java/org/alfresco/repo/domain/NodeAssoc.java index 82f8aaebe3..69270a1ebd 100644 --- a/source/java/org/alfresco/repo/domain/NodeAssoc.java +++ b/source/java/org/alfresco/repo/domain/NodeAssoc.java @@ -35,8 +35,6 @@ import org.alfresco.service.namespace.QName; */ public interface NodeAssoc { - public long getId(); - /** * Wires up the necessary bits on the source and target nodes so that the association * is immediately bidirectional. @@ -52,6 +50,13 @@ public interface NodeAssoc public AssociationRef getNodeAssocRef(); + public Long getId(); + + /** + * @return Returns the current version number + */ + public Long getVersion(); + public Node getSource(); public Node getTarget(); diff --git a/source/java/org/alfresco/repo/domain/NodeStatus.java b/source/java/org/alfresco/repo/domain/NodeStatus.java index 8a5c97e86a..1a58036ce7 100644 --- a/source/java/org/alfresco/repo/domain/NodeStatus.java +++ b/source/java/org/alfresco/repo/domain/NodeStatus.java @@ -45,6 +45,11 @@ public interface NodeStatus */ public void setKey(NodeKey key); + /** + * @return Returns the current version number + */ + public Long getVersion(); + public Node getNode(); public void setNode(Node node); diff --git a/source/java/org/alfresco/repo/domain/Server.java b/source/java/org/alfresco/repo/domain/Server.java index d86ef0844e..cf2a317ddf 100644 --- a/source/java/org/alfresco/repo/domain/Server.java +++ b/source/java/org/alfresco/repo/domain/Server.java @@ -35,6 +35,8 @@ public interface Server { public Long getId(); + public Long getVersion(); + public String getIpAddress(); public void setIpAddress(String ipAddress); diff --git a/source/java/org/alfresco/repo/domain/Store.java b/source/java/org/alfresco/repo/domain/Store.java index 79dabcff37..c05c7aa91d 100644 --- a/source/java/org/alfresco/repo/domain/Store.java +++ b/source/java/org/alfresco/repo/domain/Store.java @@ -38,6 +38,11 @@ public interface Store * @return Returns the key for the class */ public StoreKey getKey(); + + /** + * @return Returns the current version number used for optimistic locking + */ + public Long getVersion(); /** * @param key the key uniquely identifying this store diff --git a/source/java/org/alfresco/repo/domain/Transaction.java b/source/java/org/alfresco/repo/domain/Transaction.java index 07eb504391..ca1b045fa7 100644 --- a/source/java/org/alfresco/repo/domain/Transaction.java +++ b/source/java/org/alfresco/repo/domain/Transaction.java @@ -33,6 +33,8 @@ public interface Transaction { public Long getId(); + public Long getVersion(); + public String getChangeTxnId(); public void setChangeTxnId(String changeTxnId); diff --git a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java index ec72647bba..6d2f863aaa 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java @@ -33,6 +33,7 @@ import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; /** * @author Derek Hulley @@ -42,6 +43,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable private static final long serialVersionUID = -8993272236626580410L; private Long id; + private Long version; private Node parent; private Node child; private QName typeQName; @@ -127,6 +129,32 @@ public class ChildAssocImpl implements ChildAssoc, Serializable } } + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (obj == this) + { + return true; + } + else if (!(obj instanceof ChildAssoc)) + { + return false; + } + ChildAssoc that = (ChildAssoc) obj; + return (EqualsHelper.nullSafeEquals(this.getTypeQName(), that.getTypeQName()) + && EqualsHelper.nullSafeEquals(this.getQname(), that.getQname()) + && EqualsHelper.nullSafeEquals(this.getChild(), that.getChild()) + && EqualsHelper.nullSafeEquals(this.getParent(), that.getParent())); + } + + public int hashCode() + { + return (child == null ? 0 : child.hashCode()); + } + public String toString() { StringBuffer sb = new StringBuffer(32); @@ -192,6 +220,20 @@ public class ChildAssocImpl implements ChildAssoc, Serializable this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public Node getParent() { return parent; diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java index 991bb83856..22df86c9d9 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java @@ -42,18 +42,14 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter { private static final long serialVersionUID = -418837862334064582L; - /** The object id */ - private long id; - + private Long id; + private Long version; /** The container of these entries */ private DbAccessControlList accessControlList; - /** The permission to which this applies (non null - all is a special string) */ private DbPermission permission; - /** The recipient to which this applies (non null - all is a special string) */ private DbAuthority authority; - /** Is this permission allowed? */ private boolean allowed; @@ -113,7 +109,7 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter return hashCode; } - public long getId() + public Long getId() { return id; } @@ -121,11 +117,26 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter /** * For Hibernate use */ - /* package */ void setId(long id) + @SuppressWarnings("unused") + private void setId(Long id) { this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public DbAccessControlList getAccessControlList() { return accessControlList; diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java index 2fae4bc2db..f220544ab2 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java @@ -51,7 +51,8 @@ public class DbAccessControlListImpl extends LifecycleAdapter private static Log logger = LogFactory.getLog(DbAccessControlListImpl.class); - private long id; + private Long id; + private Long version; private Set entries; private boolean inherits; @@ -94,7 +95,7 @@ public class DbAccessControlListImpl extends LifecycleAdapter return (inherits == false ? 0 : 17); } - public long getId() + public Long getId() { return id; } @@ -103,11 +104,25 @@ public class DbAccessControlListImpl extends LifecycleAdapter * Hibernate use */ @SuppressWarnings("unused") - private void setId(long id) + private void setId(Long id) { this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public Set getEntries() { return entries; diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java index 0aad091c1b..2ee1e627c9 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java @@ -47,6 +47,7 @@ public class DbAuthorityImpl extends LifecycleAdapter private static Log logger = LogFactory.getLog(DbAuthorityImpl.class); + private Long version; private String recipient; private Set externalKeys; @@ -105,6 +106,20 @@ public class DbAuthorityImpl extends LifecycleAdapter return super.onDelete(session); } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public String getRecipient() { return recipient; diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java index 8b74eac3b3..7abbfb859b 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java @@ -48,7 +48,8 @@ public class DbPermissionImpl extends LifecycleAdapter private static Log logger = LogFactory.getLog(DbPermissionImpl.class); - private long id; + private Long id; + private Long version; private QName typeQname; private String name; @@ -120,7 +121,7 @@ public class DbPermissionImpl extends LifecycleAdapter return super.onDelete(session); } - public long getId() + public Long getId() { return id; } @@ -129,11 +130,25 @@ public class DbPermissionImpl extends LifecycleAdapter * For Hibernate use */ @SuppressWarnings("unused") - private void setId(long id) + private void setId(Long id) { this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public QName getTypeQname() { return typeQname; diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java index e0e98178cd..e407f49cac 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateNodeTest.java @@ -49,6 +49,7 @@ 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.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.BaseSpringTest; @@ -178,6 +179,8 @@ public class HibernateNodeTest extends BaseSpringTest // Sybase // expected } + // Just clear out any pending changes + getSession().clear(); } /** @@ -584,4 +587,58 @@ public class HibernateNodeTest extends BaseSpringTest getSession().flush(); getSession().clear(); } + + public void testDeletesAndFlush() throws Exception + { + // Create parent node + Node parentNode = new NodeImpl(); + parentNode.setStore(store); + parentNode.setUuid(GUID.generate()); + parentNode.setTypeQName(ContentModel.TYPE_CONTAINER); + Long nodeIdOne = (Long) getSession().save(parentNode); + // Create child node + Node childNode = new NodeImpl(); + childNode.setStore(store); + childNode.setUuid(GUID.generate()); + childNode.setTypeQName(ContentModel.TYPE_CONTENT); + Long nodeIdTwo = (Long) getSession().save(childNode); + // Get them into the database + getSession().flush(); + + // Now create a loads of associations + int assocCount = 1000; + List assocIds = new ArrayList(assocCount); + for (int i = 0; i < assocCount; i++) + { + ChildAssoc assoc = new ChildAssocImpl(); + assoc.buildAssociation(parentNode, childNode); + assoc.setIsPrimary(false); + assoc.setTypeQName(QName.createQName(null, "TYPE")); + assoc.setQname(QName.createQName(null, "" + System.nanoTime())); + assoc.setChildNodeName(GUID.generate()); // It must be unique + assoc.setChildNodeNameCrc(-1L); + Long assocId = (Long) getSession().save(assoc); + assocIds.add(assocId); + } + // Flush and clear the lot + getSession().flush(); + getSession().clear(); + + // Now we delete the entities, flushing and clearing every 100 deletes + int count = 0; + for (Long assocId : assocIds) + { + // Load the entity + ChildAssoc assoc = (ChildAssoc) getSession().get(ChildAssocImpl.class, assocId); + assertNotNull("Entity should exist", assoc); + getSession().delete(assoc); + // Do we flush and clear + if (count % 100 == 0) + { + getSession().flush(); + getSession().clear(); + } + count++; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 5b5046e3cb..bdfa0a7a34 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -38,6 +38,9 @@ + + + + + + + @@ -165,7 +172,7 @@ lazy="proxy" fetch="select" class="org.alfresco.repo.domain.hibernate.NodeImpl" - optimistic-lock="true" + optimistic-lock="false" not-null="true" > @@ -190,6 +197,7 @@ @@ -199,6 +207,7 @@ @@ -206,6 +215,8 @@ + + @@ -272,6 +283,17 @@ assoc.id + + select + assoc + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + where + assoc.parent.id = :parentId and + assoc.childNodeName = :childNodeName and + assoc.childNodeNameCrc = :childNodeNameCrc + + select assoc diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java index b7f08f1995..a66387d384 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeAssocImpl.java @@ -44,7 +44,8 @@ public class NodeAssocImpl implements NodeAssoc, Serializable { private static final long serialVersionUID = 864534636913524867L; - private long id; + private Long id; + private Long version; private Node source; private Node target; private QName typeQName; @@ -149,7 +150,7 @@ public class NodeAssocImpl implements NodeAssoc, Serializable return (typeQName == null ? 0 : typeQName.hashCode()); } - public long getId() + public Long getId() { return id; } @@ -163,6 +164,20 @@ public class NodeAssocImpl implements NodeAssoc, Serializable this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public Node getSource() { return source; diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java index 3afc8b632d..f08a39c13e 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java @@ -56,6 +56,7 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable private static final long serialVersionUID = -2101330674810283053L; private Long id; + private Long version; private Store store; private String uuid; private QName typeQName; @@ -179,6 +180,20 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public Store getStore() { return store; diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java index 24993599c3..e4319f22e8 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java @@ -42,6 +42,7 @@ public class NodeStatusImpl implements NodeStatus, Serializable private static final long serialVersionUID = -802747893314715639L; private NodeKey key; + private Long version; private Node node; private Transaction transaction; @@ -85,6 +86,20 @@ public class NodeStatusImpl implements NodeStatus, Serializable this.key = key; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public Node getNode() { return node; diff --git a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml index 87d9900356..46cc77331f 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml @@ -19,6 +19,8 @@ + + + + @@ -97,6 +101,8 @@ + + + + false, the global setting will take effect. + * + * @see #setDisableInTransaction() + */ + public static boolean isDisableInTransaction() + { + Boolean disableInTransaction = (Boolean) AlfrescoTransactionSupport.getResource(KEY_DISABLE_IN_TRANSACTION); + if (disableInTransaction == null || disableInTransaction == Boolean.FALSE) + { + return false; + } + else + { + return true; + } + } + + /** + * Default public constructor required for bean instantiation. + */ + public SessionSizeResourceManager() + { + this.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 @@ -74,6 +114,12 @@ public class SessionSizeResourceManager extends HibernateDaoSupport implements M long transactionElapsedTimeNs, Method currentMethod) { + if (isDisableInTransaction()) + { + // Don't do anything + return; + } + // We are go for interfering Session session = getSession(false); SessionStatistics stats = session.getStatistics(); int entityCount = stats.getEntityCount(); diff --git a/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml index 028e260a7b..3904139249 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml @@ -19,6 +19,8 @@ + + + + + + diff --git a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java index c2214478b6..e321cfec29 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java @@ -42,6 +42,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se private static final long serialVersionUID = -8264339795578077552L; private Long id; + private Long version; private String changeTxnId; private Server server; @@ -74,6 +75,20 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se this.id = id; } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + public String getChangeTxnId() { return changeTxnId; diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java index 97c5597216..0a6785ff16 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java @@ -35,8 +35,8 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.TransactionUtil; -import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; @@ -47,7 +47,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; @@ -71,7 +70,7 @@ public class FileFolderPerformanceTester extends TestCase private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - private TransactionService transactionService; + private RetryingTransactionHelper retryingTransactionHelper; private AuthenticationComponent authenticationComponent; private NodeService nodeService; private FileFolderService fileFolderService; @@ -82,7 +81,7 @@ public class FileFolderPerformanceTester extends TestCase public void setUp() throws Exception { ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - transactionService = serviceRegistry.getTransactionService(); + retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper"); authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); nodeService = serviceRegistry.getNodeService(); fileFolderService = serviceRegistry.getFileFolderService(); @@ -138,9 +137,9 @@ public class FileFolderPerformanceTester extends TestCase final int fileCount, final double[] dumpPoints) { - TransactionWork createFoldersWork = new TransactionWork() + RetryingTransactionCallback createFoldersCallback = new RetryingTransactionCallback() { - public NodeRef[] doWork() throws Exception + public NodeRef[] execute() throws Exception { NodeRef[] folders = new NodeRef[folderCount]; for (int i = 0; i < folderCount; i++) @@ -155,9 +154,7 @@ public class FileFolderPerformanceTester extends TestCase return folders; } }; - final NodeRef[] folders = TransactionUtil.executeInUserTransaction( - transactionService, - createFoldersWork); + final NodeRef[] folders = retryingTransactionHelper.doInTransaction(createFoldersCallback); // the worker that will load the files into the folders Runnable runnable = new Runnable() { @@ -192,9 +189,9 @@ public class FileFolderPerformanceTester extends TestCase for (int j = 0; j < folders.length; j++) { final NodeRef folderRef = folders[j]; - TransactionWork createFileWork = new TransactionWork() + RetryingTransactionCallback createFileCallback = new RetryingTransactionCallback() { - public FileInfo doWork() throws Exception + public FileInfo execute() throws Exception { FileInfo fileInfo = fileFolderService.create( folderRef, @@ -208,7 +205,7 @@ public class FileFolderPerformanceTester extends TestCase return fileInfo; } }; - TransactionUtil.executeInUserTransaction(transactionService, createFileWork); + retryingTransactionHelper.doInTransaction(createFileCallback); } } dumpResults(fileCount); @@ -257,6 +254,7 @@ public class FileFolderPerformanceTester extends TestCase } } + @SuppressWarnings("unused") private void readStructure( final NodeRef parentNodeRef, final int threadCount, @@ -277,9 +275,9 @@ public class FileFolderPerformanceTester extends TestCase for (ChildAssociationRef childAssociationRef : children) { final NodeRef folderRef = childAssociationRef.getChildRef(); - TransactionWork readWork = new TransactionWork() + RetryingTransactionCallback readCallback = new RetryingTransactionCallback() { - public Object doWork() throws Exception + public Object execute() throws Exception { // read the child associations of the folder nodeService.getChildAssocs(folderRef); @@ -289,7 +287,7 @@ public class FileFolderPerformanceTester extends TestCase return null; }; }; - TransactionUtil.executeInUserTransaction(transactionService, readWork, true); + retryingTransactionHelper.doInTransaction(readCallback, true); } } } @@ -337,16 +335,16 @@ public class FileFolderPerformanceTester extends TestCase // { // buildStructure(rootFolderRef, 4, true, 10, 100, new double[] {0.25, 0.50, 0.75}); // } - public void test_1_ordered_100_100() throws Exception - { - buildStructure( - rootFolderRef, - 1, - false, - 100, - 100, - new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); - } +// public void test_1_ordered_100_100() throws Exception +// { +// buildStructure( +// rootFolderRef, +// 1, +// false, +// 100, +// 100, +// new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); +// } // public void test_1_shuffled_10_400() throws Exception // { // buildStructure( @@ -357,16 +355,16 @@ public class FileFolderPerformanceTester extends TestCase // 400, // new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); // } -// public void test_4_shuffled_10_100() throws Exception -// { -// buildStructure( -// rootFolderRef, -// 4, -// true, -// 10, -// 100, -// new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); -// } + public void test_4_shuffled_10_100() throws Exception + { + buildStructure( + rootFolderRef, + 4, + true, + 10, + 100, + new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + } // public void test_1_ordered_1_50000() throws Exception // { // buildStructure( diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index a1623ce9b1..78ee8d01be 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -51,6 +51,7 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -140,6 +141,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest protected PolicyComponent policyComponent; protected DictionaryService dictionaryService; protected TransactionService transactionService; + protected RetryingTransactionHelper retryingTransactionHelper; protected AuthenticationComponent authenticationComponent; protected NodeDaoService nodeDaoService; protected NodeService nodeService; @@ -151,6 +153,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest { super.onSetUpInTransaction(); transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); + retryingTransactionHelper = (RetryingTransactionHelper) applicationContext.getBean("retryingTransactionHelper"); policyComponent = (PolicyComponent) applicationContext.getBean("policyComponent"); authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); diff --git a/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java b/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java index 2e5343bf76..d0c01503ec 100644 --- a/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java @@ -27,15 +27,14 @@ package org.alfresco.repo.node; import java.io.InputStream; import java.util.Map; -import javax.transaction.UserTransaction; - import junit.framework.TestCase; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -78,6 +77,7 @@ public class ConcurrentNodeServiceTest extends TestCase private NodeService nodeService; private TransactionService transactionService; + private RetryingTransactionHelper retryingTransactionHelper; private NodeRef rootNodeRef; @@ -107,23 +107,24 @@ public class ConcurrentNodeServiceTest extends TestCase nodeService = (NodeService) ctx.getBean("dbNodeService"); transactionService = (TransactionService) ctx.getBean("transactionComponent"); + retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper"); luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer"); this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); this.authenticationComponent.setSystemUserAsCurrentUser(); // create a first store directly - TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + RetryingTransactionCallback createRootNodeCallback = new RetryingTransactionCallback() { - - public Object doWork() throws Exception + public Object execute() throws Exception { StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); return null; } - }); + }; + retryingTransactionHelper.doInTransaction(createRootNodeCallback); } @Override @@ -140,17 +141,17 @@ public class ConcurrentNodeServiceTest extends TestCase protected Map commitNodeGraph() throws Exception { - return TransactionUtil.executeInUserTransaction(transactionService, - new TransactionUtil.TransactionWork>() - { + RetryingTransactionCallback> buildGraphCallback = + new RetryingTransactionCallback>() + { + public Map execute() throws Exception + { - public Map doWork() throws Exception - { - - Map answer = buildNodeGraph(); - return answer; - } - }); + Map answer = buildNodeGraph(); + return answer; + } + }; + return retryingTransactionHelper.doInTransaction(buildGraphCallback); } public void xtest1() throws Exception @@ -231,10 +232,10 @@ public class ConcurrentNodeServiceTest extends TestCase } } - TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + // Test it + RetryingTransactionCallback testCallback = new RetryingTransactionCallback() { - - public Object doWork() throws Exception + public Object execute() throws Exception { // There are two nodes at the base level in each test assertEquals(2 * ((COUNT * REPEATS) + 1), nodeService.getChildAssocs(rootNodeRef).size()); @@ -276,9 +277,8 @@ public class ConcurrentNodeServiceTest extends TestCase return null; } - - }); - + }; + retryingTransactionHelper.doInTransaction(testCallback); } /** diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index a59b4274f1..d373d4b3a3 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -40,8 +40,7 @@ import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionUtil; -import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -143,9 +142,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest endTransaction(); // change property - check status - TransactionWork changePropertiesWork = new TransactionWork() + RetryingTransactionCallback changePropertiesWork = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { nodeService.setProperty(n6Ref, ContentModel.PROP_CREATED, new Date()); return null; @@ -154,9 +153,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest executeAndCheck(n6Ref, changePropertiesWork); // add an aspect - TransactionWork addAspectWork = new TransactionWork() + RetryingTransactionCallback addAspectWork = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { nodeService.addAspect(n6Ref, ASPECT_QNAME_TEST_MARKER, null); return null; @@ -165,9 +164,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest executeAndCheck(n6Ref, addAspectWork); // remove an aspect - TransactionWork removeAspectWork = new TransactionWork() + RetryingTransactionCallback removeAspectWork = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { nodeService.removeAspect(n6Ref, ASPECT_QNAME_TEST_MARKER); return null; @@ -176,9 +175,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest executeAndCheck(n6Ref, removeAspectWork); // move the node - TransactionWork moveNodeWork = new TransactionWork() + RetryingTransactionCallback moveNodeWork = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { nodeService.moveNode( n6Ref, @@ -191,9 +190,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest executeAndCheck(n6Ref, moveNodeWork); // delete the node - TransactionWork deleteNodeWork = new TransactionWork() + RetryingTransactionCallback deleteNodeWork = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { nodeService.deleteNode(n6Ref); return null; @@ -202,9 +201,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest executeAndCheck(n6Ref, deleteNodeWork); // check cascade-deleted nodes - TransactionWork checkCascadeWork = new TransactionWork() + RetryingTransactionCallback checkCascadeCallback = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { // check n6 NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false); @@ -221,12 +220,12 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest return null; } }; - TransactionUtil.executeInUserTransaction(txnService, checkCascadeWork); + retryingTransactionHelper.doInTransaction(checkCascadeCallback); // check node recreation - TransactionWork checkRecreateWork = new TransactionWork() + RetryingTransactionCallback checkRecreateCallback = new RetryingTransactionCallback() { - public Object doWork() + public Object execute() { properties.put(ContentModel.PROP_STORE_PROTOCOL, n6Ref.getStoreRef().getProtocol()); properties.put(ContentModel.PROP_STORE_IDENTIFIER, n6Ref.getStoreRef().getIdentifier()); @@ -242,10 +241,10 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest return null; } }; - TransactionUtil.executeInUserTransaction(txnService, checkRecreateWork); + retryingTransactionHelper.doInTransaction(checkRecreateCallback); } - private void executeAndCheck(NodeRef nodeRef, TransactionWork work) throws Throwable + private void executeAndCheck(NodeRef nodeRef, RetryingTransactionCallback callback) throws Throwable { UserTransaction txn = txnService.getUserTransaction(); txn.begin(); @@ -257,7 +256,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest assertNotSame(currentTxnId, currentStatus.getChangeTxnId()); try { - work.doWork(); + callback.execute(); // get the status NodeRef.Status newStatus = nodeService.getNodeStatus(nodeRef); assertNotNull(newStatus); @@ -362,4 +361,20 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest // Get it again nodeService.getPrimaryParent(n8Ref); } + + /** + * It would appear that an issue has arisen with creating and deleting nodes + * in the same transaction. + */ + public void testInTransactionCreateAndDelete() throws Exception + { + // Create a node + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(NAMESPACE, this.getName()), + TYPE_QNAME_TEST_CONTENT).getChildRef(); + // Delete the node + nodeService.deleteNode(nodeRef); + } } diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index f0d9e052fe..f806c421d2 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.Set; import java.util.zip.CRC32; @@ -72,13 +73,17 @@ import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.FlushMode; +import org.hibernate.LockMode; import org.hibernate.ObjectDeletedException; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.Session; +import org.hibernate.exception.LockAcquisitionException; +import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DeadlockLoserDataAccessException; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; @@ -94,6 +99,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_GET_PRIMARY_CHILD_NODE_STATUSES = "node.GetPrimaryChildNodeStatuses"; private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; private static final String QUERY_GET_CHILD_ASSOCS_BY_ALL = "node.GetChildAssocsByAll"; + private static final String QUERY_GET_CHILD_ASSOC_BY_NAME = "node.GetChildAssocByShortName"; private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME = "node.GetChildAssocRefsByQName"; @@ -105,12 +111,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress"; private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); + private static Log loggerChildAssoc = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ChildAssoc"); /** a uuid identifying this unique instance */ private final String uuid; + /** the number of lock retries against the parent node to ensure child uniqueness */ + private int maxLockRetries; private static TransactionAwareSingleton serverIdSingleton = new TransactionAwareSingleton(); private final String ipAddress; + private Random randomWaitTime; /** used for debugging */ private Set changeTxnIdSet; @@ -121,6 +131,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements public HibernateNodeDaoServiceImpl() { this.uuid = GUID.generate(); + this.maxLockRetries = 20; try { ipAddress = InetAddress.getLocalHost().getHostAddress(); @@ -129,6 +140,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { throw new AlfrescoRuntimeException("Failed to get server IP address", e); } + randomWaitTime = new Random(System.currentTimeMillis()); changeTxnIdSet = new HashSet(0); } @@ -158,6 +170,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return uuid.hashCode(); } + /** + * Set the maximum number of retries when attempting to get a lock on a parent node + * + * @param maxLockRetries the retry count + */ + public void setMaxLockRetries(int maxLockRetries) + { + this.maxLockRetries = maxLockRetries; + } + /** * Gets/creates the server instance to use for the life of this instance */ @@ -515,8 +537,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); // finally delete the node getHibernateTemplate().delete(node); - // flush to ensure constraints can't be violated - getSession().flush(); +// // flush to ensure constraints can't be violated +// getSession().flush(); // done } @@ -584,13 +606,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements public void setChildNameUnique(final ChildAssoc childAssoc, String childName) { /* - * As the Hibernate session is rendered useless when an exception is - * bubbled up, we go direct to the database to update the child association. - * This preserves the session and client code can catch the resulting - * exception and react to it whilst in the same transaction. - * - * We ensure that case-insensitivity is maintained by persisting - * the lowercase version of the child node name. + * Work out if there has been any change in the name */ String childNameNew = null; @@ -621,40 +637,75 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } + /* + * The parent node is explicitly locked. A query is then issued to ensure that there + * are no duplicates in the index on the child assoc table. The child association is + * then modified, although not directly in the database. The lock guards against other + * transactions modifying the unique index without this transaction's knowledge. + */ + final Node parentNode = childAssoc.getParent(); +// if (loggerChildAssoc.isDebugEnabled()) +// { +// loggerChildAssoc.debug( +// "Locking parent node for modifying child assoc: \n" + +// " Parent: " + parentNode + "\n" + +// " Child Assoc: " + childAssoc + "\n" + +// " New Name: " + childNameNew); +// } +// for (int i = 0; i < maxLockRetries; i++) +// { +// try +// { +// getSession().lock(parentNode, LockMode.UPGRADE); +// // The lock was good, proceed +// break; +// } +// catch (LockAcquisitionException e) {} +// catch (ConcurrencyFailureException e) {} +// // We can retry after a short pause that gets potentially longer each time +// try { Thread.sleep(randomWaitTime.nextInt(500 * i + 500)); } catch (InterruptedException ee) {} +// } + // We have the lock, so issue the query to check HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - session.flush(); Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME) - .setString("newName", childNameNewShort) - .setLong("newNameCrc", childNameNewCrc) - .setLong("childAssocId", childAssoc.getId()); - return (Integer) query.executeUpdate(); + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_NAME) + .setLong("parentId", parentNode.getId()) + .setParameter("childNodeName", childNameNewShort) + .setLong("childNodeNameCrc", childNameNewCrc); + return query.uniqueResult(); } }; - try + ChildAssoc childAssocExisting = (ChildAssoc) getHibernateTemplate().execute(callback); + if (childAssocExisting != null) { - Integer count = (Integer) getHibernateTemplate().execute(callback); - // refresh the entity directly - if (count.intValue() == 0) + // There is already an entity + if (loggerChildAssoc.isDebugEnabled()) { - if (logger.isDebugEnabled()) - { - logger.debug("ChildAssoc not updated: " + childAssoc.getId()); - } - } - else - { - getHibernateTemplate().refresh(childAssoc); + loggerChildAssoc.debug( + "Duplicate child association detected: \n" + + " Child Assoc: " + childAssoc + "\n" + + " Existing Child Assoc: " + childName); } + throw new DuplicateChildNodeNameException( + parentNode.getNodeRef(), + childAssoc.getTypeQName(), + childName); } - catch (DataIntegrityViolationException e) + // We got past that, so we can just update the entity and know that no other transaction + // can lock the parent. + childAssoc.setChildNodeName(childNameNewShort); + childAssoc.setChildNodeNameCrc(childNameNewCrc); + + // Done + if (loggerChildAssoc.isDebugEnabled()) { - NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); - QName assocTypeQName = childAssoc.getTypeQName(); - throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName); + loggerChildAssoc.debug( + "Updated child association: \n" + + " Parent: " + parentNode + "\n" + + " Child Assoc: " + childAssoc); } } @@ -868,9 +919,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return; } - if (logger.isDebugEnabled()) + if (loggerChildAssoc.isDebugEnabled()) { - logger.debug( + loggerChildAssoc.debug( "Deleting parent-child association " + assoc.getId() + (cascade ? " with" : " without") + " cascade:" + assoc.getParent().getId() + " -> " + assoc.getChild().getId()); @@ -894,10 +945,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements * duplicate call will be received to do this */ } - - // To ensure the validity of the constraint enforcement by the database, - // we have to flush here - getSession().flush(); +// +// // To ensure the validity of the constraint enforcement by the database, +// // we have to flush here. It is possible to delete and recreate the instance +// getSession().flush(); } /** @@ -1117,9 +1168,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { // Remove instance getHibernateTemplate().delete(assoc); - // Flush to ensure that the database constraints aren't violated if the assoc - // is recreated in the transaction - getSession().flush(); +// // Flush to ensure that the database constraints aren't violated if the assoc +// // is recreated in the transaction +// getSession().flush(); } public List getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition) diff --git a/source/java/org/alfresco/repo/node/db/hibernate/SessionSizeManagementTest.java b/source/java/org/alfresco/repo/node/db/hibernate/SessionSizeManagementTest.java index 7f69a81292..8fe2f1bbd5 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/SessionSizeManagementTest.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/SessionSizeManagementTest.java @@ -136,9 +136,6 @@ public class SessionSizeManagementTest extends BaseNodeServiceTest } createNodes(nodeService, LOAD_COUNT, true); - // Check the session size - int entityCount = getSession().getStatistics().getEntityCount(); - assertTrue("Manual flush: Entity count should be less than " + LOAD_COUNT, entityCount < LOAD_COUNT); // Now flush integrity to be sure things are not broken AlfrescoTransactionSupport.flush(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index afab524a05..a4a8dc9455 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -50,6 +50,7 @@ import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.DictionaryNamespaceComponent; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.NamespaceDAOImpl; +import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.search.MLAnalysisMode; import org.alfresco.repo.search.QueryParameterDefImpl; @@ -59,6 +60,8 @@ import org.alfresco.repo.search.results.ChildAssocRefResultSet; import org.alfresco.repo.search.results.DetachedResultSet; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -67,7 +70,6 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MLText; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; @@ -120,6 +122,7 @@ public class ADMLuceneTest extends TestCase QName aspectWithChildren = QName.createQName(TEST_NAMESPACE, "aspectWithChildren"); TransactionService transactionService; + RetryingTransactionHelper retryingTransactionHelper; NodeService nodeService; @@ -205,6 +208,8 @@ public class ADMLuceneTest extends TestCase indexerAndSearcher = (LuceneIndexerAndSearcher) ctx.getBean("admLuceneIndexerAndSearcherFactory"); ((AbstractLuceneIndexerAndSearcherFactory)indexerAndSearcher).setMaxAtomicTransformationTime(1000000); transactionService = (TransactionService) ctx.getBean("transactionComponent"); + retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper"); + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); namespaceDao = (NamespaceDAOImpl) ctx.getBean("namespaceDAO"); @@ -1030,25 +1035,31 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); results.close(); - UserTransaction tx1 = transactionService.getUserTransaction(); - tx1.begin(); - for (int i = 0; i < 100; i++) + RetryingTransactionCallback createAndDeleteCallback = new RetryingTransactionCallback() { - HashSet refs = new HashSet(); - for (int j = 0; j < i; j++) + public Object execute() throws Throwable { - ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName - .createQName("{namespace}test"), testSuperType); - refs.add(test); - } + // Disable resource management + SessionSizeResourceManager.setDisableInTransaction(); + for (int i = 0; i < 100; i++) + { + HashSet refs = new HashSet(); + for (int j = 0; j < i; j++) + { + ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName + .createQName("{namespace}test"), testSuperType); + refs.add(test); + } - for (ChildAssociationRef car : refs) - { - nodeService.deleteNode(car.getChildRef()); + for (ChildAssociationRef car : refs) + { + nodeService.deleteNode(car.getChildRef()); + } + } + return null; } - - } - tx1.commit(); + }; + retryingTransactionHelper.doInTransaction(createAndDeleteCallback); UserTransaction tx3 = transactionService.getUserTransaction(); tx3.begin(); @@ -1134,29 +1145,34 @@ public class ADMLuceneTest extends TestCase try { System.out.println("Start " + this.getName()); - UserTransaction tx1 = transactionService.getUserTransaction(); - tx1.begin(); - for (int i = 0; i < 20; i++) + RetryingTransactionCallback createAndDeleteCallback = new RetryingTransactionCallback() { - HashSet refs = new HashSet(); - for (int j = 0; j < i; j++) + public Object execute() throws Throwable { - ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}test_" + getName() + "_" + i + "_" +j), testSuperType); - refs.add(test); - } + for (int i = 0; i < 20; i++) + { + HashSet refs = new HashSet(); + for (int j = 0; j < i; j++) + { + ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}test_" + getName() + "_" + i + "_" +j), testSuperType); + refs.add(test); + } - for (ChildAssociationRef car : refs) - { - nodeService.deleteNode(car.getChildRef()); + for (ChildAssociationRef car : refs) + { + nodeService.deleteNode(car.getChildRef()); + } + } + return null; } - - } - tx1.commit(); + }; + retryingTransactionHelper.doInTransaction(createAndDeleteCallback); System.out.println("End " + this.getName()); } catch (Exception e) { + System.out.println("End " + this.getName() + " with error " + e.getMessage()); e.printStackTrace(); } finally diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index 271b065f57..f8c2df7ec1 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -1,5 +1,26 @@ -/** - * +/* + * 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.transaction; @@ -10,6 +31,7 @@ import javax.transaction.SystemException; import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.error.ExceptionStackUtil; import org.alfresco.service.transaction.TransactionService; import org.apache.log4j.Logger; import org.hibernate.StaleObjectStateException; @@ -20,13 +42,28 @@ import org.springframework.dao.DeadlockLoserDataAccessException; /** * A helper that runs a unit of work inside a UserTransaction, * transparently retrying the unit of work if the cause of - * failure is an optimistic locking or deadlock condition. + * failure is an optimistic locking or deadlock condition. + * * @author britt */ public class RetryingTransactionHelper { private static Logger fgLogger = Logger.getLogger(RetryingTransactionHelper.class); + /** + * Exceptions that trigger retries. + */ + private static final Class[] RETRY_EXCEPTIONS; + static + { + RETRY_EXCEPTIONS = new Class[] { + ConcurrencyFailureException.class, + DeadlockLoserDataAccessException.class, + StaleObjectStateException.class, + LockAcquisitionException.class + }; + } + /** * Reference to the TransactionService instance. */ @@ -46,9 +83,15 @@ public class RetryingTransactionHelper * Callback interface * @author britt */ - public interface Callback + public interface RetryingTransactionCallback { - public Object execute(); + /** + * Perform a unit of transactional work. + * + * @return Return the result of the unit of work + * @throws Throwable This can be anything and will guarantee either a retry or a rollback + */ + public Result execute() throws Throwable; }; /** @@ -81,11 +124,53 @@ public class RetryingTransactionHelper * because of an error not the result of an optimistic locking failure, * or a deadlock loser failure, or until a maximum number of retries have * been attempted. - * @param cb The callback containing the unit of work. - * @param readOnly Whether this is a read only transaction. - * @return The result of the unit of work. + *

+ * If there is already an active transaction, then the callback is merely + * executed and any retry logic is left to the caller. The transaction + * will attempt to be read-write. + * + * @param cb The callback containing the unit of work. + * @return Returns the result of the unit of work. */ - public Object doInTransaction(Callback cb, boolean readOnly) + public R doInTransaction(RetryingTransactionCallback cb) + { + return doInTransaction(cb, false, false); + } + + /** + * Execute a callback in a transaction until it succeeds, fails + * because of an error not the result of an optimistic locking failure, + * or a deadlock loser failure, or until a maximum number of retries have + * been attempted. + *

+ * If there is already an active transaction, then the callback is merely + * executed and any retry logic is left to the caller. + * + * @param cb The callback containing the unit of work. + * @param readOnly Whether this is a read only transaction. + * @return Returns the result of the unit of work. + */ + public R doInTransaction(RetryingTransactionCallback cb, boolean readOnly) + { + return doInTransaction(cb, readOnly, false); + } + + /** + * Execute a callback in a transaction until it succeeds, fails + * because of an error not the result of an optimistic locking failure, + * or a deadlock loser failure, or until a maximum number of retries have + * been attempted. + *

+ * It is possible to force a new transaction to be created or to partake in + * any existing transaction. + * + * @param cb The callback containing the unit of work. + * @param readOnly Whether this is a read only transaction. + * @param newTransaction true to force a new transaction or + * false to partake in any existing transaction. + * @return Returns the result of the unit of work. + */ + public R doInTransaction(RetryingTransactionCallback cb, boolean readOnly, boolean newTransaction) { // Track the last exception caught, so that we // can throw it if we run out of retries. @@ -96,7 +181,14 @@ public class RetryingTransactionHelper boolean isNew = false; try { - txn = fTxnService.getUserTransaction(readOnly); + if (newTransaction) + { + txn = fTxnService.getNonPropagatingUserTransaction(); + } + else + { + txn = fTxnService.getUserTransaction(readOnly); + } // Do we need to handle transaction demarcation. If // no, we cannot do retries, that will be up to the containing // transaction. @@ -106,7 +198,7 @@ public class RetryingTransactionHelper txn.begin(); } // Do the work. - Object result = cb.execute(); + R result = cb.execute(); // Only commit if we 'own' the transaction. if (isNew) { @@ -160,45 +252,28 @@ public class RetryingTransactionHelper } lastException = (e instanceof RuntimeException) ? (RuntimeException)e : new AlfrescoRuntimeException("Unknown Exception in Transaction.", e); - Throwable t = e; - boolean shouldRetry = false; - while (t != null) + // Check if there is a cause for retrying + Throwable retryCause = ExceptionStackUtil.getCause(e, RETRY_EXCEPTIONS); + if (retryCause != null) { - // These are the 'OK' exceptions. These mean we can retry. - if (t instanceof ConcurrencyFailureException || - t instanceof DeadlockLoserDataAccessException || - t instanceof StaleObjectStateException || - t instanceof LockAcquisitionException) + // Sleep a random amount of time before retrying. + // The sleep interval increases with the number of retries. + try { - shouldRetry = true; - // Sleep a random amount of time before retrying. - // The sleep interval increases with the number of retries. - try - { - Thread.sleep(fRandom.nextInt(500 * count + 500)); - } - catch (InterruptedException ie) - { - // Do nothing. - } - break; + Thread.sleep(fRandom.nextInt(500 * count + 500)); } - if (t == t.getCause()) + catch (InterruptedException ie) { - break; + // Do nothing. } - t = t.getCause(); - } - if (shouldRetry) - { + // Try again continue; } - // It was a 'bad' exception. - if (e instanceof RuntimeException) + else { - throw (RuntimeException)e; + // It was a 'bad' exception. + throw lastException; } - throw new AlfrescoRuntimeException("Exception in Transaction.", e); } } // We've worn out our welcome and retried the maximum number of times. diff --git a/source/java/org/alfresco/repo/transaction/TransactionUtil.java b/source/java/org/alfresco/repo/transaction/TransactionUtil.java index f0c6f512a6..3bb5c22f7e 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionUtil.java +++ b/source/java/org/alfresco/repo/transaction/TransactionUtil.java @@ -37,7 +37,9 @@ import org.apache.commons.logging.LogFactory; /** * Class containing transactions helper methods and interfaces. * - * @author Roy Wetherall + * @deprecated Use a {@link RetryingTransactionHelper} instance + * + * @author Derek Hulley */ public class TransactionUtil { @@ -48,6 +50,9 @@ public class TransactionUtil *

* This interface encapsulates a unit of work that should be done within a * transaction. + * + * @deprecated + * @see RetryingTransactionHelper.RetryingTransactionCallback */ public interface TransactionWork { @@ -74,6 +79,8 @@ public class TransactionUtil * @param transactionWork the transaction work * * @throws java.lang.RuntimeException if the transaction was rolled back + * + * @deprecated Use a {@link RetryingTransactionHelper} instance */ public static R executeInUserTransaction( TransactionService transactionService, @@ -91,6 +98,8 @@ public class TransactionUtil * @param readOnly true if the transaction should be read-only * * @throws java.lang.RuntimeException if the transaction was rolled back + * + * @deprecated Use a {@link RetryingTransactionHelper} instance */ public static R executeInUserTransaction( TransactionService transactionService, @@ -108,6 +117,8 @@ public class TransactionUtil * @param transactionWork the transaction work * * @throws java.lang.RuntimeException if the transaction was rolled back + * + * @deprecated Use a {@link RetryingTransactionHelper} instance */ public static R executeInNonPropagatingUserTransaction( TransactionService transactionService, @@ -125,6 +136,8 @@ public class TransactionUtil * @param readOnly true if the transaction should be read-only * * @throws java.lang.RuntimeException if the transaction was rolled back + * + * @deprecated Use a {@link RetryingTransactionHelper} instance */ public static R executeInNonPropagatingUserTransaction( TransactionService transactionService,