[ 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: m...@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