You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ofbiz.apache.org by "Markus M. May (JIRA)" <ji...@apache.org> on 2012/09/03 17:30:08 UTC

[jira] [Comment Edited] (OFBIZ-3557) Enforced sequence does not work with concurrent access

    [ https://issues.apache.org/jira/browse/OFBIZ-3557?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13447316#comment-13447316 ] 

Markus M. May edited comment on OFBIZ-3557 at 9/4/12 2:29 AM:
--------------------------------------------------------------

We have now implemented the Service "invoiceSequenceEnforced" with a small Java-Method, so that the Transaction starts in the method and ends after committing the change on the DB. Seems to work now ;-)

{code:title=InvoiceServices.java|borderStyle=solid}
  // service to create a new invoiceId in a small transaction, so that the ids are updated correctly
  public static Map<String, Object> invoiceSequenceEnforced(DispatchContext dctx, Map<String, Object> context) throws GenericServiceException {
    Delegator delegator = dctx.getDelegator();
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Locale locale = (Locale) context.get("locale");

    String partyId = (String) context.get("partyId");
    Transaction parentTransaction = null;
    Long lastInvoiceNumber = null;

    try {
      // Enforce a new Transaction
      parentTransaction = TransactionUtil.suspend();
      if (TransactionUtil.isTransactionInPlace()) {
        throw new GenericTransactionException("In service " + module + " transaction is still in place after suspend, status is " + TransactionUtil.getStatusString());
      }

      // now start a new transaction
      boolean beganTrans = TransactionUtil.begin(0);

      GenericValue partyAcctgPreference = delegator.findOne("PartyAcctgPreference", false, UtilMisc.toMap("partyId", partyId));

      lastInvoiceNumber = partyAcctgPreference.getLong("lastInvoiceNumber");

      if (UtilValidate.isNotEmpty(lastInvoiceNumber)) {
        lastInvoiceNumber = lastInvoiceNumber + 1L;
      } else {
        lastInvoiceNumber = 1L;
      }

      Debug.logInfo("generated new InvoiceNumber: " + lastInvoiceNumber, module);
      partyAcctgPreference.set("lastInvoiceNumber", lastInvoiceNumber);
      partyAcctgPreference.store();

      TransactionUtil.commit();

    } catch (GenericEntityException e) {
      Debug.logError (e, "Entity/data problem creating invoiceId: " + e.toString(), module);
      return ServiceUtil.returnError(UtilProperties.getMessage(resource,
        "AccountingEntityDataProblemCreatingInvoiceFromOrderItems",
        UtilMisc.toMap("reason", e.toString()), locale));
    } finally {
      // resume the parent transaction
      if (parentTransaction != null) {
        try {
          TransactionUtil.resume(parentTransaction);
        } catch (GenericTransactionException ite) {
          Debug.logWarning(ite, "Transaction error, not resumed", module);
          throw new GenericServiceException("Resume transaction exception, see logs");
        }
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("invoiceId", lastInvoiceNumber);
    return result;
  }
{code}

And in the service-defition:

{code:title=sales_invoice.xml|borderStyle=solid}
    <service name="invoiceSequence-enforced" engine="java"
             location="org.ofbiz.accounting.invoice.InvoiceServices" invoke="invoiceSequenceEnforced">
      <attribute name="partyAcctgPreference" type="org.ofbiz.entity.GenericValue" mode="IN"/>

      <attribute name="partyId" type="String" mode="IN" optional="false"/>
      <attribute name="invoiceId" type="Long" mode="OUT"/>
    </service>
{code}

Up until now, this seems to work fine. It still has the above mentioned problem, that a sequence number is wasted if an invoice creation is rolled back, though. Therefor this issue is still marked as open, this is just a minor workaround.

                
      was (Author: mmay@apache.org):
    We have now implemented the Service "invoiceSequenceEnforced" with a small Java-Method, so that the Transaction starts in the method and ends after committing the change on the DB. Seems to work now ;-)

{code:title=InvoiceServices.java|borderStyle=solid}
  // service to create a new invoiceId in a small transaction, so that the ids are updated correctly
  public static Map<String, Object> invoiceSequenceEnforced(DispatchContext dctx, Map<String, Object> context) throws GenericServiceException {
    Delegator delegator = dctx.getDelegator();
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Locale locale = (Locale) context.get("locale");

    String partyId = (String) context.get("partyId");
    Transaction parentTransaction = null;
    Long lastInvoiceNumber = null;

    try {
      // Enforce a new Transaction
      parentTransaction = TransactionUtil.suspend();
      if (TransactionUtil.isTransactionInPlace()) {
        throw new GenericTransactionException("In service " + module + " transaction is still in place after suspend, status is " + TransactionUtil.getStatusString());
      }

      // now start a new transaction
      boolean beganTrans = TransactionUtil.begin(0);

      GenericValue partyAcctgPreference = delegator.findOne("PartyAcctgPreference", false, UtilMisc.toMap("partyId", partyId));

      lastInvoiceNumber = partyAcctgPreference.getLong("lastInvoiceNumber");

      if (UtilValidate.isNotEmpty(lastInvoiceNumber)) {
        lastInvoiceNumber = lastInvoiceNumber + 1L;
      } else {
        lastInvoiceNumber = 1L;
      }

      Debug.logInfo("generated new InvoiceNumber: " + lastInvoiceNumber, module);
      partyAcctgPreference.set("lastInvoiceNumber", lastInvoiceNumber);
      partyAcctgPreference.store();

      TransactionUtil.commit();

    } catch (GenericEntityException e) {
      Debug.logError (e, "Entity/data problem creating invoiceId: " + e.toString(), module);
      return ServiceUtil.returnError(UtilProperties.getMessage(resource,
        "AccountingEntityDataProblemCreatingInvoiceFromOrderItems",
        UtilMisc.toMap("reason", e.toString()), locale));
    } finally {
      // resume the parent transaction
      if (parentTransaction != null) {
        try {
          TransactionUtil.resume(parentTransaction);
        } catch (GenericTransactionException ite) {
          Debug.logWarning(ite, "Transaction error, not resumed", module);
          throw new GenericServiceException("Resume transaction exception, see logs");
        }
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("invoiceId", lastInvoiceNumber);
    return result;
  }
{code}

And in the service-defition:

{code title=sales_invoice.xml|borderStyle=solid}
    <service name="invoiceSequence-enforced" engine="java"
             location="org.ofbiz.accounting.invoice.InvoiceServices" invoke="invoiceSequenceEnforced">
      <attribute name="partyAcctgPreference" type="org.ofbiz.entity.GenericValue" mode="IN"/>

      <attribute name="partyId" type="String" mode="IN" optional="false"/>
      <attribute name="invoiceId" type="Long" mode="OUT"/>
    </service>
{code}

Up until now, this seems to work fine. It still has the above mentioned problem, that a sequence number is wasted if an invoice creation is rolled back, though. Therefor this issue is still marked as open, this is just a minor workaround.

                  
> Enforced sequence does not work with concurrent access
> ------------------------------------------------------
>
>                 Key: OFBIZ-3557
>                 URL: https://issues.apache.org/jira/browse/OFBIZ-3557
>             Project: OFBiz
>          Issue Type: Bug
>          Components: framework
>    Affects Versions: Release Branch 09.04, SVN trunk
>            Reporter: Wickersheimer Jeremy
>         Attachments: OFBIZ-3557-1.patch, OFBIZ-3557-2.patch
>
>
> There is a fundamental issue with enforced sequences (for orders, invoices, etc ..) and concurrency.
> For example if two users are creating an order at the same time one of them will see the creation fail with a PK error. The problem is that the "getNextXXXId" rely on the party accounting preference entity, but there is absolutely no guarantee that the last number in the sequence gets updated before another service can read it.
> This is at best very annoying when used only internally but may be unpractical for e-commerce sites.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira