曾经做项目没有考虑那么多,对于级联表操作都是正常的一步一步操作,没有考虑过失败情况,最近项目遇见了失败的情况,导致碰到了相应的情况,特此mark一下,免得后期继续踩坑。
需求如下:新建页面,页面中包含1.新建企业,2.新建联系人,3.新建机会。任何一步的逻辑或者DML操作失败都会导致整体的回滚。只有当三步都正常插入成功了以后才会跳转到新生成的机会的标准页面。
1.NewOpportunityController:这里做了一个逻辑判断,当联系人为空情况下,不允许新建联系人。当然,现实场景不会在这里判断,但是现实场景会有很多的复杂的业务逻辑,这里只是简单的处理。
1 public class newOpportunityController { 2 Account account; 3 Contact contact; 4 Opportunity opportunity; 5 OpportunityContactRole role; 6 7 public Account getAccount() { 8 if(account == null) 9 account = new Account(); 10 return account; 11 } 12 public Contact getContact() { 13 if(contact == null) 14 contact = new Contact(); 15 return contact; 16 } 17 public Opportunity getOpportunity() { 18 if(opportunity == null) 19 opportunity = new Opportunity(); 20 return opportunity; 21 } 22 public OpportunityContactRole getRole() { 23 if(role == null) 24 role = new OpportunityContactRole(); 25 return role; 26 } 27 28 29 public PageReference save() { 30 Savepoint sp = Database.setSavepoint(); 31 try { 32 account.phone = contact.phone; 33 insert account; 34 } catch(Exception e) { 35 Database.rollback(sp); 36 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入企业失败‘)); 37 38 return null; 39 } 40 try { 41 if(contact.phone == null) { 42 Database.rollback(sp); 43 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘联系人电话不能为空‘)); 44 return null; 45 } 46 contact.accountId = account.id; 47 insert contact; 48 } catch(Exception e) { 49 Database.rollback(sp); 50 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入联系人失败‘)); 51 return null; 52 } 53 try { 54 opportunity.accountId = account.id; 55 insert opportunity; 56 role.opportunityId = opportunity.id; 57 role.contactId = contact.id; 58 insert role; 59 } catch(Exception e) { 60 Database.rollback(sp); 61 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入机会失败‘)); 62 return null; 63 } 64 //跳转到新插入的opportunity的系统页面 65 PageReference opptyPage = new ApexPages.StandardController(opportunity).view(); 66 opptyPage.setRedirect(true); 67 return opptyPage; 68 } 69 }
2.NewOpportunityPage:填写企业信息,联系人信息和机会信息并实现提交
1 <apex:page controller="newOpportunityController" tabStyle="Opportunity"> 2 3 <apex:sectionHeader title="New Customer Opportunity"/> 4 <apex:form id="theForm"> 5 <apex:pageMessages/> 6 <apex:pageBlock title="Customer Information" mode="edit"> 7 <apex:pageBlockSection title="Account Information"> 8 <apex:inputField id="accountName" value="{!account.name}"/> 9 <apex:inputField id="accountSite" value="{!account.site}"/> 10 </apex:pageBlockSection> 11 <apex:pageBlockSection title="Contact Information"> 12 <apex:inputField id="contactFirstName" value="{!contact.firstName}"/> 13 <apex:inputField id="contactLastName" value="{!contact.lastName}"/> 14 <apex:inputField id="contactPhone" value="{!contact.phone}"/> 15 </apex:pageBlockSection> 16 17 <apex:pageBlockSection title="Opportunity Information"> 18 <apex:inputField id="opportunityName" value="{!opportunity.name}"/> 19 <apex:inputField id="opportunityAmount" value="{!opportunity.amount}"/> 20 <apex:inputField id="opportunityCloseDate" value="{!opportunity.closeDate}"/> 21 <apex:inputField id="opportunityStageName" value="{!opportunity.stageName}"/> 22 <apex:inputField id="contactRole" value="{!role.role}"/> 23 </apex:pageBlockSection> 24 25 <apex:pageBlockButtons > 26 <apex:commandButton action="{!save}" value="Save" reRender="theForm"/> 27 </apex:pageBlockButtons> 28 29 </apex:pageBlock> 30 31 </apex:form> 32 </apex:page>
效果展示:
1.填写相关信息,提交表单,特意没有输入联系人,显示效果如下:
2.当对数据进行相关填充以后,结果如下:
再次保存以后提示不能对于已经有ID的对象执行insert操作的错误信息。当时没有太理解因为什么原因导致了这种情况,后来joe给我答疑解惑,我才如梦初醒。当我对Account表执行了insert时,在事务还没有commit情况下,此条记录还没有存储到数据库中,但是controller中的对象便已经有了ID字段的值。当后期操作需要事务回滚时,数据库不保存insert进去的记录,但是此对象的ID却不会被清空,这就导致了下次insert此对象时,此对象已经有了ID,从而不能进行insert的操作了。同理,如果数据库没有当前的数据,对象却有ID,即使执行upsert操作也是会报类似的错误。
在我们对相关级联表进行DML操作的时候,可以使用clone操作,当回滚的时候,只是回滚数据库的内容,但是原来绑定到前台的对象并没有生成相关的ID,从而可以摆脱上述的尴尬。对Controller层改造代码如下:
1 public class newOpportunityController { 2 Account account; 3 Contact contact; 4 Opportunity opportunity; 5 OpportunityContactRole role; 6 7 public Account getAccount() { 8 if(account == null) 9 account = new Account(); 10 return account; 11 } 12 public Contact getContact() { 13 if(contact == null) 14 contact = new Contact(); 15 return contact; 16 } 17 public Opportunity getOpportunity() { 18 if(opportunity == null) 19 opportunity = new Opportunity(); 20 return opportunity; 21 } 22 public OpportunityContactRole getRole() { 23 if(role == null) 24 role = new OpportunityContactRole(); 25 return role; 26 } 27 28 public PageReference save() { 29 Savepoint sp = Database.setSavepoint(); 30 Account cloneAccount; 31 Contact cloneContact; 32 Opportunity cloneOpportunity; 33 OpportunityContactRole cloneRole; 34 try { 35 account.phone = contact.phone; 36 cloneAccount = account.clone(true); 37 insert cloneAccount; 38 } catch(Exception e) { 39 Database.rollback(sp); 40 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入企业失败‘)); 41 42 return null; 43 } 44 try { 45 if(contact.phone == null) { 46 Database.rollback(sp); 47 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘联系人电话不能为空‘)); 48 return null; 49 } 50 contact.accountId = cloneAccount.Id; 51 cloneContact = contact.clone(true); 52 insert cloneContact; 53 } catch(Exception e) { 54 Database.rollback(sp); 55 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入联系人失败‘)); 56 return null; 57 } 58 try { 59 opportunity.accountId = cloneAccount.id; 60 cloneOpportunity = opportunity.clone(false); 61 insert cloneOpportunity; 62 role.opportunityId = cloneOpportunity.id; 63 role.contactId = cloneContact.id; 64 cloneRole = role.clone(false); 65 insert cloneRole; 66 } catch(Exception e) { 67 Database.rollback(sp); 68 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,‘插入机会失败‘)); 69 return null; 70 } 71 //跳转到新插入的opportunity的系统页面 72 PageReference opptyPage = new ApexPages.StandardController(cloneOpportunity).view(); 73 opptyPage.setRedirect(true); 74 return opptyPage; 75 } 76 }
效果展示:
1.当信息填写不完整情况下效果展示:
2.填好信息保存以后跳转到标准页面
总结:当对级联表进行操作的时候,一定要考虑一下当因为某些业务逻辑或者数据自身操作失败导致需要回滚情况下,导致数据库中不存在本条记录然而后台绑定的对象却相关复制的情况,如果编辑的case没有问题,但是涉及到新增的情况便暴露出来此问题了。篇中有描述错误的地方欢迎指出,有不懂得欢迎留言。除了使用clone操作以外应该还有其他的好操作可以避免此种事情的发生,如果有更好的操作,欢迎留言。