From 67a267f84336003df490216bd092f4c655026bbc Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Tue, 22 Mar 2016 11:57:44 +1300
Subject: [PATCH] #604 - @SoftDelete with deletePermanent() on lazy loaded
@OneToOne
---
.../server/core/PersistRequestBean.java | 7 +++
.../persist/DeleteUnloadedForeignKeys.java | 26 ++++----
.../tests/model/onetoone/album/Album.java | 35 +++++++++++
.../tests/model/onetoone/album/BaseModel.java | 57 ++++++++++++++++++
.../tests/model/onetoone/album/Cover.java | 50 ++++++++++++++++
.../album/TestOneToOneHardDelete.java | 59 +++++++++++++++++++
6 files changed, 224 insertions(+), 10 deletions(-)
create mode 100644 src/test/java/com/avaje/tests/model/onetoone/album/Album.java
create mode 100644 src/test/java/com/avaje/tests/model/onetoone/album/BaseModel.java
create mode 100644 src/test/java/com/avaje/tests/model/onetoone/album/Cover.java
create mode 100644 src/test/java/com/avaje/tests/model/onetoone/album/TestOneToOneHardDelete.java
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
index d48b5e755..02a390349 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -562,6 +562,13 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
return false;
}
+ /**
+ * Return true if this was a hard/permanent delete request (and should cascade as such).
+ */
+ public boolean isHardDeleteCascade() {
+ return (type == Type.DELETE && beanDescriptor.isSoftDelete());
+ }
+
/**
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
* Save or Update is not allowed to execute using 'live' beans - must use publish().
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java
index 0a5735344..6b5093d25 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java
@@ -1,8 +1,5 @@
package com.avaje.ebeaninternal.server.persist;
-import java.util.ArrayList;
-import java.util.List;
-
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
@@ -13,6 +10,9 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Used for deletion of a partially populated bean where some cascade delete
* properties where not loaded.
@@ -20,10 +20,8 @@ import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
* This bean effectively holds the foreign properties that where not loaded, and
* helps fetch the foreign keys and delete the appropriate rows.
*
- *
- * @author rbygrave
*/
-public class DeleteUnloadedForeignKeys {
+class DeleteUnloadedForeignKeys {
private final List> propList = new ArrayList>(4);
@@ -31,11 +29,14 @@ public class DeleteUnloadedForeignKeys {
private final PersistRequestBean> request;
+ private final boolean deletePermanent;
+
private EntityBean beanWithForeignKeys;
- public DeleteUnloadedForeignKeys(SpiEbeanServer server, PersistRequestBean> request) {
+ DeleteUnloadedForeignKeys(SpiEbeanServer server, PersistRequestBean> request) {
this.server = server;
this.request = request;
+ this.deletePermanent = request.isHardDeleteCascade();
}
public boolean isEmpty() {
@@ -50,7 +51,7 @@ public class DeleteUnloadedForeignKeys {
* Execute a query fetching the missing (unloaded) foreign keys. We need to
* fetch these key values before the parent bean is deleted.
*/
- public void queryForeignKeys() {
+ void queryForeignKeys() {
BeanDescriptor> descriptor = request.getBeanDescriptor();
SpiQuery> q = (SpiQuery>) server.createQuery(descriptor.getBeanType());
@@ -67,6 +68,7 @@ public class DeleteUnloadedForeignKeys {
q.setPersistenceContextScope(PersistenceContextScope.QUERY);
q.setAutoTune(false);
q.select(sb.toString());
+ q.includeSoftDeletes();
q.where().idEq(id);
SpiTransaction t = request.getTransaction();
@@ -80,7 +82,7 @@ public class DeleteUnloadedForeignKeys {
* Delete the rows relating to the foreign keys. These deletions occur after
* the parent bean has been deleted.
*/
- public void deleteCascade() {
+ void deleteCascade() {
for (int i = 0; i < propList.size(); i++) {
BeanPropertyAssocOne> prop = propList.get(i);
@@ -88,7 +90,11 @@ public class DeleteUnloadedForeignKeys {
// if bean exists with a unique id then delete it
if (detailBean != null && prop.hasId((EntityBean) detailBean)) {
- server.delete(detailBean, request.getTransaction());
+ if (deletePermanent) {
+ server.deletePermanent(detailBean, request.getTransaction());
+ } else {
+ server.delete(detailBean, request.getTransaction());
+ }
}
}
}
diff --git a/src/test/java/com/avaje/tests/model/onetoone/album/Album.java b/src/test/java/com/avaje/tests/model/onetoone/album/Album.java
new file mode 100644
index 000000000..4b4f195a1
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/onetoone/album/Album.java
@@ -0,0 +1,35 @@
+package com.avaje.tests.model.onetoone.album;
+
+import com.avaje.ebean.Model;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToOne;
+
+@Entity
+public class Album extends BaseModel {
+
+ public static final Model.Finder find = new Model.Finder(Album.class);
+
+ private String name;
+
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
+ private Cover cover;
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Cover getCover() {
+ return this.cover;
+ }
+
+ public void setCover(Cover cover) {
+ this.cover = cover;
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/onetoone/album/BaseModel.java b/src/test/java/com/avaje/tests/model/onetoone/album/BaseModel.java
new file mode 100644
index 000000000..b3b6fc543
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/onetoone/album/BaseModel.java
@@ -0,0 +1,57 @@
+package com.avaje.tests.model.onetoone.album;
+
+import com.avaje.ebean.Model;
+import com.avaje.ebean.annotation.SoftDelete;
+import com.avaje.ebean.annotation.WhenCreated;
+import com.avaje.ebean.annotation.WhenModified;
+import org.joda.time.DateTime;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+@MappedSuperclass
+public abstract class BaseModel extends Model {
+
+ @Id
+ protected Long id;
+
+ @SoftDelete
+ @Column(name = "deleted", nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE")
+ protected boolean deleted;
+
+ @WhenCreated
+ protected DateTime createdAt;
+
+ @WhenModified
+ protected DateTime lastUpdate;
+
+ protected BaseModel() {
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public DateTime getCreatedAt() {
+ return this.createdAt;
+ }
+
+ public DateTime getLastUpdate() {
+ return this.lastUpdate;
+ }
+
+ /**
+ * Check if this entry is soft deleted.
+ */
+ public boolean isDeleted() {
+ return this.deleted;
+ }
+
+ /**
+ * Delete this entry permanently.
+ */
+ public boolean deletePermanent() {
+ return db().deletePermanent(this);
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/onetoone/album/Cover.java b/src/test/java/com/avaje/tests/model/onetoone/album/Cover.java
new file mode 100644
index 000000000..3240dde0e
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/onetoone/album/Cover.java
@@ -0,0 +1,50 @@
+package com.avaje.tests.model.onetoone.album;
+
+import com.avaje.ebean.Model;
+import com.avaje.ebean.annotation.SoftDelete;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.PreRemove;
+
+@Entity
+public class Cover extends Model {
+
+ private static final Logger logger = LoggerFactory.getLogger(Cover.class);
+
+ public static final Finder find = new Finder(Cover.class);
+
+ @Id
+ protected Long id;
+
+ @SoftDelete
+ @Column(name = "deleted", nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE")
+ protected boolean deleted;
+
+ protected String s3Url;
+
+ public String getS3Url() {
+ return this.s3Url;
+ }
+
+ public void setS3Url(String s3Url) {
+ this.s3Url = s3Url;
+ }
+
+ @PreRemove
+ public void deleteRemoteFile() {
+ logger.debug("Cover::deleteRemoteFile() --> Remove file from Amazon S3");
+ }
+
+ public boolean isDeleted() {
+ return this.deleted;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+}
diff --git a/src/test/java/com/avaje/tests/model/onetoone/album/TestOneToOneHardDelete.java b/src/test/java/com/avaje/tests/model/onetoone/album/TestOneToOneHardDelete.java
new file mode 100644
index 000000000..97228d2fd
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/onetoone/album/TestOneToOneHardDelete.java
@@ -0,0 +1,59 @@
+package com.avaje.tests.model.onetoone.album;
+
+
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestOneToOneHardDelete {
+
+ @Test
+ public void test() {
+
+ Cover cover = new Cover();
+ cover.setS3Url("http://foo");
+ cover.save();
+
+ Album album = new Album();
+ album.setName("BlackWhite");
+ album.setCover(cover);
+ album.save();
+
+ Album found1 = Album.find.where().findUnique();
+
+ LoggedSqlCollector.start();
+
+ // ---------------------
+ // SOFT DELETE
+ // ---------------------
+ found1.delete();
+
+ List sql = LoggedSqlCollector.stop();
+ assertThat(sql).hasSize(3);
+ // assert we loaded the missing/unloaded foreign key
+ assertThat(sql.get(0)).contains("select t0.id c0, t0.cover_id c1 from album t0 where t0.id = ?");
+ // assert soft delete cascaded
+ assertThat(sql.get(1)).contains("update album set deleted=?, last_update=? where id=?");
+ assertThat(sql.get(2)).contains("update cover set deleted=? where id=?");
+
+ Album found2 = Album.find.where().includeSoftDeletes().findUnique();
+
+ LoggedSqlCollector.start();
+
+ // ---------------------
+ // HARD DELETE
+ // ---------------------
+ found2.deletePermanent();
+
+ sql = LoggedSqlCollector.stop();
+ assertThat(sql).hasSize(3);
+ // assert we loaded the missing/unloaded foreign key
+ assertThat(sql.get(0)).contains("select t0.id c0, t0.cover_id c1 from album t0 where t0.id = ?");
+ // assert hard delete cascaded
+ assertThat(sql.get(1)).contains("delete from album where");
+ assertThat(sql.get(2)).contains("delete from cover where");
+ }
+}