Fix for #149 - OneToMany with nested ManyToMany deletion bug

This commit is contained in:
Rob Bygrave
2014-06-23 23:34:22 +12:00
parent 8fe1f32e78
commit df526b8f3c
6 changed files with 379 additions and 3 deletions
@@ -413,14 +413,14 @@ public final class DefaultPersister implements Persister {
if (t.isPersistCascade()) {
// OneToOne exported side with delete cascade
BeanPropertyAssocOne<?>[] expOnes = descriptor.propertiesOneExportedDelete();
for (int i = 0; i < expOnes.length; i++) {
for (int i = 0; i < expOnes.length; i++) {
BeanDescriptor<?> targetDesc = expOnes[i].getTargetDescriptor();
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
delete(targetDesc, null, childIds, t);
deleteChildrenById(t, targetDesc, childIds);
}
}
@@ -1057,12 +1057,33 @@ public final class DefaultPersister implements Persister {
Object parentId = desc.getId(parentBean);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
if (!idsByParentId.isEmpty()) {
delete(targetDesc, null, idsByParentId, t);
deleteChildrenById(t, targetDesc, idsByParentId);
}
}
}
}
/**
* Cascade delete child entities by Id.
* <p>
* Will use delete by object if the child entity has manyToMany relationships.
*/
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds) {
if (targetDesc.propertiesManyToMany().length > 0) {
// convert into a list of reference objects and perform delete by object
List<Object> refList = new ArrayList<Object>(childIds.size());
for (Object id : childIds) {
refList.add(targetDesc.createReference(null, id));
}
deleteList(refList, t);
} else {
// perform delete by statement if possible
delete(targetDesc, null, childIds, t);
}
}
/**
* Save any associated one beans.
*/
@@ -0,0 +1,71 @@
package com.avaje.tests.m2m;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.m2m.Permission;
import com.avaje.tests.model.m2m.Role;
import com.avaje.tests.model.m2m.Tenant;
public class TestM2MDeleteObjectWithCascadeToM2m extends BaseTestCase {
@Test
public void test() {
Ebean.createUpdate(Permission.class, "delete from Permission").execute();
Ebean.createUpdate(Tenant.class, "delete from Tenant").execute();
Ebean.createUpdate(Role.class, "delete from Role").execute();
Tenant tenant1 = new Tenant();
tenant1.setName("Tenant");
Ebean.save(tenant1);
Permission p1 = new Permission();
Permission p2 = new Permission();
p1.setName("p1");
p2.setName("p2");
Ebean.save(p1);
Ebean.save(p2);
Role role1 = new Role();
role1.setName("RoleOne");
role1.setTenant(tenant1);
Set<Permission> permissions = new HashSet<Permission>();
List<Permission> permsList = Ebean.find(Permission.class).findList();
permissions.addAll(permsList);
role1.setPermissions(permissions);
Ebean.save(role1);
List<Tenant> tenantList = Ebean.find(Tenant.class).fetch("roles").findList();
List<Role> roleList = Ebean.find(Role.class).fetch("permissions").findList();
List<Permission> permissionList = Ebean.find(Permission.class).fetch("roles").findList();
Assert.assertEquals(1, tenantList.size());
Assert.assertEquals(2, permissionList.size());
Assert.assertEquals(1, roleList.size());
Ebean.delete(tenant1);
List<Tenant> tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList();
List<Role> roleList2 = Ebean.find(Role.class).fetch("permissions").findList();
List<Permission> permissionList2 = Ebean.find(Permission.class).fetch("roles").findList();
Assert.assertEquals(0, tenantList2.size());
Assert.assertEquals(0, roleList2.size());
Assert.assertEquals(2, permissionList2.size());
}
}
@@ -0,0 +1,71 @@
package com.avaje.tests.m2m;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.m2m.Permission;
import com.avaje.tests.model.m2m.Role;
import com.avaje.tests.model.m2m.Tenant;
public class TestM2mDeleteObject extends BaseTestCase {
@Test
public void test() {
Ebean.createUpdate(Permission.class, "delete from Permission").execute();
Ebean.createUpdate(Tenant.class, "delete from Tenant").execute();
Ebean.createUpdate(Role.class, "delete from Role").execute();
Tenant t = new Tenant();
t.setName("tenant");
Ebean.save(t);
Permission p1 = new Permission();
Permission p2 = new Permission();
p1.setName("p1");
p2.setName("p2");
Ebean.save(p1);
Ebean.save(p2);
Role role1 = new Role();
role1.setName("role");
role1.setTenant(t);
Set<Permission> permissions = new HashSet<Permission>();
List<Permission> permsList = Ebean.find(Permission.class).findList();
permissions.addAll(permsList);
role1.setPermissions(permissions);
Ebean.save(role1);
List<Tenant> tenantList = Ebean.find(Tenant.class).fetch("roles").findList();
List<Role> roleList = Ebean.find(Role.class).fetch("permissions").findList();
List<Permission> permissionList = Ebean.find(Permission.class).fetch("roles").findList();
Assert.assertEquals(1, tenantList.size());
Assert.assertEquals(2, permissionList.size());
Assert.assertEquals(1, roleList.size());
Ebean.delete(role1);
List<Tenant> tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList();
List<Role> roleList2 = Ebean.find(Role.class).fetch("permissions").findList();
List<Permission> permissionList2 = Ebean.find(Permission.class).fetch("roles").findList();
Assert.assertEquals(0, roleList2.size());
Assert.assertEquals(1, tenantList2.size());
Assert.assertEquals(2, permissionList2.size());
}
}
@@ -0,0 +1,60 @@
package com.avaje.tests.model.m2m;
import java.util.Set;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import com.avaje.ebean.annotation.CacheStrategy;
/**
* The Class Permission.
*/
@Entity
@CacheStrategy(readOnly = true)
@Table(name = "mt_permission")
public class Permission {
@Id
private UUID id;
@Column
private String name;
@ManyToMany(mappedBy = "permissions")
private Set<Role> roles;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "name:" + name + "id:" + id;
}
}
@@ -0,0 +1,81 @@
package com.avaje.tests.model.m2m;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Version;
/**
* The Class Role.
*/
@Entity
@Table(name = "mt_role")
public class Role {
@Id
private UUID id;
@Column(length = 50)
private String name;
@ManyToMany(cascade = CascadeType.REMOVE)
private Set<Permission> permissions;
@ManyToOne
private Tenant tenant;
@Version
private Long version;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public String toString() {
return "name:" + name + " id:" + id + " tenant:" + tenant;
}
}
@@ -0,0 +1,72 @@
package com.avaje.tests.model.m2m;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
/**
* The Class Tenant.
*/
@Entity
@Table(name = "mt_tenant")
public class Tenant {
@Id
private UUID id;
@Column
private String name;
@OneToMany(mappedBy = "tenant", cascade = CascadeType.REMOVE)
private Set<Role> roles;
@Version
private Long version;
public Tenant() {
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "name:" + name + " id:" + id;
}
}