@@ -27,14 +30,20 @@
io.ebean
ebean
- 12.11.1
+ ${ebean-version}
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.28
io.ebean
querybean-generator
- 12.11.1
+ ${ebean-version}
provided
@@ -43,7 +52,7 @@
io.ebean
ebean-test
- 12.11.1
+ ${ebean-version}
test
@@ -75,7 +84,7 @@
org.avaje.tile:java-compile:11
- io.ebean.tile:enhancement:12.11.1
+ io.ebean.tile:enhancement:${ebean-version}
diff --git a/src/main/java/org/example/domain/Customer.java b/src/main/java/org/example/domain/Customer.java
deleted file mode 100644
index 0d3da1f..0000000
--- a/src/main/java/org/example/domain/Customer.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.example.domain;
-
-import io.ebean.Model;
-
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Version;
-
-@Entity
-public class Customer extends Model {
-
- @Id
- Long id;
-
- String name;
-
- String notes;
-
- @Version
- Long version;
-
- public Customer(String name) {
- this.name = name;
- }
-
- public Customer() {
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getNotes() {
- return notes;
- }
-
- public void setNotes(String notes) {
- this.notes = notes;
- }
-
- public Long getVersion() {
- return version;
- }
-
- public void setVersion(Long version) {
- this.version = version;
- }
-
-}
diff --git a/src/main/java/org/example/domain/Environment.java b/src/main/java/org/example/domain/Environment.java
new file mode 100644
index 0000000..f02cd34
--- /dev/null
+++ b/src/main/java/org/example/domain/Environment.java
@@ -0,0 +1,34 @@
+package org.example.domain;
+
+import java.util.UUID;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import io.ebean.Model;
+import io.ebean.annotation.SoftDelete;
+
+@Entity
+public class Environment extends Model {
+ @Id
+ private UUID id;
+
+ @SoftDelete
+ private boolean deleted;
+
+ private String name;
+
+ public Environment(String name, boolean deleted) {
+ this.id = UUID.randomUUID();
+ this.name = name;
+ this.deleted = deleted;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/org/example/domain/ServiceAccount.java b/src/main/java/org/example/domain/ServiceAccount.java
new file mode 100644
index 0000000..f6755b7
--- /dev/null
+++ b/src/main/java/org/example/domain/ServiceAccount.java
@@ -0,0 +1,45 @@
+package org.example.domain;
+
+import java.util.UUID;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+
+import io.ebean.Model;
+import io.ebean.annotation.SoftDelete;
+
+@Entity
+public class ServiceAccount extends Model {
+ @Id
+ private UUID id;
+
+ @ManyToOne(cascade = CascadeType.REMOVE)
+ private Environment environment;
+
+ @SoftDelete
+ private boolean deleted;
+
+ private String name;
+
+ public ServiceAccount(String name, Environment environment, boolean deleted) {
+ this.id = UUID.randomUUID();
+ this.name = name;
+ this.environment = environment;
+ this.deleted = deleted;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/org/example/domain/UsageRaw.java b/src/main/java/org/example/domain/UsageRaw.java
new file mode 100644
index 0000000..9e362e0
--- /dev/null
+++ b/src/main/java/org/example/domain/UsageRaw.java
@@ -0,0 +1,39 @@
+package org.example.domain;
+
+import java.util.UUID;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+
+import io.ebean.Model;
+
+@Entity
+public class UsageRaw extends Model {
+ @Id
+ private UUID id;
+
+ @ManyToOne(cascade = CascadeType.REMOVE)
+ private ServiceAccount serviceAccount;
+
+ private String name;
+
+ public UsageRaw(String name, ServiceAccount serviceAccount) {
+ this.id = UUID.randomUUID();
+ this.name = name;
+ this.serviceAccount = serviceAccount;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public ServiceAccount getServiceAccount() {
+ return serviceAccount;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/resources/dbmigration/1.1_create_tables.xml b/src/main/resources/dbmigration/1.1_create_tables.xml
new file mode 100644
index 0000000..a4857e1
--- /dev/null
+++ b/src/main/resources/dbmigration/1.1_create_tables.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/main/MainDbMigration.java b/src/test/java/main/MainDbMigration.java
index 428b246..0db1f00 100644
--- a/src/test/java/main/MainDbMigration.java
+++ b/src/test/java/main/MainDbMigration.java
@@ -18,7 +18,7 @@ public static void main(String[] args) throws Exception {
//System.setProperty("ddl.migration.pendingDropsFor", "1.1");
DbMigration dbMigration = DbMigration.create();
- dbMigration.setPlatform(Platform.POSTGRES);
+ dbMigration.setPlatform(Platform.MYSQL);
// generate the migration ddl and xml
dbMigration.generateMigration();
}
diff --git a/src/test/java/org/example/domain/CustomerQueryTest.java b/src/test/java/org/example/domain/CustomerQueryTest.java
deleted file mode 100644
index 6524ab1..0000000
--- a/src/test/java/org/example/domain/CustomerQueryTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.example.domain;
-
-import io.ebean.DB;
-import org.example.domain.query.QCustomer;
-import org.junit.jupiter.api.Test;
-
-public class CustomerQueryTest {
-
- @Test
- public void findAll() {
-
- DB.find(Customer.class)
- .findList();
-
- new QCustomer()
- .id.greaterOrEqualTo(1L)
- .findList();
- }
-}
diff --git a/src/test/java/org/example/domain/CustomerTest.java b/src/test/java/org/example/domain/CustomerTest.java
deleted file mode 100644
index 29e66e4..0000000
--- a/src/test/java/org/example/domain/CustomerTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.example.domain;
-
-import io.ebean.DB;
-import io.ebean.Database;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-
-/**
- * When running tests in the IDE install the "Enhancement plugin".
- *
- * http://ebean-orm.github.io/docs/setup/enhancement#ide
- */
-public class CustomerTest {
-
-
- /**
- * Get the "default database" and save().
- */
- @Test
- public void insert_via_database() {
-
- Customer rob = new Customer("Rob");
-
- Database server = DB.getDefault();
- server.save(rob);
-
- assertNotNull(rob.getId());
- }
-
- /**
- * Use the Ebean singleton (effectively using the "default server").
- */
- @Test
- public void insert_via_model() {
-
- Customer jim = new Customer("Jim");
- jim.save();
-
- assertNotNull(jim.getId());
- }
-
-
- /**
- * Find and then update.
- */
- @Test
- public void updateRob() {
-
- Customer newBob = new Customer("Bob");
- newBob.save();
-
- Customer bob = DB.find(Customer.class)
- .where().eq("name", "Bob")
- .findOne();
-
- bob.setNotes("Doing an update");
- bob.save();
- }
-
- /**
- * Execute an update without a prior query.
- */
- @Test
- public void statelessUpdate() {
-
- Customer newMob = new Customer("Mob");
- newMob.save();
-
- Customer upd = new Customer();
- upd.setId(newMob.getId());
- upd.setNotes("Update without a fetch");
-
- upd.update();
- }
-
-}
diff --git a/src/test/java/org/example/domain/LazyLoadingSoftDeletedReferencesTest.java b/src/test/java/org/example/domain/LazyLoadingSoftDeletedReferencesTest.java
new file mode 100644
index 0000000..6b1c261
--- /dev/null
+++ b/src/test/java/org/example/domain/LazyLoadingSoftDeletedReferencesTest.java
@@ -0,0 +1,157 @@
+package org.example.domain;
+
+import static java.util.Objects.nonNull;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.ebean.DB;
+import io.ebean.Database;
+
+public class LazyLoadingSoftDeletedReferencesTest {
+ @BeforeEach
+ private void setup() {
+ insertRows(true);
+ insertRows(false);
+ }
+
+ @AfterEach
+ private void cleanup() {
+ Database server = DB.getDefault();
+ server.deleteAll(server.find(UsageRaw.class).findList());
+ }
+
+ /**
+ * Demonstrates that default fetching behaves differently as of 13.6.6.
+ *
+ * Also demonstrates workaround, using fetchLazy() which yields desired results.
+ */
+ @Test
+ public void setIncludeSoftDeletes_does_not_fetch_deep_relations_that_are_softdeleted() {
+ // given: existing usage
+ /*
+ [
+ { id: 1, account: { name: 'usage-1', deleted: false, environment: { name: 'env-1', deleted: false } },
+ { id: 2, account: { name: 'usage-2', deleted: true, environment: { name: 'env-2', deleted: true } },
+ ] */
+ Database server = DB.getDefault();
+
+ // demonstrates new behaviour in 13.6.6
+ // when: selecting without fetch variations
+ List usage = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .findList();
+ // then: soft-deleted environments not loaded
+ assert(usage.size() == 2);
+ assert(usage.stream().allMatch(accountLoaded()));
+ assert(usage.stream().filter(environmentLoaded()).count() == 1);
+
+ // demonstrates workaround, use fetchLazy()
+ // when: lazy fetching
+ List lazyUsage = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .fetchLazy("serviceAccount.environment")
+ .findList();
+ // then: soft-deleted environments are loaded
+ assert(lazyUsage.size() == 2);
+ assert(lazyUsage.stream().allMatch(accountLoaded()));
+ assert(lazyUsage.stream().allMatch(environmentLoaded()));
+ }
+
+ /**
+ * Demonstrates that .fetchLazy() with multiple paths behaves differently in 13.6.6
+ * and does not behave the same as .fetch().
+ *
+ * Also demonstrates workaround that yield desired results.
+ */
+
+ @Test
+ public void fetchLazy_with_multiple_paths_does_not_fetch_deep_relations_that_are_softdeleted() {
+ // given: existing usage
+ /*
+ [
+ { id: 1, account: { name: 'usage-1', deleted: false, environment: { name: 'env-1', deleted: false } },
+ { id: 2, account: { name: 'usage-2', deleted: true, environment: { name: 'env-2', deleted: true } },
+ ] */
+ Database server = DB.getDefault();
+
+ // demonstrates new behaviour in 13.6.6
+ // when: lazy fetching serviceAccount AND serviceAccount.environment
+ List lazyUsageMultiplePaths = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .fetchLazy("serviceAccount")
+ .fetchLazy("serviceAccount.environment")
+ .findList();
+ // then: soft-deleted environments are NOT loaded (not the case before 13.6.6)
+ assert(lazyUsageMultiplePaths.size() == 2);
+ assert(lazyUsageMultiplePaths.stream().allMatch(accountLoaded()));
+ assert(lazyUsageMultiplePaths.stream().filter(environmentLoaded()).count() == 1);
+
+ // demonstrates workaround, use single path, all required properties are loaded
+ // when: lazy fetching serviceAccount.environment
+ List lazyUsageSinglePath = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .fetchLazy("serviceAccount.environment")
+ .findList();
+ // then: soft-deleted environments loaded
+ assert(lazyUsageSinglePath.size() == 2);
+ assert(lazyUsageSinglePath.stream().allMatch(accountLoaded()));
+ assert(lazyUsageSinglePath.stream().allMatch(environmentLoaded()));
+
+ // demonstrates other workaround, use fetch() for environment, all required properties are loaded
+ // when: lazy fetching serviceAccount.environment
+ List lazyUsageMultiplePathsWithFetch = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .fetchLazy("serviceAccount")
+ .fetch("serviceAccount.environment")
+ .findList();
+ // then: soft-deleted environments loaded
+ assert(lazyUsageMultiplePathsWithFetch.size() == 2);
+ assert(lazyUsageMultiplePathsWithFetch.stream().allMatch(accountLoaded()));
+ assert(lazyUsageMultiplePathsWithFetch.stream().allMatch(environmentLoaded()));
+
+ // demonstrates that eager fetching works when multiple paths are used
+ // when: eagerly fetching serviceAccount AND serviceAccount.environment
+ List eagerUsage = server.find(UsageRaw.class)
+ .setIncludeSoftDeletes()
+ .fetch("serviceAccount")
+ .fetch("serviceAccount.environment")
+ .findList();
+ // then: soft-deleted environments loaded
+ assert(eagerUsage.size() == 2);
+ assert(eagerUsage.stream().allMatch(accountLoaded()));
+ assert(eagerUsage.stream().allMatch(environmentLoaded()));
+ }
+
+ private static Predicate accountLoaded() {
+ return usage -> {
+ boolean loaded = nonNull(usage.getServiceAccount().getName());
+ // put breakpoint here to see post-load state
+ return loaded;
+ };
+ }
+
+ private static Predicate environmentLoaded() {
+ return usage -> {
+ boolean loaded = nonNull(usage.getServiceAccount())
+ && nonNull(usage.getServiceAccount().getEnvironment().getName());
+ // put breakpoint here to see post-load state
+ return loaded;
+ };
+ }
+
+ private static void insertRows(boolean softDeleted) {
+ Environment environment = new Environment("environment", softDeleted);
+ ServiceAccount account = new ServiceAccount("account", environment, softDeleted);
+ UsageRaw usage = new UsageRaw("usage", account);
+
+ Database server = DB.getDefault();
+ server.save(environment);
+ server.save(account);
+ server.save(usage);
+ }
+}
diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml
index 49adf3b..fc4c72e 100644
--- a/src/test/resources/application-test.yaml
+++ b/src/test/resources/application-test.yaml
@@ -1,7 +1,11 @@
ebean:
test:
-# useDocker: false
-# shutdown: stop # stop | remove
- platform: h2 # h2, postgres, mysql, oracle, sqlserver, sqlite
- ddlMode: dropCreate # none | dropCreate | create | migration | createOnly | migrationDropCreate
- dbName: myapp
+ useDocker: true
+ platform: mysql # h2, postgres, mysql, oracle, sqlserver
+ dbName: test
+ mysql:
+ username: your-username
+ password: your-password
+ url: jdbc:mysql://localhost:3306/test
+ collation: utf8mb4_unicode_ci
+ characterSet: utf8mb4
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index e16eb74..0c9a6e1 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -28,7 +28,7 @@
-
+
@@ -45,4 +45,4 @@
-
\ No newline at end of file
+