From 00b0fcaeb3e81417a8f211b3094f2e052136bbc4 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Mon, 22 Mar 2021 23:24:16 +1300
Subject: [PATCH 01/52] #2202 - kotlin-maven-plugin issue with JDK 16
Temporary modify tests to not invoke the kotlin-maven-plugin while adding JDK 16 specific tests.
Hitting error InaccessibleObjectException: Unable to make protected void java.util.ResourceBundle.setParent(java.util.ResourceBundle) accessible: module java.base does not "opens java.util" to unnamed module @62732be7
---
kotlin-querybean-generator/pom.xml | 77 +++++++++++++++---------------
1 file changed, 39 insertions(+), 38 deletions(-)
diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml
index 7016e2aa4..cf3dca074 100644
--- a/kotlin-querybean-generator/pom.xml
+++ b/kotlin-querybean-generator/pom.xml
@@ -12,7 +12,7 @@
kotlin-querybean-generator
- 1.4.21
+ 1.4.31
@@ -81,46 +81,47 @@
src/test/kotlin
-
- org.jetbrains.kotlin
- kotlin-maven-plugin
- ${kotlin.version}
-
-
- test-compile
- test-compile
-
- test-compile
-
-
-
- test-kapt
-
- test-kapt
-
-
-
- src/test/kotlin
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1.8
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
org.apache.maven.pluginsmaven-compiler-plugin
- 3.2
+ 3.8.1default-testCompile
From 78965c1ac4476ea3f6fc03b945f9b8582cbc52f6 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Mon, 22 Mar 2021 23:43:35 +1300
Subject: [PATCH 02/52] #2203 - Support use of Java Record type with @Entity,
@Embeddable @IdClass
Allow java.lang.Record to be a root level parent for entity beans etc
---
.../ebeaninternal/server/deploy/BeanDescriptorManager.java | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
index 0cb5deeaa..dca6b2b09 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -93,6 +93,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
+ public static final String JAVA_LANG_RECORD = "java.lang.Record";
private final ReadAnnotations readAnnotations;
private final TransientProperties transientProperties;
@@ -1418,14 +1419,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkInheritedClasses(Class> beanClass) {
Class> superclass = beanClass.getSuperclass();
- if (Object.class.equals(superclass)) {
+ if (Object.class.equals(superclass) || Model.class.equals(superclass) || JAVA_LANG_RECORD.equals(superclass.getName())) {
// we got to the top of the inheritance
return;
}
- if (Model.class.equals(superclass)) {
- // top of the inheritance. Not enhancing Model at this stage
- return;
- }
if (!EntityBean.class.isAssignableFrom(superclass)) {
if (isMappedSuperWithNoProperties(superclass)) {
// ok to stop and treat just the same as Object.class
From 94bab5f83b7e0b959dc2e63e7a3e4c7609450bf0 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Mon, 22 Mar 2021 23:44:10 +1300
Subject: [PATCH 03/52] #2203 - Support use of Java Record type with @Entity,
@Embeddable @IdClass
Add tests for entity use of record type
---
tests/pom.xml | 23 +++++++
tests/test-java16/pom.xml | 69 +++++++++++++++++++
.../java/org/example/records/BaseModel.java | 35 ++++++++++
.../main/java/org/example/records/Course.java | 33 +++++++++
.../example/records/CourseRecordEntity.java | 13 ++++
.../records/CourseRecordEntityTest.java | 40 +++++++++++
.../records/DtoQueryUsingRecordsTest.java | 59 ++++++++++++++++
.../src/test/resources/application-test.yaml | 6 ++
.../src/test/resources/logback-test.xml | 22 ++++++
9 files changed, 300 insertions(+)
create mode 100644 tests/pom.xml
create mode 100644 tests/test-java16/pom.xml
create mode 100644 tests/test-java16/src/main/java/org/example/records/BaseModel.java
create mode 100644 tests/test-java16/src/main/java/org/example/records/Course.java
create mode 100644 tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java
create mode 100644 tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java
create mode 100644 tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java
create mode 100644 tests/test-java16/src/test/resources/application-test.yaml
create mode 100644 tests/test-java16/src/test/resources/logback-test.xml
diff --git a/tests/pom.xml b/tests/pom.xml
new file mode 100644
index 000000000..2c79d71fa
--- /dev/null
+++ b/tests/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ org.avaje
+ java8-oss
+ 2.3
+
+
+ io.ebean
+ tests
+ 1.0
+ pom
+
+ tests
+ test modules
+
+
+ test-java16
+
+
+
+
diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml
new file mode 100644
index 000000000..21dfa7852
--- /dev/null
+++ b/tests/test-java16/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+
+
+
+
+
+ io.ebean
+ test-java16
+ 1.0
+
+
+ 3.8.1
+
+
+
+
+ io.ebean
+ ebean
+ 12.8.1-SNAPSHOT
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+
+ io.ebean
+ ebean-test
+ 12.8.1-SNAPSHOT
+ test
+
+
+
+ org.avaje.composite
+ junit
+ 5.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ 16
+ 16
+ 16
+
+
+ io.ebean
+ querybean-generator
+ 12.8.1-SNAPSHOT
+
+
+
+
+
+
+
+
+
diff --git a/tests/test-java16/src/main/java/org/example/records/BaseModel.java b/tests/test-java16/src/main/java/org/example/records/BaseModel.java
new file mode 100644
index 000000000..b5116e638
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/BaseModel.java
@@ -0,0 +1,35 @@
+package org.example.records;
+
+import io.ebean.Model;
+import io.ebean.annotation.Identity;
+
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Version;
+
+@Identity(start = 1000, cache = 100)
+@MappedSuperclass
+public class BaseModel extends Model {
+
+ @Id
+ long id;
+
+ @Version
+ long version;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/Course.java b/tests/test-java16/src/main/java/org/example/records/Course.java
new file mode 100644
index 000000000..19e8d09fe
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Course.java
@@ -0,0 +1,33 @@
+package org.example.records;
+
+import io.ebean.annotation.Length;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "course")
+public class Course extends BaseModel {
+
+ @Length(200)
+ final String name;
+
+ @Length(400)
+ String summary;
+
+ public Course(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java b/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java
new file mode 100644
index 000000000..dc95a8723
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java
@@ -0,0 +1,13 @@
+package org.example.records;
+
+import io.ebean.annotation.Identity;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Identity(start = 1000)
+@Entity
+@Table(name="course_rec")
+public record CourseRecordEntity(@Id long id, String name, String notes) {
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java b/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java
new file mode 100644
index 000000000..63e0c7d84
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java
@@ -0,0 +1,40 @@
+package org.example.records;
+
+import io.ebean.DB;
+import org.example.records.query.QCourseRecordEntity;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CourseRecordEntityTest {
+
+ @Test
+ void record_insert_withGivenIdValue() {
+ var course = new CourseRecordEntity(42, "SuppliedId", "Record");
+ DB.save(course);
+ assertThat(course.id()).isEqualTo(42);
+
+ var found = new QCourseRecordEntity()
+ .name.startsWith("SuppliedId")
+ .findOneOrEmpty();
+
+ assertThat(found).isPresent();
+ assertThat(found.get().id()).isEqualTo(42L);
+ assertThat(found.get().notes()).isEqualTo("Record");
+ }
+
+ @Test
+ void record_insert_usingGeneratedId() {
+ var course2 = new CourseRecordEntity(0, "Second", "Record with generated id");
+ DB.save(course2);
+ // as using @Identity(start = 1000)
+ assertThat(course2.id()).isEqualTo(1000);
+
+ var second = new QCourseRecordEntity()
+ .name.startsWith("Second")
+ .findOneOrEmpty();
+
+ assertThat(second).isPresent();
+ assertThat(second.get().notes()).isEqualTo("Record with generated id");
+ }
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java b/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java
new file mode 100644
index 000000000..e64c15b1c
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java
@@ -0,0 +1,59 @@
+package org.example.records;
+
+import io.ebean.DB;
+import org.example.records.query.QCourse;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.example.records.query.QCourse.Alias.id;
+import static org.example.records.query.QCourse.Alias.name;
+
+class DtoQueryUsingRecordsTest {
+
+ @Test
+ void dtoQuery_projectRecords() {
+
+ var course = new Course("Calculus");
+ course.setSummary("Something here");
+ course.save();
+
+ performOrmQuery();
+
+ records_via_SqlDtoQuery(course);
+ records_via_OrmQueryToDtoQuery(course);
+
+ course.delete();
+ }
+
+ private void performOrmQuery() {
+ List courses = new QCourse()
+ .name.startsWith("Calc")
+ .findList();
+
+ assertThat(courses).hasSize(1);
+ }
+
+ private void records_via_SqlDtoQuery(Course course) {
+ List records = DB.findDto(Foo.class, "select id, name from course where name like ?")
+ .setParameter("Calc%")
+ .findList();
+
+ assertThat(records).hasSize(1);
+ assertThat(records.get(0).name()).isEqualTo(course.getName());
+ }
+
+ private void records_via_OrmQueryToDtoQuery(Course course) {
+ List records2 = new QCourse()
+ .select(id, name )
+ .name.startsWith("Calc")
+ .asDto(Foo.class)
+ .findList();
+
+ assertThat(records2).hasSize(1);
+ assertThat(records2.get(0).name()).isEqualTo(course.getName());
+ }
+
+ public record Foo(long id, String name){}
+}
diff --git a/tests/test-java16/src/test/resources/application-test.yaml b/tests/test-java16/src/test/resources/application-test.yaml
new file mode 100644
index 000000000..029b11023
--- /dev/null
+++ b/tests/test-java16/src/test/resources/application-test.yaml
@@ -0,0 +1,6 @@
+ebean:
+ test:
+ platform: h2
+ ddlMode: dropCreate
+ dbName: foo
+
diff --git a/tests/test-java16/src/test/resources/logback-test.xml b/tests/test-java16/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..ce7ad5968
--- /dev/null
+++ b/tests/test-java16/src/test/resources/logback-test.xml
@@ -0,0 +1,22 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 548454c90d7d920efb6e9b217d084924b2f6b6f1 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Mon, 22 Mar 2021 23:57:14 +1300
Subject: [PATCH 04/52] #2203 - Support use of Java Record type with @Entity,
@Embeddable @IdClass
Add tests for embedded use of record type
---
.../java/org/example/records/Address.java | 7 +++
.../java/org/example/records/Contact.java | 58 +++++++++++++++++++
.../example/records/RecordAsEmbeddedTest.java | 48 +++++++++++++++
3 files changed, 113 insertions(+)
create mode 100644 tests/test-java16/src/main/java/org/example/records/Address.java
create mode 100644 tests/test-java16/src/main/java/org/example/records/Contact.java
create mode 100644 tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java
diff --git a/tests/test-java16/src/main/java/org/example/records/Address.java b/tests/test-java16/src/main/java/org/example/records/Address.java
new file mode 100644
index 000000000..2ac531402
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Address.java
@@ -0,0 +1,7 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+
+@Embeddable
+public record Address (String line1, String line2, String city) {
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/Contact.java b/tests/test-java16/src/main/java/org/example/records/Contact.java
new file mode 100644
index 000000000..871862f21
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Contact.java
@@ -0,0 +1,58 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+
+@Entity
+public class Contact extends Model {
+
+ @Id
+ private long id;
+
+ @Version
+ private long version;
+
+ private final String name;
+
+ @Embedded(prefix = "home_")
+ private Address homeAddress;
+
+ @Embedded(prefix = "work_")
+ private Address workAddress;
+
+ public Contact(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Address getHomeAddress() {
+ return homeAddress;
+ }
+
+ public void setHomeAddress(Address homeAddress) {
+ this.homeAddress = homeAddress;
+ }
+
+ public Address getWorkAddress() {
+ return workAddress;
+ }
+
+ public void setWorkAddress(Address workAddress) {
+ this.workAddress = workAddress;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java b/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java
new file mode 100644
index 000000000..271481468
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java
@@ -0,0 +1,48 @@
+package org.example.records;
+
+import org.example.records.query.QContact;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.example.records.query.QContact.Alias.homeAddress;
+import static org.example.records.query.QContact.Alias.name;
+
+public class RecordAsEmbeddedTest {
+
+ @Test
+ void insert_query() {
+
+ var contact = new Contact("Rob");
+ contact.setWorkAddress(new Address("45 work", "workling", "wo"));
+ contact.setHomeAddress(new Address("94 home st", "homeline", "wo"));
+
+ contact.save();
+
+ Contact found = new QContact()
+ .id.eq(contact.getId())
+ .findOne();
+
+ assertThat(found.getWorkAddress().toString()).isEqualTo("Address[line1=45 work, line2=workling, city=wo]");
+ assertThat(found.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ Contact foundPartial = new QContact()
+ .select(name, homeAddress)
+ .id.eq(contact.getId())
+ .findOne();
+
+ // invoke lazy loading on getWorkAddress
+ assertThat(foundPartial.getWorkAddress().toString()).isEqualTo("Address[line1=45 work, line2=workling, city=wo]");
+ assertThat(foundPartial.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ Contact foundNoLazyLoading = new QContact()
+ .select(name, homeAddress)
+ .setDisableLazyLoading(true)
+ .id.eq(contact.getId())
+ .findOne();
+
+ // no lazy loading on getWorkAddress this time
+ assertThat(foundNoLazyLoading.getWorkAddress()).isNull();
+ assertThat(foundNoLazyLoading.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ }
+}
From 53c903fa4815ddda4aa07079790a247cbab9cecd Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Tue, 23 Mar 2021 00:10:28 +1300
Subject: [PATCH 05/52] #2203 - Support use of Java Record type with @Entity,
@Embeddable @IdClass
Add tests for EmbeddedId use of record type
---
.../java/org/example/records/UserRole.java | 36 ++++++++++++++++++
.../java/org/example/records/UserRoleId.java | 7 ++++
.../org/example/records/UserRoleTest.java | 38 +++++++++++++++++++
3 files changed, 81 insertions(+)
create mode 100644 tests/test-java16/src/main/java/org/example/records/UserRole.java
create mode 100644 tests/test-java16/src/main/java/org/example/records/UserRoleId.java
create mode 100644 tests/test-java16/src/test/java/org/example/records/UserRoleTest.java
diff --git a/tests/test-java16/src/main/java/org/example/records/UserRole.java b/tests/test-java16/src/main/java/org/example/records/UserRole.java
new file mode 100644
index 000000000..682e61f4a
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserRole.java
@@ -0,0 +1,36 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Version;
+
+@Entity
+public class UserRole extends Model {
+
+ @EmbeddedId
+ final UserRoleId id;
+
+ String note;
+
+ @Version
+ long version;
+
+ public UserRole(UserRoleId id, String note) {
+ this.id = id;
+ this.note = note;
+ }
+
+ public UserRoleId getId() {
+ return id;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserRoleId.java b/tests/test-java16/src/main/java/org/example/records/UserRoleId.java
new file mode 100644
index 000000000..a0807de0a
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserRoleId.java
@@ -0,0 +1,7 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+
+@Embeddable
+public record UserRoleId(Integer userId, String roleId) {
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java b/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java
new file mode 100644
index 000000000..1b4231b58
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java
@@ -0,0 +1,38 @@
+package org.example.records;
+
+import org.example.records.query.QUserRole;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UserRoleTest {
+
+ final UserRoleId id = new UserRoleId(42, "R7");
+
+ @Test
+ void id_equals() {
+ assertThat(id).isEqualTo(new UserRoleId(42, "R7"));
+ }
+
+ @Test
+ void id_notEquals() {
+ assertThat(id).isNotEqualTo(new UserRoleId(43, "R7"));
+ assertThat(id).isNotEqualTo(new UserRoleId(42, "R8"));
+ }
+
+ @Test
+ void insert_query() {
+
+ var userRole = new UserRole(id, "hello");
+ userRole.save();
+
+ UserRole found = new QUserRole()
+ .id.eq(new UserRoleId(42, "R7"))
+ .findOne();
+
+ UserRoleId id1 = found.getId();
+ assertThat(id1).isEqualTo(id);
+ assertThat(id1.userId()).isEqualTo(42);
+ assertThat(id1.roleId()).isEqualTo("R7");
+ }
+}
From 09a887aeb7df25466286bf7a8eaf7f388b8187d8 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Tue, 23 Mar 2021 00:19:52 +1300
Subject: [PATCH 06/52] #2203 - Support use of Java Record type with @Entity,
@Embeddable @IdClass
Add tests for IdClass use of record type
---
.../java/org/example/records/UserSite.java | 51 +++++++++++++++++++
.../java/org/example/records/UserSiteId.java | 8 +++
.../example/records/RecordIdClassTest.java | 34 +++++++++++++
3 files changed, 93 insertions(+)
create mode 100644 tests/test-java16/src/main/java/org/example/records/UserSite.java
create mode 100644 tests/test-java16/src/main/java/org/example/records/UserSiteId.java
create mode 100644 tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java
diff --git a/tests/test-java16/src/main/java/org/example/records/UserSite.java b/tests/test-java16/src/main/java/org/example/records/UserSite.java
new file mode 100644
index 000000000..a8cf46449
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserSite.java
@@ -0,0 +1,51 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.*;
+import java.util.UUID;
+
+@IdClass(UserSiteId.class)
+@Entity
+public class UserSite extends Model {
+
+ @Id
+ final UUID userId;
+
+ @Id
+ final UUID siteId;
+
+ String note;
+
+ @Version
+ long version;
+
+ public UserSite(UUID userId, UUID siteId) {
+ this.userId = userId;
+ this.siteId = siteId;
+ }
+
+ public UUID getUserId() {
+ return userId;
+ }
+
+ public UUID getSiteId() {
+ return siteId;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserSiteId.java b/tests/test-java16/src/main/java/org/example/records/UserSiteId.java
new file mode 100644
index 000000000..36c4dd599
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserSiteId.java
@@ -0,0 +1,8 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+import java.util.UUID;
+
+@Embeddable
+public record UserSiteId(UUID userId, UUID siteId) {
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java b/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java
new file mode 100644
index 000000000..47008c544
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java
@@ -0,0 +1,34 @@
+package org.example.records;
+
+import org.example.records.query.QUserSite;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordIdClassTest {
+
+ @Test
+ void insert_query() {
+
+ UUID userId = UUID.randomUUID();
+ UUID siteId = UUID.randomUUID();
+
+ var userSite = new UserSite(userId, siteId);
+ userSite.setNote("HelloIdClass");
+ userSite.save();
+
+ var id = new UserSiteId(userId, siteId);
+
+ UserSite found = new QUserSite()
+ .setId(id)
+ .findOne();
+
+ assertThat(found.getUserId()).isEqualTo(userId);
+ assertThat(found.getSiteId()).isEqualTo(siteId);
+ assertThat(found.getNote()).isEqualTo("HelloIdClass");
+
+
+ }
+}
From b5cef354ad39ad70b07dca042e2f9f26b829ad02 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Tue, 23 Mar 2021 14:53:30 +1300
Subject: [PATCH 07/52] Refactor simplify
LoadContext.getSecondaryQueriesMinBatchSize()
No functional change here. Just move the constant into DLoadContext.
---
.../src/main/java/io/ebeaninternal/api/LoadContext.java | 2 +-
.../java/io/ebeaninternal/server/core/OrmQueryRequest.java | 4 ++--
.../io/ebeaninternal/server/loadcontext/DLoadContext.java | 4 ++--
.../main/java/io/ebeaninternal/server/query/CQueryEngine.java | 4 +---
4 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
index 4ba56604f..51f02826c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
@@ -16,7 +16,7 @@ public interface LoadContext {
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
- int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
+ int getSecondaryQueriesMinBatchSize();
/**
* Execute any secondary (+query) queries if there are any defined.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index 9b0e1235e..3ec5deac7 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -173,8 +173,8 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
* iteration is fine.
*
*/
- public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
- return loadContext.getSecondaryQueriesMinBatchSize(defaultQueryBatch);
+ public int getSecondaryQueriesMinBatchSize() {
+ return loadContext.getSecondaryQueriesMinBatchSize();
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
index b5aa6f873..912b8fc0c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
@@ -182,7 +182,7 @@ public class DLoadContext implements LoadContext {
* Return the minimum batch size when using QueryIterator with query joins.
*/
@Override
- public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
+ public int getSecondaryQueriesMinBatchSize() {
if (secQuery == null) {
return -1;
}
@@ -190,7 +190,7 @@ public class DLoadContext implements LoadContext {
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getBatchSize();
if (batchSize == 0) {
- batchSize = defaultQueryBatch;
+ batchSize = 100;
}
maxBatch = Math.max(maxBatch, batchSize);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
index 945b343ca..6e4804ca6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
@@ -37,8 +37,6 @@ public class CQueryEngine {
private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class);
- private static final int defaultSecondaryQueryBatchSize = 100;
-
private static final String T0 = "t0";
private final int defaultFetchSizeFindList;
@@ -214,7 +212,7 @@ public class CQueryEngine {
logSql(cquery);
}
// first check batch sizes set on query joins
- int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);
+ int iterateBufferSize = request.getSecondaryQueriesMinBatchSize();
if (iterateBufferSize < 1) {
// not set on query joins so check if batch size set on query itself
int queryBatch = request.getQuery().getLazyLoadBatchSize();
From 562d175de1f4435d623f2c4abeec82b38b609f41 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Tue, 23 Mar 2021 23:17:07 +1300
Subject: [PATCH 08/52] With FetchConfig.ofLazy() honor
query.setLazyLoadBatchSize()
Use 0 for default lazy batch such that it honors a value set via query.setLazyLoadBatchSize()
---
.../src/main/java/io/ebean/FetchConfig.java | 21 ++++------
.../server/loadcontext/DLoadBaseContext.java | 8 +---
.../server/loadcontext/DLoadBeanContext.java | 4 +-
.../server/loadcontext/DLoadContext.java | 42 ++++++++++---------
.../server/loadcontext/DLoadManyContext.java | 7 +---
.../test/java/io/ebean/FetchConfigTest.java | 3 +-
.../server/grammer/ParseFetchConfigTest.java | 2 +-
.../org/tests/query/TestQueryFindEach.java | 38 ++++++++++++-----
8 files changed, 68 insertions(+), 57 deletions(-)
diff --git a/ebean-api/src/main/java/io/ebean/FetchConfig.java b/ebean-api/src/main/java/io/ebean/FetchConfig.java
index 64f4c3daa..e2d4fbe35 100644
--- a/ebean-api/src/main/java/io/ebean/FetchConfig.java
+++ b/ebean-api/src/main/java/io/ebean/FetchConfig.java
@@ -46,7 +46,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
- *
+ *
* Construct using default JOIN mode.
*/
@Deprecated
@@ -89,7 +89,7 @@ public class FetchConfig implements Serializable {
* Return FetchConfig to lazily load the relationship.
*/
public static FetchConfig ofLazy() {
- return new FetchConfig(LAZY_MODE, 10);
+ return new FetchConfig(LAZY_MODE, 0);
}
/**
@@ -110,8 +110,8 @@ public class FetchConfig implements Serializable {
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
*/
private FetchConfig mutate(int mode, int batchSize) {
- if (batchSize < 1) {
- throw new IllegalArgumentException("batch size "+batchSize+" must be > 0");
+ if (batchSize < 0) {
+ throw new IllegalArgumentException("batch size " + batchSize + " must be > 0");
}
this.mode = mode;
this.batchSize = batchSize;
@@ -124,7 +124,7 @@ public class FetchConfig implements Serializable {
*/
@Deprecated
public FetchConfig lazy() {
- return mutate(LAZY_MODE, 10);
+ return mutate(LAZY_MODE, 0);
}
/**
@@ -137,7 +137,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery().
- *
+ *
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
*
@@ -150,17 +150,15 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
- *
+ *
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
*
* The queryBatchSize is the number of parent id's that this separate query
* will load per batch.
- *
*
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
* is also used.
- *
*
* @param batchSize the batch size used to load beans on this path
*/
@@ -171,13 +169,12 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
- *
+ *
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
*
* If there are more parent beans than the batch size then they will not be
* loaded eagerly but instead use lazy loading.
- *
*
* @param batchSize the number of parent beans this path is populated for
*/
@@ -188,7 +185,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofCache().
- *
+ *
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
index 1be4717e9..24233cf87 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
@@ -33,7 +33,7 @@ abstract class DLoadBaseContext {
final boolean queryFetch;
- DLoadBaseContext(DLoadContext parent, BeanDescriptor> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
+ DLoadBaseContext(DLoadContext parent, BeanDescriptor> desc, String path, OrmQueryProperties queryProps) {
this.parent = parent;
this.serverName = parent.getEbeanServer().getName();
this.desc = desc;
@@ -42,11 +42,7 @@ abstract class DLoadBaseContext {
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
this.objectGraphNode = parent.getObjectGraphNode(path);
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
- this.batchSize = initBatchSize(defaultBatchSize, queryProps);
- }
-
- private int initBatchSize(int batchSize, OrmQueryProperties queryProps) {
- return queryProps == null ? batchSize : queryProps.getBatchSize();
+ this.batchSize = parent.batchSize(queryProps);
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
index d24a24532..25fffe5b7 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
@@ -31,8 +31,8 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
private LoadBuffer currentBuffer;
- DLoadBeanContext(DLoadContext parent, BeanDescriptor> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
- super(parent, desc, path, defaultBatchSize, queryProps);
+ DLoadBeanContext(DLoadContext parent, BeanDescriptor> desc, String path, OrmQueryProperties queryProps) {
+ super(parent, desc, path, queryProps);
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(batchSize);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
index 912b8fc0c..060633073 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
@@ -90,7 +90,7 @@ public class DLoadContext implements LoadContext {
this.planLabel = null;
this.profileLocation = null;
this.profilingListener = null;
- this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
+ this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
}
private ObjectGraphOrigin initOrigin() {
@@ -128,7 +128,7 @@ public class DLoadContext implements LoadContext {
}
// initialise rootBeanContext after origin and relativePath have been set
- this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
+ this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
registerSecondaryQueries(secondaryQueries);
}
@@ -287,51 +287,55 @@ public class DLoadContext implements LoadContext {
getManyContext(path, many).register(bc);
}
+ int batchSize(OrmQueryProperties props) {
+ if (props == null) {
+ return defaultBatchSize;
+ }
+ int batchSize = props.getBatchSize();
+ return batchSize == 0 ? defaultBatchSize : batchSize;
+ }
+
DLoadBeanContext getBeanContext(String path) {
if (path == null) {
return rootBeanContext;
}
- return beanMap.computeIfAbsent(path, p -> createBeanContext(p, defaultBatchSize, null));
+ return beanMap.computeIfAbsent(path, p -> createBeanContext(p, null));
}
DLoadBeanContext getBeanContextWithInherit(String path, BeanPropertyAssocOne> property) {
String key = path + ":" + property.getTargetDescriptor().getName();
- return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, defaultBatchSize, null));
+ return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, null));
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
- int batchSize = props.getBatchSize();
- if (batchSize == 0) {
- batchSize = defaultBatchSize;
- }
String path = props.getPath();
if (many) {
- manyMap.put(path, createManyContext(path, batchSize, props));
+ manyMap.put(path, createManyContext(path, props));
} else {
- beanMap.put(path, createBeanContext(path, batchSize, props));
+ beanMap.put(path, createBeanContext(path, props));
}
}
DLoadManyContext getManyContext(String path, BeanPropertyAssocMany> many) {
- return manyMap.computeIfAbsent(path, p -> createManyContext(p, many, defaultBatchSize));
+ return manyMap.computeIfAbsent(path, p -> createManyContext(p, many));
}
- private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany> many, int batchSize) {
- return new DLoadManyContext(this, many, path, batchSize, null);
+ private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany> many) {
+ return new DLoadManyContext(this, many, path, null);
}
- private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) {
+ private DLoadManyContext createManyContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssocMany> p = (BeanPropertyAssocMany>) getBeanProperty(rootDescriptor, path);
- return new DLoadManyContext(this, p, path, batchSize, queryProps);
+ return new DLoadManyContext(this, p, path, queryProps);
}
- private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) {
+ private DLoadBeanContext createBeanContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssoc> p = (BeanPropertyAssoc>) getBeanProperty(rootDescriptor, path);
- return new DLoadBeanContext(this, p.getTargetDescriptor(), path, batchSize, queryProps);
+ return new DLoadBeanContext(this, p.getTargetDescriptor(), path, queryProps);
}
- private DLoadBeanContext createBeanContext(BeanPropertyAssoc> property, String path, int batchSize, OrmQueryProperties queryProps) {
- return new DLoadBeanContext(this, property.getTargetDescriptor(), path, batchSize, queryProps);
+ private DLoadBeanContext createBeanContext(BeanPropertyAssoc> property, String path, OrmQueryProperties queryProps) {
+ return new DLoadBeanContext(this, property.getTargetDescriptor(), path, queryProps);
}
private BeanProperty getBeanProperty(BeanDescriptor> desc, String path) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
index 7a51127d8..5035f0802 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
@@ -31,11 +31,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
private LoadBuffer currentBuffer;
- DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany> property,
- String path, int defaultBatchSize, OrmQueryProperties queryProps) {
-
- super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
-
+ DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany> property, String path, OrmQueryProperties queryProps) {
+ super(parent, property.getBeanDescriptor(), path, queryProps);
this.property = property;
this.docStoreMapped = property.isTargetDocStoreMapped();
// bufferList only required when using query joins (queryFetch)
diff --git a/ebean-core/src/test/java/io/ebean/FetchConfigTest.java b/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
index abd09d692..f85808f6c 100644
--- a/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
+++ b/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
@@ -1,6 +1,5 @@
package io.ebean;
-import io.ebean.FetchConfig;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -10,7 +9,7 @@ public class FetchConfigTest {
@Test
public void testLazy() {
FetchConfig config = new FetchConfig().lazy();
- assertThat(config.getBatchSize()).isEqualTo(10);
+ assertThat(config.getBatchSize()).isEqualTo(0);
}
@Test
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
index a940cd26b..c4976f56d 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
@@ -19,7 +19,7 @@ public class ParseFetchConfigTest {
@Test
public void parseLazy() {
FetchConfig lazy = ParseFetchConfig.parse("lazy");
- assertThat(lazy.getBatchSize()).isEqualTo(10);
+ assertThat(lazy.getBatchSize()).isEqualTo(0);
}
@Test
diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java b/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
index 1a2c16bab..b37f67279 100644
--- a/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
+++ b/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
@@ -169,22 +169,40 @@ public class TestQueryFindEach extends BaseTestCase {
DB.find(OmBasicParent.class).delete();
insertData();
+ test_setLazyLoadBatchSize_withFetchLazy();
+
LoggedSqlCollector.start();
- try (final Transaction transaction = DB.beginTransaction()) {
- // DB.find(OmBasicParent.class).findList();
- DB.find(OmBasicChild.class)
- .setLazyLoadBatchSize(100)
- //.fetchQuery("parent","name")
- //.fetch("parent","name")
- .findEach(child -> {
- assertNotNull(child.getParent().getName());
- });
- }
+
+ DB.find(OmBasicChild.class)
+ .setLazyLoadBatchSize(100)
+ .findEach(child -> {
+ assertNotNull(child.getParent().getName());
+ });
final List sql = LoggedSqlCollector.stop();
assertThat(sql.size()).isLessThan(50);
}
+ private void test_setLazyLoadBatchSize_withFetchLazy() {
+
+ LoggedSqlCollector.start();
+
+ DB.find(OmBasicParent.class)
+ .setLazyLoadBatchSize(5)
+ .fetchLazy("children")
+ .setMaxRows(50)
+ .findEach(it -> {
+ it.getChildren().size();
+ });
+
+ final List sql = LoggedSqlCollector.stop();
+ assertThat(sql).hasSize(11);
+ assertThat(sql.get(0)).contains(" from om_basic_parent ");
+ for (int i = 1; i < 11; i++) {
+ assertThat(sql.get(i)).contains(" --bind(Array[5]");
+ }
+ }
+
@Transactional(batchSize = 40)
private void insertData() {
for (int i = 0; i < 150; i++) {
From abfa841ed787f332b637b9d7c16081b0bd92783f Mon Sep 17 00:00:00 2001
From: rob bygrave
Date: Tue, 23 Mar 2021 23:51:07 +1300
Subject: [PATCH 09/52] #2205 - [oracle] Incorrect DDL generated for alter
table add column
---
.../ddlgeneration/platform/OracleDdl.java | 1 +
.../platform/PlatformDdl_AlterColumnTest.java | 20 +++++++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
index ea7d50d62..740870b09 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
@@ -15,6 +15,7 @@ public class OracleDdl extends PlatformDdl {
this.dropConstraintIfExists = "drop constraint";
this.dropIndexIfExists = "drop index ";
this.dropTableCascade = " cascade constraints purge";
+ this.addColumn = "add";
this.alterColumn = "modify";
this.columnSetNotnull = "not null";
this.columnSetNull = "null";
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
index 3a0a8d727..a696c4e62 100644
--- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
@@ -9,11 +9,15 @@ import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
+import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.PlatformDdlBuilder;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
+import io.ebeaninternal.dbmigration.migration.Column;
import org.junit.Test;
+import java.io.IOException;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -238,6 +242,22 @@ public class PlatformDdl_AlterColumnTest {
assertTrue(exceptionCaught);
}
+ @Test
+ public void oracle_alterTableAddColumn() throws IOException {
+ DdlWrite write = new DdlWrite();
+ oraDdl.alterTableAddColumn(write.apply(), "my_table", simpleColumn(), false, "1");
+ assertThat(write.apply().getBuffer()).isEqualTo("alter table my_table add my_column int default 1 not null;\n");
+ }
+
+ private Column simpleColumn() {
+ Column column = new Column();
+ column.setName("my_column");
+ column.setType("int");
+ column.setNotnull(true);
+ column.setDefaultValue("1");
+ return column;
+ }
+
@Test
public void useIdentityType_h2() {
assertEquals(h2Ddl.useIdentityType(null), IdType.IDENTITY);
From f341ff0167cd6838b27039267c05558952d03723 Mon Sep 17 00:00:00 2001
From: rob bygrave
Date: Tue, 23 Mar 2021 23:58:28 +1300
Subject: [PATCH 10/52] #2205 - [oracle] Incorrect DDL generated for alter
table add column
---
.../platform/BaseTableDdlTest.java | 2 +-
.../dbmigration/migrationtest/oracle/1.1.sql | 28 +++++++++----------
.../dbmigration/migrationtest/oracle/1.3.sql | 10 +++----
3 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
index c0bbd4df6..c32d6ad80 100644
--- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
@@ -61,7 +61,7 @@ public class BaseTableDdlTest {
ddlGen.alterTableAddColumn(write.apply(), "mytable", column, false, false);
String ddl = write.apply().getBuffer();
- assertThat(ddl).contains("alter table mytable add column col_name varchar2(20)");
+ assertThat(ddl).contains("alter table mytable add col_name varchar2(20)");
}
@Test
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
index a7ebf50ef..7e6c98a90 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
@@ -17,11 +17,11 @@ create table migtest_mtm_m_migtest_mtm_c (
constraint pk_migtest_mtm_m_migtest_mtm_c primary key (migtest_mtm_m_id,migtest_mtm_c_id)
);
-alter table migtest_ckey_detail add column one_key number(10);
-alter table migtest_ckey_detail add column two_key varchar2(127);
+alter table migtest_ckey_detail add one_key number(10);
+alter table migtest_ckey_detail add two_key varchar2(127);
alter table migtest_ckey_detail add constraint fk_migtest_ckey_detail_parent foreign key (one_key,two_key) references migtest_ckey_parent (one_key,two_key);
-alter table migtest_ckey_parent add column assoc_id number(10);
+alter table migtest_ckey_parent add assoc_id number(10);
alter table migtest_fk_cascade drop constraint fk_migtest_fk_cascade_one_id;
alter table migtest_fk_cascade add constraint fk_migtest_fk_cascade_one_id foreign key (one_id) references migtest_fk_cascade_one (id);
@@ -46,14 +46,14 @@ alter table migtest_e_basic modify status2 null;
insert into migtest_e_user (id) select distinct user_id from migtest_e_basic;
alter table migtest_e_basic add constraint fk_migtest_e_basic_user_id foreign key (user_id) references migtest_e_user (id);
alter table migtest_e_basic modify user_id null;
-alter table migtest_e_basic add column new_string_field varchar2(255) default 'foo''bar' not null;
-alter table migtest_e_basic add column new_boolean_field number(1) default 1 not null;
+alter table migtest_e_basic add new_string_field varchar2(255) default 'foo''bar' not null;
+alter table migtest_e_basic add new_boolean_field number(1) default 1 not null;
update migtest_e_basic set new_boolean_field = old_boolean;
-alter table migtest_e_basic add column new_boolean_field2 number(1) default 1 not null;
-alter table migtest_e_basic add column progress number(10) default 0 not null;
+alter table migtest_e_basic add new_boolean_field2 number(1) default 1 not null;
+alter table migtest_e_basic add progress number(10) default 0 not null;
alter table migtest_e_basic add constraint ck_migtest_e_basic_progress check ( progress in (0,1,2));
-alter table migtest_e_basic add column new_integer number(10) default 42 not null;
+alter table migtest_e_basic add new_integer number(10) default 42 not null;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_indextest2;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_indextest6;
@@ -70,12 +70,12 @@ comment on table migtest_e_history is 'We have history now';
update migtest_e_history2 set test_string = 'unknown' where test_string is null;
alter table migtest_e_history2 modify test_string default 'unknown';
alter table migtest_e_history2 modify test_string not null;
-alter table migtest_e_history2 add column test_string2 varchar2(255);
-alter table migtest_e_history2 add column test_string3 varchar2(255) default 'unknown' not null;
-alter table migtest_e_history2 add column new_column varchar2(20);
+alter table migtest_e_history2 add test_string2 varchar2(255);
+alter table migtest_e_history2 add test_string3 varchar2(255) default 'unknown' not null;
+alter table migtest_e_history2 add new_column varchar2(20);
alter table migtest_e_history4 modify test_number number(19);
-alter table migtest_e_history5 add column test_boolean number(1) default 0 not null;
+alter table migtest_e_history5 add test_boolean number(1) default 0 not null;
-- NOTE: table has @History - special migration may be necessary
@@ -83,9 +83,9 @@ update migtest_e_history6 set test_number1 = 42 where test_number1 is null;
alter table migtest_e_history6 modify test_number1 default 42;
alter table migtest_e_history6 modify test_number1 not null;
alter table migtest_e_history6 modify test_number2 null;
-alter table migtest_e_softdelete add column deleted number(1) default 0 not null;
+alter table migtest_e_softdelete add deleted number(1) default 0 not null;
-alter table migtest_oto_child add column master_id number(19);
+alter table migtest_oto_child add master_id number(19);
create index ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
create index ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
index 47a095e4e..ceafdbcff 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
@@ -31,9 +31,9 @@ update migtest_e_basic set user_id = 23 where user_id is null;
alter table migtest_e_basic drop constraint fk_migtest_e_basic_user_id;
alter table migtest_e_basic modify user_id default 23;
alter table migtest_e_basic modify user_id not null;
-alter table migtest_e_basic add column old_boolean number(1) default 0 not null;
-alter table migtest_e_basic add column old_boolean2 number(1);
-alter table migtest_e_basic add column eref_id number(10);
+alter table migtest_e_basic add old_boolean number(1) default 0 not null;
+alter table migtest_e_basic add old_boolean2 number(1);
+alter table migtest_e_basic add eref_id number(10);
alter table migtest_e_basic drop constraint uq_mgtst__bsc_stts_ndxtst1;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_name;
@@ -47,8 +47,8 @@ comment on column migtest_e_history.test_string is '';
comment on table migtest_e_history is '';
alter table migtest_e_history2 modify test_string drop default;
alter table migtest_e_history2 modify test_string null;
-alter table migtest_e_history2 add column obsolete_string1 varchar2(255);
-alter table migtest_e_history2 add column obsolete_string2 varchar2(255);
+alter table migtest_e_history2 add obsolete_string1 varchar2(255);
+alter table migtest_e_history2 add obsolete_string2 varchar2(255);
alter table migtest_e_history4 modify test_number number(10);
alter table migtest_e_history6 modify test_number1 drop default;
From 8992980689d2fe7936acfe4d0fbeadf72bb676a8 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 25 Mar 2021 16:45:44 +1300
Subject: [PATCH 11/52] #2206 - Postgres DDL generation - for create index use
"if not exists" clause
---
.../ddlgeneration/platform/PlatformDdl.java | 5 +-
.../ddlgeneration/platform/PostgresDdl.java | 1 +
.../platform/WriteCreateIndex.java | 13 +++
.../platform/PlatformDdl_CreateIndexTest.java | 98 +++++++++++++++++++
4 files changed, 116 insertions(+), 1 deletion(-)
create mode 100644 ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
index 648cfac83..59cf38e0d 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
@@ -3,7 +3,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DbConstraintNaming;
-import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbIdentity;
@@ -113,6 +112,7 @@ public class PlatformDdl {
protected String uniqueIndex = "unique";
protected String indexConcurrent = "";
+ protected String createIndexIfNotExists = "";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
@@ -414,6 +414,9 @@ public class PlatformDdl {
if (create.isConcurrent()) {
buffer.append(indexConcurrent);
}
+ if (create.isNotExistsCheck()) {
+ buffer.append(createIndexIfNotExists);
+ }
buffer.append(maxConstraintName(create.getIndexName())).append(" on ").append(create.getTableName());
appendColumns(create.getColumns(), buffer);
return buffer.toString();
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
index daeaf57fb..807bec456 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
@@ -18,6 +18,7 @@ public class PostgresDdl extends PlatformDdl {
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
+ this.createIndexIfNotExists = "if not exists ";
this.columnSetNull = "drop not null";
this.addForeignKeySkipCheck = " not valid";
this.indexConcurrent = "concurrently ";
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
index c743c979c..fbb7b72ce 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
@@ -12,7 +12,11 @@ class WriteCreateIndex {
private final boolean unique;
private final boolean concurrent;
private final String definition;
+ private final boolean notExistsCheck;
+ /**
+ * Create index for foreign key.
+ */
WriteCreateIndex(String indexName, String tableName, String[] columns, boolean unique) {
this.indexName = indexName;
this.tableName = tableName;
@@ -20,8 +24,12 @@ class WriteCreateIndex {
this.unique = unique;
this.concurrent = false;
this.definition = null;
+ this.notExistsCheck = false;
}
+ /**
+ * Create non-foreign key index.
+ */
public WriteCreateIndex(CreateIndex index) {
this.indexName = index.getIndexName();
this.tableName = index.getTableName();
@@ -29,6 +37,7 @@ class WriteCreateIndex {
this.unique = Boolean.TRUE.equals(index.isUnique());
this.concurrent = Boolean.TRUE.equals(index.isConcurrent());
this.definition = index.getDefinition();
+ this.notExistsCheck = true;
}
public String getIndexName() {
@@ -58,4 +67,8 @@ class WriteCreateIndex {
public boolean useDefinition() {
return definition != null && !definition.isEmpty();
}
+
+ public boolean isNotExistsCheck() {
+ return notExistsCheck;
+ }
}
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java
new file mode 100644
index 000000000..d7d33feae
--- /dev/null
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java
@@ -0,0 +1,98 @@
+package io.ebeaninternal.dbmigration.ddlgeneration.platform;
+
+import io.ebean.DB;
+import io.ebean.config.DatabaseConfig;
+import io.ebean.config.dbplatform.h2.H2Platform;
+import io.ebean.config.dbplatform.hana.HanaPlatform;
+import io.ebean.config.dbplatform.mysql.MySqlPlatform;
+import io.ebean.config.dbplatform.oracle.OraclePlatform;
+import io.ebean.config.dbplatform.postgres.PostgresPlatform;
+import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
+import io.ebeaninternal.dbmigration.ddlgeneration.PlatformDdlBuilder;
+import io.ebeaninternal.dbmigration.migration.CreateIndex;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class PlatformDdl_CreateIndexTest {
+
+ private final PlatformDdl h2Ddl = PlatformDdlBuilder.create(new H2Platform());
+ private final PlatformDdl pgDdl = PlatformDdlBuilder.create(new PostgresPlatform());
+ private final PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
+ private final PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
+ private final PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
+ private final PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
+
+ {
+ DatabaseConfig config = DB.getDefault().getPluginApi().getServerConfig();
+ h2Ddl.configure(config);
+ pgDdl.configure(config);
+ mysqlDdl.configure(config);
+ oraDdl.configure(config);
+ sqlServerDdl.configure(config);
+ hanaDdl.configure(config);
+ }
+
+ WriteCreateIndex writeCreateIndex() {
+
+ return writeCreateIndex(true, true);
+ }
+
+ WriteCreateIndex writeCreateIndex(boolean unique, boolean concurrent) {
+ CreateIndex createIndex = new CreateIndex();
+ createIndex.setIndexName("ix_mytab_acol");
+ createIndex.setTableName("mytab");
+ createIndex.setColumns("acol");
+ createIndex.setUnique(unique);
+ createIndex.setConcurrent(concurrent);
+ return new WriteCreateIndex(createIndex);
+ }
+
+ WriteCreateIndex fkeyCreateIndex(boolean unique) {
+ return new WriteCreateIndex("ix_mytab_acol", "mytab", new String[]{"acol"}, unique);
+ }
+
+
+ @Test
+ public void createUniqueIndex() {
+
+ WriteCreateIndex createIndex = writeCreateIndex();
+
+ String sql = h2Ddl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(createIndex);
+ assertEquals("create unique index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = mysqlDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = sqlServerDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = oraDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = hanaDdl.createIndex(createIndex);
+ assertThat(sql).isEqualTo("-- explicit index \"ix_mytab_acol\" for single column \"acol\" of table \"mytab\" is not necessary");
+ }
+
+ @Test
+ public void postgres_createIndex() {
+
+ String sql = pgDdl.createIndex(writeCreateIndex(true, true));
+ assertEquals("create unique index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(false, false));
+ assertEquals("create index if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(true, false));
+ assertEquals("create unique index if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(false, true));
+ assertEquals("create index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ }
+
+ @Test
+ public void postgres_fkeyCreateIndex() {
+ String sql = pgDdl.createIndex(fkeyCreateIndex(true));
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+
+ sql = pgDdl.createIndex(fkeyCreateIndex(false));
+ assertEquals("create index ix_mytab_acol on mytab (acol)", sql);
+ }
+
+}
From 57736185d0489b41e4dfdff4898e0ab9f950db2e Mon Sep 17 00:00:00 2001
From: rob bygrave
Date: Thu, 25 Mar 2021 23:13:15 +1300
Subject: [PATCH 12/52] #2206 - Postgres DDL generation - for create index use
"if not exists" clause
---
.../migrationtest/postgres/1.0__initial.sql | 8 ++++----
.../dbmigration/migrationtest/postgres/1.1.sql | 6 +++---
.../dbmigration/migrationtest/postgres/1.3.sql | 10 +++++-----
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
index 5229ed032..af08de6d3 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
@@ -161,11 +161,11 @@ create table migtest_oto_master (
constraint pk_migtest_oto_master primary key (id)
);
-create index ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
-create index ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
+create index if not exists ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
+create index if not exists ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
create index idxd_migtest_0 on migtest_oto_child using hash (upper(name)) where upper(name) = 'JIM';
-create index concurrently ix_migtest_oto_child_lowername_id on migtest_oto_child (lower(name),id);
-create index ix_migtest_oto_child_lowername on migtest_oto_child (lower(name));
+create index concurrently if not exists ix_migtest_oto_child_lowername_id on migtest_oto_child (lower(name),id);
+create index if not exists ix_migtest_oto_child_lowername on migtest_oto_child (lower(name));
create index ix_migtest_fk_cascade_one_id on migtest_fk_cascade (one_id);
alter table migtest_fk_cascade add constraint fk_migtest_fk_cascade_one_id foreign key (one_id) references migtest_fk_cascade_one (id) on delete cascade on update restrict;
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
index 6c97d734e..1c0ef5385 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
@@ -100,9 +100,9 @@ alter table migtest_e_softdelete add column deleted boolean default false not nu
alter table migtest_oto_child add column master_id bigint;
-create index ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
-create index ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
-create index ix_migtest_oto_child_name on migtest_oto_child (name);
+create index if not exists ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
+create index if not exists ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
+create index if not exists ix_migtest_oto_child_name on migtest_oto_child (name);
drop index if exists ix_migtest_e_basic_indextest1;
drop index if exists ix_migtest_e_basic_indextest5;
drop index if exists idxd_migtest_0;
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
index a57b95ae2..dbcb50aac 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
@@ -66,11 +66,11 @@ alter table migtest_e_history6 alter column test_number1 drop not null;
update migtest_e_history6 set test_number2 = 7 where test_number2 is null;
alter table migtest_e_history6 alter column test_number2 set default 7;
alter table migtest_e_history6 alter column test_number2 set not null;
-create index ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
-create index ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
-create index ix_m12_otoc71 on migtest_oto_child (name);
-create unique index uq_m12_otoc71 on migtest_oto_child (lower(name));
-create unique index ix_migtest_oto_master_lowername on migtest_oto_master (lower(name));
+create index if not exists ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
+create index if not exists ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
+create index if not exists ix_m12_otoc71 on migtest_oto_child (name);
+create unique index if not exists uq_m12_otoc71 on migtest_oto_child (lower(name));
+create unique index if not exists ix_migtest_oto_master_lowername on migtest_oto_master (lower(name));
drop index if exists ix_migtest_e_basic_indextest3;
drop index if exists ix_migtest_e_basic_indextest6;
create index ix_migtest_e_basic_eref_id on migtest_e_basic (eref_id);
From 91044d0a757c4586e07212b6bdf717f20dcbe3b4 Mon Sep 17 00:00:00 2001
From: rob bygrave
Date: Thu, 25 Mar 2021 23:13:51 +1300
Subject: [PATCH 13/52] javadoc only - improve javadoc for
DatabaseConfig.loadFromProperties()
---
ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
index 8e65f9bb3..49c458743 100644
--- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
+++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
@@ -2693,7 +2693,10 @@ public class DatabaseConfig {
}
/**
- * Load settings from ebean.properties.
+ * Load settings from application.properties, application.yaml and other sources.
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * int selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * int mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(int flags, int mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(int flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(int flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(int flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java b/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
index e5869682c..d8e4cf58b 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
@@ -5,7 +5,7 @@ package io.ebean.typequery;
*
* @param the root query bean type
*/
-public class PLong extends PBaseNumber {
+public class PLong extends PBaseNumber {
/**
* Construct with a property name and root instance.
@@ -14,7 +14,7 @@ public class PLong extends PBaseNumber {
* @param root the root query bean instance
*/
public PLong(String name, R root) {
- super(name , root);
+ super(name, root);
}
/**
@@ -24,4 +24,70 @@ public class PLong extends PBaseNumber {
super(name, root, prefix);
}
+ /**
+ * Add bitwise AND expression of the given bit flags to compare with the match/mask.
+ *
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * long mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(long flags, long mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(long flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(long flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(long flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java b/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
index 440a5ab4c..f8ee7bf8a 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
@@ -5,7 +5,7 @@ package io.ebean.typequery;
*
* @param the root query bean type
*/
-public class PShort extends PBaseNumber {
+public class PShort extends PBaseNumber {
/**
* Construct with a property name and root instance.
@@ -24,4 +24,70 @@ public class PShort extends PBaseNumber {
super(name, root, prefix);
}
+ /**
+ * Add bitwise AND expression of the given bit flags to compare with the match/mask.
+ *
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * short selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * short mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(short flags, short mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(short flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(short flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ *
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(short flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
From f53d31aa2e80f8e05663b99daa29ee4dd8fbccdd Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 1 Apr 2021 12:22:45 +1300
Subject: [PATCH 27/52] No functional change - final field for BatchControl
---
.../main/java/io/ebeaninternal/server/persist/BatchControl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
index 10ba7f205..f276bb331 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
@@ -81,7 +81,7 @@ public final class BatchControl {
*/
private int bufferMax;
- private Queue[] queues = new Queue[3];
+ private final Queue[] queues = new Queue[3];
/**
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
From a617d5890203d445cbd33badcec33ddd3d9d5c0a Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 1 Apr 2021 14:47:54 +1300
Subject: [PATCH 28/52] Initial experimental support for "transparent
persistence"
In short, when this is turned on at flush() get any dirty beans held by the PersistenceContext and persist them (always update for these beans)
---
.../src/main/java/io/ebean/Transaction.java | 8 +
.../io/ebean/bean/PersistenceContext.java | 7 +
.../ebeaninternal/api/ScopedTransaction.java | 5 +
.../server/core/DefaultServer.java | 2 +-
.../server/core/InternalConfiguration.java | 4 +-
.../DefaultPersistenceContext.java | 31 ++-
.../ImplicitReadOnlyTransaction.java | 5 +
.../server/transaction/JdbcTransaction.java | 14 +-
.../server/transaction/NoTransaction.java | 5 +
.../transaction/SavepointTransaction.java | 5 +
.../transaction/TransactionManager.java | 16 +-
.../TransactionManagerOptions.java | 6 +-
.../TestTransparentPersist.java | 204 ++++++++++++++++++
.../src/test/resources/logback-test.xml | 4 +-
14 files changed, 303 insertions(+), 13 deletions(-)
create mode 100644 ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
diff --git a/ebean-api/src/main/java/io/ebean/Transaction.java b/ebean-api/src/main/java/io/ebean/Transaction.java
index d4918e84e..0cbfd6e3c 100644
--- a/ebean-api/src/main/java/io/ebean/Transaction.java
+++ b/ebean-api/src/main/java/io/ebean/Transaction.java
@@ -59,6 +59,14 @@ public interface Transaction extends AutoCloseable {
*/
void register(TransactionCallback callback);
+ /**
+ * EXPERIMENTAL - turn on transparent persistence and batchMode true.
+ *
+ * With this turned on beans that are dirty in the persistence context
+ * are automatically persisted on flush() and commit().
+ */
+ void setTransparentPersistence(boolean transparentPersistence);
+
/**
* Set a label on the transaction.
*
diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
index a8a42e51b..8c861af1f 100644
--- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
+++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
@@ -1,5 +1,7 @@
package io.ebean.bean;
+import java.util.List;
+
/**
* Holds entity beans by there type and id.
*
@@ -77,6 +79,11 @@ public interface PersistenceContext {
*/
boolean resetLimit();
+ /**
+ * Return the list of dirty beans held by this persistence context.
+ */
+ List dirtyBeans();
+
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
*
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
index f5a1db07b..6e95a114e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
@@ -131,6 +131,11 @@ public class ScopedTransaction extends SpiTransactionProxy {
}
}
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ current.getTransaction().setTransparentPersistence(transparentPersistence);
+ }
+
@Override
public void setRollbackOnly() {
current.setRollbackOnly();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 52b94bfb6..6595d9816 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -236,7 +236,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
- this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
+ this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
index 8cbbbfa4b..dca8e3059 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
@@ -423,13 +423,13 @@ public class InternalConfiguration {
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
- TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
+ TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
- new TransactionManagerOptions(notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
+ new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
index df92246fa..27eea2e4c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.transaction;
+import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -200,6 +198,20 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
}
+ @Override
+ public List dirtyBeans() {
+ lock.lock();
+ try {
+ List list = new ArrayList<>();
+ for (ClassContext classContext : typeCache.values()) {
+ classContext.dirtyBeans(list);
+ }
+ return list;
+ } finally {
+ lock.unlock();
+ }
+ }
+
@Override
public String toString() {
lock.lock();
@@ -318,6 +330,17 @@ public final class DefaultPersistenceContext implements PersistenceContext {
deleteSet.add(id);
map.remove(id);
}
+
+ /**
+ * Add the dirty beans to the list.
+ */
+ void dirtyBeans(List list) {
+ for (Object value : map.values()) {
+ if (((EntityBean) value)._ebean_getIntercept().isDirty()) {
+ list.add(value);
+ }
+ }
+ }
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
index 79bf85335..04d7a83a5 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
@@ -95,6 +95,11 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
return startNanos;
}
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ // do nothing
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index 8c1897ff5..2585c2b23 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -187,6 +187,8 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private final long startNanos;
+ private boolean transparentPersistence;
+
/**
* Create a new JdbcTransaction.
*/
@@ -294,6 +296,12 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return sb.toString();
}
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ this.transparentPersistence = transparentPersistence;
+ this.batchMode = true;
+ }
+
@Override
public boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
@@ -774,6 +782,10 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
+ if (transparentPersistence) {
+ // Experimental - flush dirty beans held by the persistence context
+ manager.flushTransparent(persistenceContext, this);
+ }
batchFlush();
if (deferredList != null) {
for (PersistDeferredRelationship deferred : deferredList) {
@@ -1042,7 +1054,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
throw new IllegalStateException(illegalStateMessage);
}
try {
- if (queryOnly) {
+ if (queryOnly && !transparentPersistence) {
connectionEndForQueryOnly();
} else {
flushCommitAndNotify();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
index 2ef3f2058..5d593302f 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
@@ -25,6 +25,11 @@ class NoTransaction implements SpiTransaction {
static final NoTransaction INSTANCE = new NoTransaction();
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ // do nothing
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
index b50122f5c..7b596afec 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
@@ -60,6 +60,11 @@ class SavepointTransaction extends SpiTransactionProxy {
this.rollbackOnly = true;
}
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ throw new IllegalStateException("This is not handled yet. Need to review this case.");
+ }
+
@Override
public void commit() {
if (rollbackOnly) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
index e42ce2fe3..45885ad2a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
@@ -5,6 +5,7 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
+import io.ebean.bean.PersistenceContext;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
@@ -17,6 +18,7 @@ import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
+import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
@@ -59,6 +61,8 @@ public class TransactionManager implements SpiTransactionManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
+ private final SpiServer server;
+
private final BeanDescriptorManager beanDescriptorManager;
/**
@@ -153,7 +157,7 @@ public class TransactionManager implements SpiTransactionManager {
* Create the TransactionManager
*/
public TransactionManager(TransactionManagerOptions options) {
-
+ this.server = options.server;
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.txnDebug = txnLogger.isDebug();
@@ -786,4 +790,14 @@ public class TransactionManager implements SpiTransactionManager {
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
+
+ /**
+ * Experimental - find dirty beans in the persistence context and persist them.
+ */
+ public void flushTransparent(PersistenceContext persistenceContext, SpiTransaction transaction) {
+ List dirtyBeans = persistenceContext.dirtyBeans();
+ if (!dirtyBeans.isEmpty()) {
+ server.updateAll(dirtyBeans, transaction);
+ }
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
index 9a1b0139b..5d0c87794 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.DatabaseConfig;
+import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
@@ -15,6 +16,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
*/
public class TransactionManagerOptions {
+ final SpiServer server;
final boolean notifyL2CacheInForeground;
final DatabaseConfig config;
final ClusterManager clusterManager;
@@ -31,11 +33,11 @@ public class TransactionManagerOptions {
final ClockService clockService;
- public TransactionManagerOptions(boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
+ public TransactionManagerOptions(SpiServer server, boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify, ClockService clockService) {
-
+ this.server = server;
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
this.scopeManager = scopeManager;
diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
new file mode 100644
index 000000000..2922d20b2
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
@@ -0,0 +1,204 @@
+package org.tests.transparentpersist;
+
+import io.ebean.BaseTestCase;
+import io.ebean.DB;
+import io.ebean.Database;
+import io.ebean.Transaction;
+import io.ebeaninternal.api.SpiTransaction;
+import io.ebeantest.LoggedSql;
+import org.junit.Test;
+import org.tests.model.basic.Customer;
+import org.tests.model.basic.EBasicVer;
+import org.tests.model.basic.Order;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestTransparentPersist extends BaseTestCase {
+
+ @Test
+ public void simpleInsertUpdateDelete_experimental() {
+
+ EBasicVer b0 = new EBasicVer("simpleIUD_0");
+ b0.save();
+ EBasicVer b1 = new EBasicVer("simpleIUD_1");
+ b1.save();
+ EBasicVer b2 = new EBasicVer("simpleIUD_2");
+ b2.save();
+
+ EBasicVer newBean;
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ found.setName("auto dirty");
+
+ // delete by id
+ DB.delete(EBasicVer.class, b1.getId());
+ // find and delete, note the delete is batched up to execute later
+ DB.delete(DB.find(EBasicVer.class, b2.getId()));
+
+ // insert is batched up to execute later
+ newBean = new EBasicVer("simpleIUD_New1");
+ DB.save(newBean);
+ // can still mutate newBean before flush (but not after flush yet as new bean isn't put into Persistence context)
+ newBean.setName("simpleIUD_New2");
+
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after.getName()).isEqualTo("auto dirty");
+
+ EBasicVer wasInserted = DB.find(EBasicVer.class, newBean.getId());
+ assertThat(wasInserted.getName()).isEqualTo("simpleIUD_New2");
+
+ assertThat(DB.find(EBasicVer.class, b1.getId())).isNull();
+ assertThat(DB.find(EBasicVer.class, b2.getId())).isNull();
+
+ DB.delete(after);
+ DB.delete(wasInserted);
+ }
+
+ @Test
+ public void simpleUpdate_experimental() {
+
+ EBasicVer transPersist = new EBasicVer("simulate_simpleUpdate");
+ transPersist.save();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, transPersist.getId());
+ found.setName("Persisted automatically as dirty");
+
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class,transPersist.getId());
+ assertThat(after.getName()).isEqualTo("Persisted automatically as dirty");
+ DB.delete(after);
+ }
+
+ @Test
+ public void updateWithPersistCascadeInsert() {
+
+ // setup data
+ Customer c0 = new Customer();
+ c0.setName("firstCust");
+ Order order = new Order();
+ order.setStatus(Order.Status.NEW);
+ order.setCustomer(c0);
+ DB.save(order);
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ Order foundOrder = DB.find(Order.class, order.getId());
+ foundOrder.setStatus(Order.Status.APPROVED);
+ // cascade persist will insert this customer (even though it isn't in the persistence context)
+ Customer c1 = new Customer();
+ c1.setName("newCust CascadePersist");
+ foundOrder.setCustomer(c1);
+
+ transaction.commit();
+ }
+
+ Order checkOrder = DB.find(Order.class, order.getId());
+
+ assertThat(checkOrder.getStatus()).isEqualTo(Order.Status.APPROVED);
+ assertThat(checkOrder.getCustomer().getName()).isEqualTo("newCust CascadePersist");
+
+ DB.delete(checkOrder);
+ DB.delete(Customer.class, checkOrder.getCustomer().getId());
+ DB.delete(Customer.class, c0.getId());
+ }
+
+ @Test
+ public void updateReferenceOnlyWithPersistCascade_Insert_andUpdateForeignKey() {
+
+ // setup data
+ Customer c0 = new Customer();
+ c0.setName("firstCust");
+ Order order = new Order();
+ order.setStatus(Order.Status.NEW);
+ order.setCustomer(c0);
+ DB.save(order);
+
+ LoggedSql.start();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ Order foundOrder = DB.find(Order.class, order.getId());
+ // we ONLY mutate the foreign key
+ // cascade persist will insert this customer (even though it isn't in the persistence context)
+ Customer c1 = new Customer();
+ c1.setName("newCust CascadePersist");
+ foundOrder.setCustomer(c1);
+
+ transaction.commit();
+ }
+
+ List sql = LoggedSql.stop();
+
+ Order checkOrder = DB.find(Order.class, order.getId());
+
+ assertThat(checkOrder.getStatus()).isEqualTo(Order.Status.NEW);
+ assertThat(checkOrder.getCustomer().getName()).isEqualTo("newCust CascadePersist");
+
+ assertThat(sql).hasSize(5);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.order_date");
+ assertThat(sql.get(1)).contains("insert into o_customer");
+ assertThat(sql.get(2)).contains(" -- bind(");
+ assertThat(sql.get(3)).contains("update o_order set updtime=?, kcustomer_id=? where id=? and updtime=?");
+ assertThat(sql.get(4)).contains(" -- bind(");
+
+ DB.delete(checkOrder);
+ DB.delete(Customer.class, checkOrder.getCustomer().getId());
+ DB.delete(Customer.class, c0.getId());
+ }
+
+ @Test
+ public void simulate_transparentPersistence_forSimpleUpdate() {
+
+ EBasicVer transPersist = new EBasicVer("simulate_simpleUpdate");
+ transPersist.save();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setBatchMode(true);
+ transaction.setBatchSize(10);
+
+ EBasicVer found = DB.find(EBasicVer.class, transPersist.getId());
+ found.setName("Changed");
+
+ // simulate transparent persistence
+ List dirtyBeans = simulateTransparentPersist(transaction);
+ assertThat(dirtyBeans).hasSize(1);
+ assertThat(dirtyBeans).contains(found);
+
+ // would occur as first part of flush
+ transaction.flush();
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class,transPersist.getId());
+ assertThat(after.getName()).isEqualTo("Changed");
+ DB.delete(after);
+ }
+
+ private List simulateTransparentPersist(Transaction transaction) {
+ Database db = DB.getDefault();
+ List dirtyBeans = getDirtyBeansFromPersistenceContext(transaction);
+ for (Object dirtyBean : dirtyBeans) {
+ db.update(dirtyBean, transaction);
+ }
+ return dirtyBeans;
+ }
+
+ private List getDirtyBeansFromPersistenceContext(Transaction transaction) {
+ return ((SpiTransaction)transaction).getPersistenceContext().dirtyBeans();
+ }
+
+}
diff --git a/ebean-core/src/test/resources/logback-test.xml b/ebean-core/src/test/resources/logback-test.xml
index 1b8a60a9b..164ff73d3 100644
--- a/ebean-core/src/test/resources/logback-test.xml
+++ b/ebean-core/src/test/resources/logback-test.xml
@@ -79,8 +79,8 @@
-
-
+
+
From ba397bbb0c383bb9be0d1174e3752b9db09c4ef5 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 1 Apr 2021 16:04:18 +1300
Subject: [PATCH 29/52] For experimental "transparent persistence" - Delete
removes from PC early
When delete bean executed with transparent persistence, this marks the bean as removed from the persistence context early.
This avoids a "dirty" deleted bean from being seen as a "dirty" bean in the persistence context at flush() time.
---
.../server/core/PersistRequestBean.java | 8 +++++++
.../server/persist/DefaultPersister.java | 1 +
.../TestTransparentPersist.java | 23 +++++++++++++++++++
3 files changed, 32 insertions(+)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index eb48fc2ef..c4d02cbf6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -887,6 +887,14 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
}
}
+ /**
+ * Remove deleted beans from the persistence context early.
+ */
+ public void removeFromPersistenceContext() {
+ idValue = beanDescriptor.getId(entityBean);
+ beanDescriptor.contextDeleted(transaction.getPersistenceContext(), idValue);
+ }
+
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
index a7b21f0c8..005e85d95 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
@@ -881,6 +881,7 @@ public final class DefaultPersister implements Persister {
}
int count = request.executeOrQueue();
+ request.removeFromPersistenceContext();
if (request.isPersistCascade()) {
deleteAssocOne(request);
diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
index 2922d20b2..be5d8329b 100644
--- a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
+++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
@@ -17,6 +17,29 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestTransparentPersist extends BaseTestCase {
+ @Test
+ public void delete_expect_beanRemovedFromPersistenceContext() {
+
+ EBasicVer b0 = new EBasicVer("simpleDelete");
+ DB.save(b0);
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ // make it dirty
+ found.setName("make it dirty");
+
+ // delete it, should remove it from the "live" part of persistence context
+ // with the expectation that no update is executed (no dirty in PC update)
+ DB.delete(found);
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after).isNull();
+ }
+
@Test
public void simpleInsertUpdateDelete_experimental() {
From 0527f45f5a4e3b479296e051493cf536728bcc06 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 1 Apr 2021 16:59:03 +1300
Subject: [PATCH 30/52] For experimental "transparent persistence" - Inserts
added to PC
When inserting bean with transparent persistence the beans are registered with the persistence context. This is done to support the cases where the beans are inserted, a flush() occurs, the inserted bean is mutated and now dirty - we want that to be detected and get an update
---
.../ebeaninternal/api/ScopedTransaction.java | 5 ---
.../io/ebeaninternal/api/SpiTransaction.java | 5 +++
.../api/SpiTransactionProxy.java | 10 ++++++
.../server/core/PersistRequestBean.java | 4 +++
.../ImplicitReadOnlyTransaction.java | 5 +++
.../server/transaction/JdbcTransaction.java | 5 +++
.../server/transaction/NoTransaction.java | 5 +++
.../transaction/SavepointTransaction.java | 5 ---
.../TestTransparentPersist.java | 36 +++++++++++++++++++
9 files changed, 70 insertions(+), 10 deletions(-)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
index 6e95a114e..f5a1db07b 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
@@ -131,11 +131,6 @@ public class ScopedTransaction extends SpiTransactionProxy {
}
}
- @Override
- public void setTransparentPersistence(boolean transparentPersistence) {
- current.getTransaction().setTransparentPersistence(transparentPersistence);
- }
-
@Override
public void setRollbackOnly() {
current.setRollbackOnly();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
index 74faeccb0..8c520f79e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
@@ -147,6 +147,11 @@ public interface SpiTransaction extends Transaction {
*/
int depth();
+ /**
+ * Return true if transparent persistence is turned on.
+ */
+ boolean isTransparentPersistence();
+
/**
* Return true if this transaction was created explicitly via
* Ebean.beginTransaction().
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
index fe5351ddc..1652b56f6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
@@ -43,6 +43,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.getLabel();
}
+ @Override
+ public void setTransparentPersistence(boolean transparentPersistence) {
+ transaction.setTransparentPersistence(transparentPersistence);
+ }
+
+ @Override
+ public boolean isTransparentPersistence() {
+ return transaction.isTransparentPersistence();
+ }
+
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index c4d02cbf6..3f7d6e79a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -1038,6 +1038,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
+ if (transaction.isTransparentPersistence() && idValue != null) {
+ // with getGeneratedKeys off we will not have a idValue
+ beanDescriptor.contextPut(transaction.getPersistenceContext(), idValue, entityBean);
+ }
}
public boolean isReference() {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
index 04d7a83a5..a94260fb6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
@@ -100,6 +100,11 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
// do nothing
}
+ @Override
+ public boolean isTransparentPersistence() {
+ return false;
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index 2585c2b23..bb672cd33 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -302,6 +302,11 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.batchMode = true;
}
+ @Override
+ public boolean isTransparentPersistence() {
+ return transparentPersistence;
+ }
+
@Override
public boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
index 5d593302f..92f0e1ca9 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
@@ -30,6 +30,11 @@ class NoTransaction implements SpiTransaction {
// do nothing
}
+ @Override
+ public boolean isTransparentPersistence() {
+ return false;
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
index 7b596afec..b50122f5c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/SavepointTransaction.java
@@ -60,11 +60,6 @@ class SavepointTransaction extends SpiTransactionProxy {
this.rollbackOnly = true;
}
- @Override
- public void setTransparentPersistence(boolean transparentPersistence) {
- throw new IllegalStateException("This is not handled yet. Need to review this case.");
- }
-
@Override
public void commit() {
if (rollbackOnly) {
diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
index be5d8329b..a59ddda60 100644
--- a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
+++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
@@ -17,6 +17,42 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestTransparentPersist extends BaseTestCase {
+ @Test
+ public void insertFlush_mutateFlush_expect_update() {
+
+ LoggedSql.start();
+
+ EBasicVer newBean;
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+
+ newBean = new EBasicVer("insertMe");
+ DB.save(newBean);
+
+ // flush - new bean needs to get registered into persistence context
+ transaction.flush();
+
+ // make it dirty, we expect it to execute an update on flush()
+ newBean.setName("make it dirty - auto save");
+
+ // flush again, auto persist dirty bean in persistence context
+ transaction.commit();
+ }
+
+ List sql = LoggedSql.stop();
+
+ EBasicVer found = DB.find(EBasicVer.class, newBean.getId());
+ assertThat(found.getName()).isEqualTo("make it dirty - auto save");
+
+ assertThat(sql).hasSize(4);
+ assertThat(sql.get(0)).contains("insert into e_basicver");
+ assertThat(sql.get(1)).contains(" -- bind(");
+ assertThat(sql.get(2)).contains("update e_basicver set name=?, last_update=? where id=? and last_update=?");
+ assertThat(sql.get(3)).contains(" -- bind(");
+
+ DB.delete(found);
+ }
+
@Test
public void delete_expect_beanRemovedFromPersistenceContext() {
From 6bcf5c6881e14b0156abaaf14fd4cff39dce8df1 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 1 Apr 2021 22:57:34 +1300
Subject: [PATCH 31/52] Add DatabaseConfig.autoPersistUpdates for experimental
"transparent persistence"
---
.../java/io/ebean/config/DatabaseConfig.java | 23 ++++++++++++++++++-
.../server/transaction/JdbcTransaction.java | 5 ++--
.../transaction/TransactionManager.java | 11 +++++++--
.../io/ebean/config/ServerConfigTest.java | 3 +++
4 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
index bf4c61675..be2ac6c1f 100644
--- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
+++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
@@ -232,6 +232,12 @@ public class DatabaseConfig {
*/
private String historyTableSuffix = "_history";
+ /**
+ * When true explicit transactions beans that have been made dirty will be
+ * automatically persisted via update on flush.
+ */
+ private boolean autoPersistUpdates;
+
/**
* Use for transaction scoped batch mode.
*/
@@ -905,6 +911,20 @@ public class DatabaseConfig {
this.tenantCatalogProvider = tenantCatalogProvider;
}
+ /**
+ * Return true if dirty beans are automatically persisted.
+ */
+ public boolean isAutoPersistUpdates() {
+ return autoPersistUpdates;
+ }
+
+ /**
+ * Set to true if dirty beans are automatically persisted.
+ */
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ this.autoPersistUpdates = autoPersistUpdates;
+ }
+
/**
* Return the PersistBatch mode to use by default at the transaction level.
*
* With this turned on beans that are dirty in the persistence context
* are automatically persisted on flush() and commit().
*/
- void setTransparentPersistence(boolean transparentPersistence);
+ void setAutoPersistUpdates(boolean autoPersistUpdates);
/**
* Set a label on the transaction.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
index 8c520f79e..8c59fa7dc 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
@@ -148,9 +148,9 @@ public interface SpiTransaction extends Transaction {
int depth();
/**
- * Return true if transparent persistence is turned on.
+ * Return true if dirty beans are automatically persisted.
*/
- boolean isTransparentPersistence();
+ boolean isAutoPersistUpdates();
/**
* Return true if this transaction was created explicitly via
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
index 1652b56f6..5fd2a3c6e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
@@ -44,13 +44,13 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
- public void setTransparentPersistence(boolean transparentPersistence) {
- transaction.setTransparentPersistence(transparentPersistence);
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ transaction.setAutoPersistUpdates(autoPersistUpdates);
}
@Override
- public boolean isTransparentPersistence() {
- return transaction.isTransparentPersistence();
+ public boolean isAutoPersistUpdates() {
+ return transaction.isAutoPersistUpdates();
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index 3f7d6e79a..31d92b0a0 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -1038,7 +1038,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
- if (transaction.isTransparentPersistence() && idValue != null) {
+ if (transaction.isAutoPersistUpdates() && idValue != null) {
// with getGeneratedKeys off we will not have a idValue
beanDescriptor.contextPut(transaction.getPersistenceContext(), idValue, entityBean);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
index a94260fb6..5f2b5d195 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
@@ -96,12 +96,12 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
}
@Override
- public void setTransparentPersistence(boolean transparentPersistence) {
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
// do nothing
}
@Override
- public boolean isTransparentPersistence() {
+ public boolean isAutoPersistUpdates() {
return false;
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index 411331aef..7d3b3ddf1 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -187,7 +187,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private final long startNanos;
- private boolean transparentPersistence;
+ private boolean autoPersistUpdates;
/**
* Create a new JdbcTransaction.
@@ -211,7 +211,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.batchOnCascadeMode = false;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
- this.transparentPersistence = explicit && manager.isAutoPersistUpdates();
+ this.autoPersistUpdates = explicit && manager.isAutoPersistUpdates();
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
@@ -298,14 +298,14 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
@Override
- public void setTransparentPersistence(boolean transparentPersistence) {
- this.transparentPersistence = transparentPersistence;
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ this.autoPersistUpdates = autoPersistUpdates;
this.batchMode = true;
}
@Override
- public boolean isTransparentPersistence() {
- return transparentPersistence;
+ public boolean isAutoPersistUpdates() {
+ return autoPersistUpdates;
}
@Override
@@ -788,7 +788,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
- if (transparentPersistence) {
+ if (autoPersistUpdates) {
// Experimental - flush dirty beans held by the persistence context
manager.flushTransparent(persistenceContext, this);
}
@@ -1060,7 +1060,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
throw new IllegalStateException(illegalStateMessage);
}
try {
- if (queryOnly && !transparentPersistence) {
+ if (queryOnly && !autoPersistUpdates) {
connectionEndForQueryOnly();
} else {
flushCommitAndNotify();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
index 92f0e1ca9..0d3c82cd6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
@@ -26,12 +26,12 @@ class NoTransaction implements SpiTransaction {
static final NoTransaction INSTANCE = new NoTransaction();
@Override
- public void setTransparentPersistence(boolean transparentPersistence) {
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
// do nothing
}
@Override
- public boolean isTransparentPersistence() {
+ public boolean isAutoPersistUpdates() {
return false;
}
diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
index a59ddda60..963cc1dcb 100644
--- a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
+++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
@@ -24,7 +24,7 @@ public class TestTransparentPersist extends BaseTestCase {
EBasicVer newBean;
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
newBean = new EBasicVer("insertMe");
DB.save(newBean);
@@ -60,7 +60,7 @@ public class TestTransparentPersist extends BaseTestCase {
DB.save(b0);
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
EBasicVer found = DB.find(EBasicVer.class, b0.getId());
// make it dirty
@@ -88,7 +88,7 @@ public class TestTransparentPersist extends BaseTestCase {
EBasicVer newBean;
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
EBasicVer found = DB.find(EBasicVer.class, b0.getId());
found.setName("auto dirty");
@@ -127,7 +127,7 @@ public class TestTransparentPersist extends BaseTestCase {
transPersist.save();
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
EBasicVer found = DB.find(EBasicVer.class, transPersist.getId());
found.setName("Persisted automatically as dirty");
@@ -152,7 +152,7 @@ public class TestTransparentPersist extends BaseTestCase {
DB.save(order);
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
Order foundOrder = DB.find(Order.class, order.getId());
foundOrder.setStatus(Order.Status.APPROVED);
@@ -188,7 +188,7 @@ public class TestTransparentPersist extends BaseTestCase {
LoggedSql.start();
try (Transaction transaction = DB.beginTransaction()) {
- transaction.setTransparentPersistence(true); // EXPERIMENTAL feature
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
Order foundOrder = DB.find(Order.class, order.getId());
// we ONLY mutate the foreign key
From 6d8a1c9398951b2bffe51e1e9d951b971bfbcb91 Mon Sep 17 00:00:00 2001
From: Vladimir Konkov
Date: Mon, 5 Apr 2021 21:45:48 +0300
Subject: [PATCH 33/52] Explicit locking method withLock() is unaccessible on
query beans
---
.../src/main/java/io/ebean/typequery/TQRootBean.java | 4 ++--
.../src/test/java/org/querytest/QCustomerTest.java | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
index 654e35e9d..728184cf4 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
@@ -679,7 +679,7 @@ public abstract class TQRootBean {
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
- R withLock(Query.LockType lockType) {
+ public R withLock(Query.LockType lockType) {
query.withLock(lockType);
return root;
}
@@ -693,7 +693,7 @@ public abstract class TQRootBean {
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
- R withLock(Query.LockType lockType, Query.LockWait lockWait) {
+ public R withLock(Query.LockType lockType, Query.LockWait lockWait) {
query.withLock(lockType, lockWait);
return root;
}
diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
index 56aec1c51..867290e71 100644
--- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
@@ -71,6 +71,7 @@ public class QCustomerTest {
// not found using other transaction
final Customer foundNot = new QCustomer()
.name.eq("explicitTransaction")
+ .withLock(Query.LockType.SHARE)
.findOne();
assertThat(foundNot).isNull();
From 9b04f20ed3a1c40de97826f682524d3a6757a77d Mon Sep 17 00:00:00 2001
From: Roman Parshikov
Date: Tue, 6 Apr 2021 13:32:57 +0800
Subject: [PATCH 34/52] Fix copy/paste typo
---
ebean-api/src/main/java/io/ebean/ExpressionList.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java
index 72f1c06f9..e91846e08 100644
--- a/ebean-api/src/main/java/io/ebean/ExpressionList.java
+++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java
@@ -1671,7 +1671,7 @@ public interface ExpressionList {
ExpressionList endAnd();
/**
- * End a AND junction - synonym for endJunction().
+ * End a OR junction - synonym for endJunction().
*/
ExpressionList endOr();
From f7c1845f0c5dae997f27a799438fea02a7dadf2f Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Tue, 6 Apr 2021 19:58:02 +1200
Subject: [PATCH 35/52] #2177 - Fix where schema not provided by JDBC driver
For @Table with schema, using findNative with a JDBC driver that does not provide the schema this fix matches by just using the table name.
---
.../server/deploy/BeanDescriptor.java | 6 +--
.../server/deploy/BeanDescriptorTest.java | 53 ++++++++++++++++---
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index a2d902f13..e05c636bb 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -2473,7 +2473,7 @@ public class BeanDescriptor implements BeanType, STreeType {
* Return the property path given the db table and column.
*/
public String findBeanPath(String schemaName, String tableName, String columnName) {
- if (matchBaseTable(schemaName, tableName)) {
+ if (matchBaseTable(tableName)) {
return columnPath.get(columnName);
}
BeanPropertyAssoc> assocProperty = tablePath.get(tableName);
@@ -2489,10 +2489,10 @@ public class BeanDescriptor implements BeanType, STreeType {
return null;
}
- private boolean matchBaseTable(String schemaName, String tableName) {
+ boolean matchBaseTable(String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
- || baseTable.equalsIgnoreCase(schemaName + "." + tableName);
+ || baseTable.endsWith("." + tableName);
}
/**
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
index d4cbcbfbf..ab8ae6a87 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
@@ -4,21 +4,22 @@ import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.bean.EntityBean;
import io.ebean.plugin.Property;
+import io.ebeaninternal.server.core.CacheOptions;
+import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+import io.ebeaninternal.server.deploy.meta.DeployIdentityMode;
+import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import org.junit.Test;
-import org.tests.model.basic.Animal;
-import org.tests.model.basic.AnimalShelter;
-import org.tests.model.basic.Cat;
-import org.tests.model.basic.Contact;
-import org.tests.model.basic.Country;
-import org.tests.model.basic.Customer;
-import org.tests.model.basic.Dog;
-import org.tests.model.basic.Order;
+import org.tests.model.basic.*;
import org.tests.model.bridge.BSite;
import org.tests.model.bridge.BUser;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class BeanDescriptorTest extends BaseTestCase {
@@ -88,6 +89,42 @@ public class BeanDescriptorTest extends BaseTestCase {
assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate");
}
+ @Test
+ public void matchBaseTable() {
+ BeanDescriptor desc = getBeanDescriptor(Customer.class);
+ assertTrue(desc.matchBaseTable("o_customer"));
+ }
+
+ @Test
+ public void matchBaseTable_whenTableHasSchema_expect_matchRegardlessOfSchema() {
+
+ DeployBeanDescriptor deploy = mockDeployCustomer();
+
+ when(deploy.getBaseTable()).thenReturn("foo.o_customer");
+ BeanDescriptor> desc1 = new BeanDescriptor<>(mockOwner(), deploy);
+ assertTrue(desc1.matchBaseTable("o_customer"));
+
+ when(deploy.getBaseTable()).thenReturn("bar.o_customer");
+ BeanDescriptor> desc2 = new BeanDescriptor<>(mockOwner(), deploy);
+ assertTrue(desc2.matchBaseTable("o_customer"));
+ }
+
+ @SuppressWarnings("unchecked")
+ private DeployBeanDescriptor mockDeployCustomer() {
+ DeployBeanDescriptor deploy = mock(DeployBeanDescriptor.class);
+ when(deploy.getBeanType()).thenReturn(Customer.class);
+ when(deploy.getIdentityMode()).thenReturn(DeployIdentityMode.auto());
+ when(deploy.buildIdentityMode()).thenReturn(IdentityMode.NONE);
+ when(deploy.getCacheOptions()).thenReturn(CacheOptions.NO_CACHING);
+ return deploy;
+ }
+
+ private BeanDescriptorMap mockOwner() {
+ BeanDescriptorMap owner = mock(BeanDescriptorMap.class);
+ when(owner.createDocStoreBeanAdapter(any(), any())).thenReturn(mock(DocStoreBeanAdapter.class));
+ return owner;
+ }
+
@Test
public void merge_when_empty() {
From 3e97a4005ab34abbac6829ac7e8d70550427c96f Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Tue, 6 Apr 2021 20:09:09 +1200
Subject: [PATCH 36/52] Change TestCustomerFinder drop query plan threshold for
tests only
---
.../java/org/tests/query/finder/TestCustomerFinder.java | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
index 8446f9d5b..468b165bb 100644
--- a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
+++ b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
@@ -55,7 +55,6 @@ public class TestCustomerFinder extends BaseTestCase {
assertThat(json1).contains("\"name\":\"txn.main\"");
assertThat(json1).contains("\"name\":\"orm.Customer.findList\"");
assertThat(json1).doesNotContain("\"sql\":\"select t0.id, t0.status, t0.name");
-
}
@Test
@@ -85,7 +84,6 @@ public class TestCustomerFinder extends BaseTestCase {
assertThat(customer.getName()).isEqualTo(customer1.getName());
assertThat(Customer.find.db().getName()).isEqualTo(DB.getDefault().getName());
-
}
@Test
@@ -178,7 +176,7 @@ public class TestCustomerFinder extends BaseTestCase {
// change default collect query plan threshold to 200 micros
QueryPlanInit init0 = new QueryPlanInit();
init0.setAll(true);
- init0.setThresholdMicros(200);
+ init0.setThresholdMicros(2);
final List plans = server().getMetaInfoManager().queryPlanInit(init0);
assertThat(plans.size()).isGreaterThan(1);
@@ -188,7 +186,7 @@ public class TestCustomerFinder extends BaseTestCase {
// change query plan threshold to 100 micros
QueryPlanInit init = new QueryPlanInit();
init.setAll(true);
- init.setThresholdMicros(100);
+ init.setThresholdMicros(1);
final List appliedToPlans = server().getMetaInfoManager().queryPlanInit(init);
assertThat(appliedToPlans.size()).isGreaterThan(4);
From bb1275f4f95aa43c34b392791b419f2e673bb6dc Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Tue, 6 Apr 2021 21:13:59 +1200
Subject: [PATCH 37/52] #2217 - Inserting an entity bean is skipped with a
String @Id property and no other properties set
---
.../server/core/PersistRequestBean.java | 7 ++
.../server/deploy/BeanDescriptor.java | 2 +-
.../server/persist/DefaultPersister.java | 2 +-
.../cache/personinfo/PersonCacheEmail.java | 4 +
.../tests/cache/personinfo/PersonOther.java | 73 +++++++++++++++++++
.../cache/personinfo/TestStringIdOnly.java | 30 ++++++++
6 files changed, 116 insertions(+), 2 deletions(-)
create mode 100644 ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java
create mode 100644 ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index eb48fc2ef..01c27fa0f 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -1032,6 +1032,13 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
}
}
+ /**
+ * Return if persist can be skipped on the reference only bean.
+ */
+ public boolean isSkipReference() {
+ return intercept.isReference() || (Flags.isRecurse(flags) && beanDescriptor.referenceIdPropertyOnly(intercept));
+ }
+
public boolean isReference() {
return beanDescriptor.isReference(intercept);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index a2d902f13..2e0e00b65 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -3134,7 +3134,7 @@ public class BeanDescriptor implements BeanType, STreeType {
return ebi.isReference() || referenceIdPropertyOnly(ebi);
}
- boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
+ public boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
return idOnlyReference && ebi.hasIdOnly(idPropertyIndex);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
index a7b21f0c8..935fbb6b8 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
@@ -447,7 +447,7 @@ public final class DefaultPersister implements Persister {
public void insert(EntityBean bean, Transaction t) {
PersistRequestBean> req = createRequest(bean, t, PersistRequest.Type.INSERT);
- if (req.isReference()) {
+ if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
index 58ddea0a2..144253edf 100644
--- a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
@@ -25,6 +25,10 @@ public class PersonCacheEmail {
this.email = email;
}
+ public PersonCacheEmail(String id) {
+ this.id = id;
+ }
+
public String getId() {
return id;
}
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java
new file mode 100644
index 000000000..b4b505125
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java
@@ -0,0 +1,73 @@
+package org.tests.cache.personinfo;
+
+import io.ebean.annotation.WhenCreated;
+import io.ebean.annotation.WhenModified;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+import javax.validation.constraints.Size;
+import java.time.Instant;
+
+@Entity
+public class PersonOther {
+
+ @Id
+ @Size(max=128)
+ private String id;
+
+ private String email;
+
+ @WhenCreated
+ private Instant whenCreated;
+
+ @WhenModified
+ private Instant whenModified;
+
+ @Version
+ private long version;
+
+ public PersonOther(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Instant getWhenCreated() {
+ return whenCreated;
+ }
+
+ public void setWhenCreated(Instant whenCreated) {
+ this.whenCreated = whenCreated;
+ }
+
+ public Instant getWhenModified() {
+ return whenModified;
+ }
+
+ public void setWhenModified(Instant whenModified) {
+ this.whenModified = whenModified;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+}
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java b/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java
new file mode 100644
index 000000000..6379491ff
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java
@@ -0,0 +1,30 @@
+package org.tests.cache.personinfo;
+
+import io.ebean.BaseTestCase;
+import io.ebean.DB;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestStringIdOnly extends BaseTestCase {
+
+ @Test
+ public void insert() {
+
+ PersonCacheEmail b0 = new PersonCacheEmail("IdOnly");
+ DB.save(b0);
+
+ PersonCacheEmail found = DB.find(PersonCacheEmail.class, b0.getId());
+ assertThat(found).isNotNull();
+ }
+
+ @Test
+ public void insert_whenIdOnly() {
+
+ PersonOther b0 = new PersonOther("IdOnly");
+ DB.save(b0);
+
+ PersonOther found = DB.find(PersonOther.class, b0.getId());
+ assertThat(found).isNotNull();
+ }
+}
From f7f383d8f303682368096a9fdb52da50bcaf7e50 Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Wed, 7 Apr 2021 13:38:34 +1200
Subject: [PATCH 38/52] No effective change - tidy tests
TestRawSqlPositionedParams, TestRawSqlUnparsedQuery
---
.../rawsql/TestRawSqlPositionedParams.java | 54 +++++++++----------
.../tests/rawsql/TestRawSqlUnparsedQuery.java | 29 +++++-----
2 files changed, 42 insertions(+), 41 deletions(-)
diff --git a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
index f1c3ef4fd..75d678fd6 100644
--- a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
+++ b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
@@ -1,38 +1,42 @@
package org.tests.rawsql;
import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.Query;
+import io.ebean.DB;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
-import org.junit.Test;
import java.util.List;
-import static org.junit.Assert.assertNotNull;
+import static org.assertj.core.api.Assertions.assertThat;
public class TestRawSqlPositionedParams extends BaseTestCase {
+ private static final RawSql RAWSQL_1 = RawSqlBuilder
+ .parse("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
+ .create();
+
+ private static final RawSql RAW_SQL_2 = RawSqlBuilder
+ .unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
+ .columnMapping("r.id", "id")
+ .columnMapping("r.name", "name")
+ .create();
+
@Test
public void test() {
ResetBasicData.reset();
- RawSql rawSql = RawSqlBuilder
- .parse("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
- .create();
+ List list = DB.find(Customer.class)
+ .setRawSql(RAWSQL_1)
+ .setParameter(1)
+ .setParameter("R%")
+ .where().lt("id", 2001)
+ .findList();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter(1, 1);
- query.setParameter(2, "R%");
- query.where().lt("id", 2001);
-
- List list = query.findList();
-
- assertNotNull(list);
+ assertThat(list).isNotNull();
}
@Test
@@ -40,18 +44,12 @@ public class TestRawSqlPositionedParams extends BaseTestCase {
ResetBasicData.reset();
- RawSql rawSql = RawSqlBuilder
- .unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
- .columnMapping("r.id", "id")
- .columnMapping("r.name", "name").create();
+ List list = DB.find(Customer.class)
+ .setRawSql(RAW_SQL_2)
+ .setParameter(1)
+ .setParameter("R%")
+ .findList();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter(1, 1);
- query.setParameter(2, "R%");
-
- List list = query.findList();
-
- assertNotNull(list);
+ assertThat(list).isNotNull();
}
}
diff --git a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
index 8d900b15f..1e576f0b7 100644
--- a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
+++ b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
@@ -1,19 +1,25 @@
package org.tests.rawsql;
import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.Query;
+import io.ebean.DB;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
-import org.junit.Assert;
-import org.junit.Test;
import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class TestRawSqlUnparsedQuery extends BaseTestCase {
+ private static final RawSql rawSql = RawSqlBuilder
+ .unparsed("select r.id, r.name from o_customer r where r.id >= :a and r.name like :b")
+ .columnMapping("r.id", "id")
+ .columnMapping("r.name", "name")
+ .create();
+
@Test
public void testDoubleUnparsedQuery() {
@@ -25,17 +31,14 @@ public class TestRawSqlUnparsedQuery extends BaseTestCase {
}
private static void test() {
- RawSql rawSql = RawSqlBuilder
- .unparsed("select r.id, r.name from o_customer r where r.id >= :a and r.name like :b")
- .columnMapping("r.id", "id").columnMapping("r.name", "name").create();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter("a", 1);
- query.setParameter("b", "R%");
+ List list = DB.find(Customer.class)
+ .setRawSql(rawSql)
+ .setParameter("a", 1)
+ .setParameter("b", "R%")
+ .findList();
- List list = query.findList();
- Assert.assertNotNull(list);
+ assertThat(list).isNotNull();
}
}
From 59793f3c018e1291ec4bf6d869dba4117aa1ca7c Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Wed, 7 Apr 2021 15:50:04 +1200
Subject: [PATCH 39/52] Improve javadoc for Transaction#setGetGeneratedKeys
More clearly document the limitation that we can't update beans that don't have id values
---
.../src/main/java/io/ebean/Transaction.java | 30 ++++++-------------
1 file changed, 9 insertions(+), 21 deletions(-)
diff --git a/ebean-api/src/main/java/io/ebean/Transaction.java b/ebean-api/src/main/java/io/ebean/Transaction.java
index d4918e84e..d3e918c0d 100644
--- a/ebean-api/src/main/java/io/ebean/Transaction.java
+++ b/ebean-api/src/main/java/io/ebean/Transaction.java
@@ -339,15 +339,12 @@ public interface Transaction extends AutoCloseable {
* The batch is automatically flushed when it hits the batch size and also when we
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
* beans.
- *
*
* We use {@link #flush()} to explicitly flush the batch and we can use
* {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)}
* to control the automatic flushing behaviour.
- *
*
* Example: batch processing of CallableSql executing every 10 rows
- *
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
- *
*
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
- *
*
* This is effectively already turned on for all platforms apart from older Sql Server.
- *
*
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch)
@@ -422,15 +416,19 @@ public interface Transaction extends AutoCloseable {
int getBatchSize();
/**
- * Specify if you want batched inserts to use getGeneratedKeys.
+ * Specify if we want batched inserts to use getGeneratedKeys.
*
* By default batched inserts will try to use getGeneratedKeys if it is
* supported by the underlying jdbc driver and database.
- *
*
- * You may want to turn getGeneratedKeys off when you are inserting a large
- * number of objects and you don't care about getting back the ids.
- *
+ * We want to turn off getGeneratedKeys when we are inserting a large
+ * number of objects and we don't care about getting back the ids. In this
+ * way we avoid the extra cost of getting back the generated id values
+ * from the database.
+ *
+ * Note that when we do turn off getGeneratedKeys then we have the limitation
+ * that after a bean has been inserted we are unable to then mutate the bean
+ * and update it in the same transaction as we have not obtained it's id value.
*/
void setGetGeneratedKeys(boolean getGeneratedKeys);
@@ -449,13 +447,11 @@ public interface Transaction extends AutoCloseable {
*
* If you want to execute both WITHOUT having the batch automatically flush
* you need to call this with batchFlushOnMixed = false.
- *
*
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
* beans are executed). This is because the UpdateSql and CallableSql have
* already been bound to their PreparedStatements. The beans on the other hand
* have a 2 step process (delayed binding).
- *
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
- *
*/
void setFlushOnQuery(boolean batchFlushOnQuery);
@@ -490,7 +485,6 @@ public interface Transaction extends AutoCloseable {
* should be flushed prior to executing a query.
*
* The default is for this to be true.
- *
*/
boolean isFlushOnQuery();
@@ -507,7 +501,6 @@ public interface Transaction extends AutoCloseable {
* flush the batch if you like.
*
* Flushing occurs automatically when:
- *
*
*
the batch size is reached
*
A query is executed on the same transaction
@@ -533,11 +526,9 @@ public interface Transaction extends AutoCloseable {
* commit() rollback() and end() methods on the Transaction should still be
* used. Calling these methods on the Connection would be a big no no unless
* you know what you are doing.
- *
*
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
- *
*/
Connection getConnection();
@@ -545,17 +536,14 @@ public interface Transaction extends AutoCloseable {
* Add table modification information to the TransactionEvent.
*
* Use this in conjunction with getConnection() and raw JDBC.
- *
*
* This effectively informs Ebean of the data that has been changed by the
* transaction and this information is normally automatically handled by Ebean
* when you save entity beans or use UpdateSql etc.
- *
*
* If you use raw JDBC then you can use this method to inform Ebean for the
* tables that have been modified. Ebean uses this information to keep its
* caches in synch and maintain text indexes.
- *