Refactor detection of deferred persist relationship (circular imported ids)

This commit is contained in:
rob bygrave
2019-10-08 21:49:22 +13:00
parent 5e7dda1ec8
commit 6621c5dfbb
13 changed files with 224 additions and 19 deletions
@@ -283,10 +283,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Check for batch escalation on cascade.
*/
public void checkBatchEscalationOnCascade() {
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
if (type != Type.INSERT || beanDescriptor.isCascadeBatchEscalateSupported()) {
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
}
}
persistCascade = transaction.isPersistCascade();
}
@@ -140,6 +140,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final short profileBeanId;
private final boolean multiValueSupported;
private boolean cascadeBatchEscalateSupported;
public enum EntityType {
ORM, EMBEDDED, VIEW, SQL, DOC
@@ -752,6 +753,24 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
private boolean hasCircularImportedId() {
for (BeanPropertyAssocOne<?> assocOne : propertiesOneImportedSave) {
if (assocOne.hasCircularImportedId(this)) {
return true;
}
}
return false;
}
boolean hasCircularImportedIdTo(BeanDescriptor sourceDesc) {
for (BeanPropertyAssocOne<?> assocOne : propertiesOneImportedSave) {
if (assocOne.getTargetDescriptor() == sourceDesc) {
return true;
}
}
return false;
}
void registerColumn(String dbColumn, String path) {
String key = dbColumn.toLowerCase();
// check for clash with imported OneToOne PK
@@ -812,6 +831,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
@SuppressWarnings("unchecked")
void initialiseDocMapping() {
cascadeBatchEscalateSupported = supportCascadeBatch();
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.initialisePostTarget();
}
@@ -826,6 +846,18 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
cacheHelp.deriveNotifyFlags();
}
private boolean supportCascadeBatch() {
return idType == IdType.IDENTITY || !hasCircularImportedId();
}
/**
* Return false if JDBC batch can't be implicitly escalated to.
* This happens when we have circular import id situation (need to defer setting identity value).
*/
public boolean isCascadeBatchEscalateSupported() {
return cascadeBatchEscalateSupported;
}
void initInheritInfo() {
if (inheritInfo != null) {
// need to check every BeanDescriptor in the inheritance hierarchy
@@ -807,4 +807,8 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
}
public boolean hasCircularImportedId(BeanDescriptor<?> sourceDesc) {
return targetDescriptor.hasCircularImportedIdTo(sourceDesc);
}
}
@@ -643,6 +643,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
// implicit transaction, no gain by batching where depth <= 0
return false;
}
//
return batchMode;
}
@@ -2,10 +2,12 @@ package org.tests.basic.one2one;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
public class TestOne2OneBookingInvoice extends BaseTestCase {
@Test
@@ -25,23 +27,23 @@ public class TestOne2OneBookingInvoice extends BaseTestCase {
Ebean.save(b);
Invoice invoice = Ebean.find(Invoice.class, ai.getId());
Assert.assertEquals(b.getId(), invoice.getBooking().getId());
assertEquals(b.getId(), invoice.getBooking().getId());
Booking b1 = Ebean.find(Booking.class, b.getId());
Invoice ai1 = b1.getAgentInvoice();
Assert.assertNotNull(ai1);
assertNotNull(ai1);
Booking b2 = ai1.getBooking();
Assert.assertNotNull(b2);
Assert.assertEquals(b1.getId(), b2.getId());
Assert.assertSame(b1, b2);
assertNotNull(b2);
assertEquals(b1.getId(), b2.getId());
assertSame(b1, b2);
Invoice ci1 = b1.getClientInvoice();
Booking b3 = ci1.getBooking();
Assert.assertNotNull(b3);
Assert.assertEquals(b1.getId(), b2.getId());
Assert.assertSame(b1, b2);
assertNotNull(b3);
assertEquals(b1.getId(), b2.getId());
assertSame(b1, b2);
// cleanup
ai.setBooking(null);
@@ -49,7 +51,5 @@ public class TestOne2OneBookingInvoice extends BaseTestCase {
Ebean.save(ai);
Ebean.save(ci);
Ebean.delete(b);
}
}
@@ -0,0 +1,19 @@
package org.tests.iud;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.CascadeType.ALL;
@Entity
public class PcfCalendar extends PcfModel {
@OneToMany(cascade = ALL, orphanRemoval = true)
private List<PcfEvent> events = new ArrayList<>();
public void addEvent(PcfEvent event) {
events.add(event);
}
}
+24
View File
@@ -0,0 +1,24 @@
package org.tests.iud;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
import static javax.persistence.CascadeType.ALL;
@Entity
public class PcfCity extends PcfModel {
private final String name;
@OneToOne(cascade = ALL, optional = false)
private PcfPerson mayor;
@OneToOne(cascade = ALL, optional = false)
private PcfPerson viceMayor;
public PcfCity(String name, PcfPerson mayor, PcfPerson viceMayor) {
this.name = name;
this.mayor = mayor;
this.viceMayor = viceMayor;
}
}
@@ -0,0 +1,20 @@
package org.tests.iud;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class PcfCountry extends PcfModel {
@OneToMany(cascade = CascadeType.ALL)
private List<PcfCity> cities = new ArrayList<>();
public void addCity(PcfCity city) {
cities.add(city);
}
}
+13
View File
@@ -0,0 +1,13 @@
package org.tests.iud;
import javax.persistence.Entity;
@Entity
public class PcfEvent extends PcfModel {
final String name;
public PcfEvent(String name) {
this.name = name;
}
}
+18
View File
@@ -0,0 +1,18 @@
package org.tests.iud;
import io.ebean.Model;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
@MappedSuperclass
public class PcfModel extends Model {
@Id
long id;
@Version
long version;
}
@@ -0,0 +1,26 @@
package org.tests.iud;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.CascadeType.ALL;
@Entity
public class PcfPerson extends PcfModel {
private final String name;
@OneToMany(cascade = ALL, orphanRemoval = true)
private List<PcfCalendar> calendars = new ArrayList<>();
public PcfPerson(String name) {
this.name = name;
}
public void addCalendar(PcfCalendar calendar) {
calendars.add(calendar);
}
}
@@ -0,0 +1,46 @@
package org.tests.iud;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.Transaction;
import org.avaje.moduuid.ModUUID;
import org.junit.Test;
public class TestPersistCascade extends BaseTestCase {
@Test
public void insert() throws InterruptedException {
PcfCountry country = new PcfCountry();
for (int a = 0; a < 3; a++) {
PcfPerson mayor = createPerson();
PcfPerson viceMayor = createPerson();
country.addCity(new PcfCity("c_" + ModUUID.newShortId(), mayor, viceMayor));
}
// try (Transaction txn = DB.beginTransaction()) {
// txn.setBatchSize(20);
// country.save();
// txn.commit();
// }
country.save();
Thread.sleep(2000);
country.deletePermanent();
}
private static PcfPerson createPerson() {
PcfPerson person = new PcfPerson("per_" + ModUUID.newShortId());
for (int a = 0; a < 2; a++) {
PcfCalendar calendar = new PcfCalendar();
for (int b = 0; b < 10; b++) {
calendar.addEvent(new PcfEvent("evt_" + ModUUID.newShortId()));
}
person.addCalendar(calendar);
}
return person;
}
}
+3 -3
View File
@@ -79,9 +79,9 @@
<logger name="io.ebean.docker" level="TRACE"/>
<!-- <logger name="io.ebean.DDL" level="DEBUG"/>-->
<!-- <logger name="io.ebean.SQL" level="TRACE"/>-->
<!-- <logger name="io.ebean.TXN" level="TRACE"/>-->
<!-- <logger name="io.ebean.SUM" level="TRACE"/> -->
<logger name="io.ebean.SQL" level="TRACE"/>
<logger name="io.ebean.TXN" level="TRACE"/>
<logger name="io.ebean.SUM" level="TRACE"/>
<!-- <logger name="io.ebean.cache" level="TRACE"/> -->
<!-- <logger name="io.ebean.cache.REGION" level="TRACE"/>-->