Stateless update problem with OneToOne mapping

85 views
Skip to first unread message

Simon Godard

unread,
Jan 30, 2014, 1:03:24 PM1/30/14
to
Hi,

We are using Ebean 3.2.5 with Maven bean enhancement. Our application is relying heavily on REST APIs (with JSON) to manage CRUD operations, so we are using Ebean in stateless mode for updates.

We would like to use the Cascade mode for our OneToOne associations and are facing a problem with the update. The parent bean is correctly updated, but it tries to insert a new child instead of updating it. This creates an Exception since the primary key already exists.

Model classes

@Entity
@Table(name = "tenants", uniqueConstraints = @UniqueConstraint(columnNames = { "name", "entry_point" }))
public class Tenant {

 
@Id
 
private UUID id;

 
@Column (length=50)
 
private String name;

 
@OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
 
private LDAPServerDefinition ldap;

 
@OneToOne(cascade = CascadeType.ALL)
 
@JoinColumn(name="policy_id")
 
private TenantPolicies tenantPolicies;

 
@Version
 
private Long version;

 
...
}

@Entity
@Table(name = "ldap_server_definitions")
public class LDAPServerDefinition {

   
@Id
   
private UUID id;

   
@OneToOne(cascade = CascadeType.ALL)
   
private Tenant tenant;

   
@Column
   
private String host;

   
@Version
   
private Long version;

   
...
}


@Entity
@Table(name = "tenant_policies")
public class TenantPolicies {

 
@Id
 
private UUID id;

 
@Column(name = "ar_ldap_enabled")
 
private boolean isLdapSelfRequest;

 
...
}


Test code
 @Test
   public void shouldCascadeOneToOneAssociationsOnStatelessUpdate2() throws Exception {
      // given
      Tenant tAcme = new Tenant();
      tAcme.setName("ACME Corp");

      TenantPolicies acmePolicies = new TenantPolicies();
      acmePolicies.setSubtenantsAbleToCreateGroups(false);
      tAcme.setTenantPolicies(acmePolicies);

      LDAPServerDefinition acmeLdap = new LDAPServerDefinition();
      acmeLdap.setHost("myserver.com");
      tAcme.setLdap(acmeLdap);

      ebeanServer.save(tAcme); // initial insert

      Tenant updatedTenant = new Tenant();
      updatedTenant.setId(tAcme.getId());
      updatedTenant.setName("Updated ACME Corp");
      updatedTenant.setVersion(tAcme.getVersion());

      TenantPolicies updatedAcmePolicies = new TenantPolicies();
      updatedAcmePolicies.setId(acmePolicies.getId());
      updatedAcmePolicies.setLdapSelfRequest(true);
      updatedTenant.setTenantPolicies(updatedAcmePolicies);

      LDAPServerDefinition updatedAcmeLdap = new LDAPServerDefinition();
      updatedAcmeLdap.setId(acmeLdap.getId());
      updatedAcmeLdap.setHost("mynewserver.com");
      updatedTenant.setLdap(updatedAcmeLdap);

      // when
      ebeanServer.update(updatedTenant, null, null, true, false);

      // then
      Tenant updatedTenantFromDB = ebeanServer.find(Tenant.class, tAcme.getId());

      LDAPServerDefinition updatedLdapFromDB = updatedTenantFromDB.getLdap();
      assertThat(updatedLdapFromDB.getHost(), is(equalTo("mynewserver.com")));

      TenantPolicies updatedPoliciesFromDB = updatedTenantFromDB.getTenantPolicies();
      assertThat(updatedPoliciesFromDB.isLdapSelfRequest(), is(true));

      // cleanup
      ebeanServer.delete(updatedTenant);
   }



Exception stacktrace:
javax.persistence.PersistenceException: ERROR executing DML bindLog[5ac1c653-339a-40b0-b86f-d15a9e61fbe7,true,false,false,false,false,false,1,] error[Duplicate entry '5ac1c653-339a-40b0-b86f-d15a9e61fbe7' for key 'PRIMARY']
at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:97)
at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.insert(DmlBeanPersister.java:57)
at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeInsertBean(DefaultPersistExecute.java:66)
at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:448)
at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:478)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:335)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.saveEnhanced(DefaultPersister.java:310)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.saveRecurse(DefaultPersister.java:280)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.saveAssocOne(DefaultPersister.java:1159)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.update(DefaultPersister.java:361)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.forceUpdateStateless(DefaultPersister.java:238)
at com.avaje.ebeaninternal.server.persist.DefaultPersister.forceUpdate(DefaultPersister.java:200)
at com.avaje.ebeaninternal.server.core.DefaultServer.update(DefaultServer.java:1609)


My question is: Does Ebean support cascading stateless updates for OneToOne associations ?

Rob Bygrave

unread,
Jan 30, 2014, 3:50:25 PM1/30/14
to ebean@googlegroups

Q: Does Ebean support cascading stateless updates for OneToOne associations ?

Yes, that is the design goal however ... 

I have reproduced your problem. I'll have a decent look tonight, the state of the bean that is inserted (actually both beans) isn't quite right so yes I can see the problem there. A quick glance suggests to me that an issue was introduced here when subclassing support was removed and the "insert vs update detection" logic was changed and now I'd say it is incompatible with the JSON unmarshalling). 

Also it is worth noting that in the 4.0 release the stateless update is basically rewritten.  4.0 has changes to the enhancement and that results in much simpler/better stateless update support. The 4.0 release is about 1 month away (just pushed out 3.3.1 release candidate releases last night so I'll be back to merging 4.x now).


Cheers, Rob.



On 31 January 2014 05:17, Simon Godard <simon....@gmail.com> wrote:
Hi,

We are using Ebean 3.2.5 with Maven bean enhancement. Our application is relying heavily on REST APIs (with JSON) to manage CRUD operations, so we are using Ebean in stateless mode for updates.

We would like to use the Cascade mode for our OneToOne associations and are facing a problem with the update. The parent bean is correctly updated, but it tries to insert a new child instead of updating it. This creates an Exception since the primary key already exists.

My question is: Does Ebean support cascading stateless updates for OneToOne associations ?

--
 
---
You received this message because you are subscribed to the Google Groups "Ebean ORM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ebean+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Simon Godard

unread,
Jan 30, 2014, 5:14:03 PM1/30/14
to eb...@googlegroups.com
Hi Rob,

Thanks for your quick reply. Is there any chance to include the fix in 3.3.1 or only in 4.0 ?

Regards,
Simon

Rob Bygrave

unread,
Jan 30, 2014, 5:34:42 PM1/30/14
to ebean@googlegroups
I'd be looking to fix this in 3.3.1.

Cheers, Rob.

Rob Bygrave

unread,
Apr 2, 2014, 4:42:53 AM4/2/14
to ebean@googlegroups
Hi,

I have logged this issue as:

In short, I looked to fix this in 3.3.1 but I was not happy with the result. There is a workaround and I have documented that in #83 ... the workaround is to explicitly update the OneToOne bean before updating the 'main' bean.  This is not an ideal workaround if there are many related beans that are cascaded to as part of the update (but ok if it is just one or two beans).

Right now, I'm keen to park this and fix it properly in 4.0.  Let me know if that is going to be a real pain.

Cheers, Rob.
Reply all
Reply to author
Forward
0 new messages