|
[ 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: [hidden email]): 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 |
| Free forum by Nabble | Edit this page |
