diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java index b8ac8f42c..5edfc43ba 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -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 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 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. + *

+ * Will use delete by object if the child entity has manyToMany relationships. + */ + private void deleteChildrenById(SpiTransaction t, BeanDescriptor targetDesc, List childIds) { + + if (targetDesc.propertiesManyToMany().length > 0) { + // convert into a list of reference objects and perform delete by object + List refList = new ArrayList(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. */ diff --git a/src/test/java/com/avaje/tests/m2m/TestM2MDeleteObjectWithCascadeToM2m.java b/src/test/java/com/avaje/tests/m2m/TestM2MDeleteObjectWithCascadeToM2m.java new file mode 100644 index 000000000..e7d9e2551 --- /dev/null +++ b/src/test/java/com/avaje/tests/m2m/TestM2MDeleteObjectWithCascadeToM2m.java @@ -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 permissions = new HashSet(); + List permsList = Ebean.find(Permission.class).findList(); + permissions.addAll(permsList); + + role1.setPermissions(permissions); + + Ebean.save(role1); + + + List tenantList = Ebean.find(Tenant.class).fetch("roles").findList(); + List roleList = Ebean.find(Role.class).fetch("permissions").findList(); + List 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 tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList(); + List roleList2 = Ebean.find(Role.class).fetch("permissions").findList(); + List permissionList2 = Ebean.find(Permission.class).fetch("roles").findList(); + + Assert.assertEquals(0, tenantList2.size()); + Assert.assertEquals(0, roleList2.size()); + Assert.assertEquals(2, permissionList2.size()); + + } +} diff --git a/src/test/java/com/avaje/tests/m2m/TestM2mDeleteObject.java b/src/test/java/com/avaje/tests/m2m/TestM2mDeleteObject.java new file mode 100644 index 000000000..0daecd353 --- /dev/null +++ b/src/test/java/com/avaje/tests/m2m/TestM2mDeleteObject.java @@ -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 permissions = new HashSet(); + List permsList = Ebean.find(Permission.class).findList(); + permissions.addAll(permsList); + + role1.setPermissions(permissions); + + Ebean.save(role1); + + List tenantList = Ebean.find(Tenant.class).fetch("roles").findList(); + List roleList = Ebean.find(Role.class).fetch("permissions").findList(); + List 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 tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList(); + List roleList2 = Ebean.find(Role.class).fetch("permissions").findList(); + List permissionList2 = Ebean.find(Permission.class).fetch("roles").findList(); + + Assert.assertEquals(0, roleList2.size()); + Assert.assertEquals(1, tenantList2.size()); + Assert.assertEquals(2, permissionList2.size()); + } + +} diff --git a/src/test/java/com/avaje/tests/model/m2m/Permission.java b/src/test/java/com/avaje/tests/model/m2m/Permission.java new file mode 100644 index 000000000..e364f822b --- /dev/null +++ b/src/test/java/com/avaje/tests/model/m2m/Permission.java @@ -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 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 getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @Override + public String toString() { + return "name:" + name + "id:" + id; + } + +} diff --git a/src/test/java/com/avaje/tests/model/m2m/Role.java b/src/test/java/com/avaje/tests/model/m2m/Role.java new file mode 100644 index 000000000..35bd29932 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/m2m/Role.java @@ -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 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 getPermissions() { + return permissions; + } + + public void setPermissions(Set 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; + } +} diff --git a/src/test/java/com/avaje/tests/model/m2m/Tenant.java b/src/test/java/com/avaje/tests/model/m2m/Tenant.java new file mode 100644 index 000000000..afa726d2d --- /dev/null +++ b/src/test/java/com/avaje/tests/model/m2m/Tenant.java @@ -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 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 getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @Override + public String toString() { + return "name:" + name + " id:" + id; + } +}