Compare commits

...
Author SHA1 Message Date
rob bygrave 1a2ca63d65 [maven-release-plugin] prepare release ebean-11.15.5 2018-05-06 20:50:40 +12:00
rob bygrave 33031d1c69 #1369 - BeanListener and/or BeanPersistAdapter not fired on delete of children when cascaded 2018-05-06 20:21:10 +12:00
Roland PramlandRob Bygrave 49d9d88fbe FIX: load all props from cache when inheritance is involved (#1366) 2018-04-27 14:44:18 +12:00
Rob Bygrave 756f867ed2 #1365 - Regression: property ebean.namingConvention=io.ebean.config.MatchingNamingConvention ... no longer works 2018-04-18 21:24:37 +12:00
Rob Bygrave 12108ada38 #1363 - Update ebean-migration to 11.5.4 2018-04-18 20:47:59 +12:00
rob bygrave fb2fdc9b53 Merge branch 'master' of github.com:ebean-orm/ebean 2018-04-12 20:44:57 +12:00
rob bygrave d570971fd1 #1363 - Update ebean-migration to 11.5.2 2018-04-12 20:44:39 +12:00
André CamiloandRob Bygrave a8434f22ff Inheritance - Add support for @DiscriminatorValue to be optional (#1361) 2018-04-12 20:38:09 +12:00
rob bygrave b0d3921264 [maven-release-plugin] prepare for next development iteration 2018-04-05 22:23:18 +12:00
rob bygrave 4167dfc4af [maven-release-plugin] prepare release ebean-11.15.4 2018-04-05 22:23:03 +12:00
rob bygrave e49a0c9ffb #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints
Additional tests
2018-04-05 21:48:54 +12:00
rob bygrave dd6f223ae1 #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints 2018-04-05 21:35:29 +12:00
Roman ParshikovandRob Bygrave 05aff53241 Fix Maven links in README (#1358) 2018-04-03 21:17:46 +12:00
Rob Bygrave 9a3362e842 [maven-release-plugin] prepare for next development iteration 2018-03-29 14:35:40 +13:00
27 changed files with 734 additions and 35 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
## Current versions
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
+3 -3
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.3</version>
<version>11.15.5</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.3</tag>
<tag>ebean-11.15.5</tag>
</scm>
<profiles>
@@ -130,7 +130,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.5.1</version>
<version>11.5.4</version>
</dependency>
<dependency>
+15 -3
View File
@@ -23,6 +23,7 @@ public interface Transaction extends AutoCloseable {
* This returns the current transaction for the 'default server'. If you are using
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
* </p>
*
* @see Ebean#currentTransaction()
* @see EbeanServer#currentTransaction()
*/
@@ -136,6 +137,18 @@ public interface Transaction extends AutoCloseable {
*/
void rollback(Throwable e) throws PersistenceException;
/**
* Set when we want nested transactions to use Savepoint's.
* <p>
* This means that for a nested transaction:
* <ul>
* <li>begin transaction maps to creating a savepoint</li>
* <li>commit transaction maps to releasing a savepoint</li>
* <li>rollback transaction maps to rollback a savepoint</li>
* </ul>
*/
void setNestedUseSavepoint();
/**
* Mark the transaction for rollback only.
*/
@@ -151,7 +164,6 @@ public interface Transaction extends AutoCloseable {
*/
void end();
/**
* Synonym for end() to support AutoClosable.
*/
@@ -227,7 +239,7 @@ public interface Transaction extends AutoCloseable {
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
* </p>
* <p>
*
* <pre>{@code
*
* // assume Customer has L2 bean caching enabled ...
@@ -309,7 +321,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* Example: batch processing executing every 3 rows
* </p>
* <p>
*
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
@@ -2871,7 +2871,7 @@ public class ServerConfig {
}
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingconvention", null);
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingConvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -319,4 +319,9 @@ public interface SpiTransaction extends Transaction {
* Return the profile location for this transaction.
*/
ProfileLocation getProfileLocation();
/**
* Return true when nested transactions should create Savepoints.
*/
boolean isNestedUseSavepoint();
}
@@ -21,7 +21,7 @@ import java.sql.SQLException;
/**
* Proxy for an underlying SpiTransaction (most of the API).
*/
abstract class SpiTransactionProxy implements SpiTransaction {
public abstract class SpiTransactionProxy implements SpiTransaction {
protected SpiTransaction transaction;
@@ -50,6 +50,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.isRollbackOnly();
}
@Override
public void setNestedUseSavepoint() {
transaction.setNestedUseSavepoint();
}
@Override
public boolean isNestedUseSavepoint() {
return transaction.isNestedUseSavepoint();
}
@Override
public long profileOffset() {
return transaction.profileOffset();
@@ -15,6 +15,10 @@ public class CachedBeanDataToBean {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
BeanProperty idProperty = desc.getIdProperty();
if (desc.getInheritInfo() != null) {
desc = desc.getInheritInfo().readType(bean.getClass()).desc();
}
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
@@ -1723,7 +1723,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* associated L2 bean caching.
*/
public boolean isDeleteByStatement() {
return deleteRecurseSkippable && !isBeanCaching();
return persistListener == null
&& persistController == null
&& deleteRecurseSkippable && !isBeanCaching();
}
public boolean isDeleteByBulk() {
return persistListener == null
&& persistController == null
&& propertiesManyToMany.length == 0;
}
/**
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.parse.DeployInheritInfo;
import io.ebeaninternal.server.query.SqlTreeProperties;
import java.lang.reflect.Modifier;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -291,7 +292,7 @@ public class InheritInfo {
* Return true if this is considered a concrete type in the inheritance hierarchy.
*/
public boolean isConcrete() {
return discriminatorValue != null;
return !Modifier.isAbstract(type.getModifiers());
}
/**
@@ -126,6 +126,8 @@ public class DeployInherit {
DiscriminatorValue dv = AnnotationUtil.findAnnotation(cls, DiscriminatorValue.class); // do not search recursive
if (dv != null) {
info.setDiscriminatorValue(dv.value());
} else {
info.setDiscriminatorValue(cls.getSimpleName());
}
return info;
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebeaninternal.server.deploy.InheritInfo;
import java.lang.reflect.Modifier;
import javax.persistence.DiscriminatorType;
import java.sql.Types;
import java.util.ArrayList;
@@ -64,7 +65,7 @@ public class DeployInheritInfo {
* Return true if this is abstract node.
*/
public boolean isAbstract() {
return (discriminatorObjectValue == null);
return Modifier.isAbstract(type.getModifiers());
}
/**
@@ -205,7 +206,7 @@ public class DeployInheritInfo {
}
private void appendDiscriminator(List<Object> list) {
if (discriminatorObjectValue != null) {
if (!isAbstract()) {
list.add(discriminatorObjectValue);
}
for (DeployInheritInfo child : children) {
@@ -588,10 +588,18 @@ public final class DefaultPersister implements Persister {
}
}
private void deleteList(List<?> beanList, Transaction t, boolean softDelete) {
private void deleteList(List<?> beanList, SpiTransaction t, boolean softDelete, boolean children) {
if (children) {
t.depth(-1);
t.checkBatchEscalationOnCollection();
}
for (Object aBeanList : beanList) {
deleteRecurse((EntityBean) aBeanList, t, softDelete);
}
if (children) {
t.flushBatchOnCollection();
t.depth(+1);
}
}
/**
@@ -675,7 +683,7 @@ public final class DefaultPersister implements Persister {
t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values");
}
List<?> beanList = server.findList(q, t);
deleteList(beanList, t, softDelete);
deleteList(beanList, t, softDelete, false);
return beanList.size();
} else {
@@ -1012,7 +1020,7 @@ public final class DefaultPersister implements Persister {
// cascade delete the beans in the collection
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
if (!softDelete || targetDesc.isSoftDelete()) {
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
if (targetDesc.isDeleteByStatement()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
SqlUpdate sqlDelete = intRow.createDelete(server, softDelete);
@@ -1037,13 +1045,13 @@ public final class DefaultPersister implements Persister {
*/
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds, boolean softDelete) {
if (targetDesc.propertiesManyToMany().length > 0) {
if (!targetDesc.isDeleteByBulk()) {
// convert into a list of reference objects and perform delete by object
List<Object> refList = new ArrayList<>(childIds.size());
for (Object id : childIds) {
refList.add(targetDesc.createReference(id, null));
}
deleteList(refList, t, softDelete);
deleteList(refList, t, softDelete, true);
} else {
// perform delete by statement if possible
@@ -231,6 +231,16 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
public void markNotQueryOnly() {
}
@Override
public boolean isNestedUseSavepoint() {
return false;
}
@Override
public void setNestedUseSavepoint() {
}
@Override
public boolean isReadOnly() {
if (!isActive()) {
@@ -77,6 +77,8 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected boolean rollbackOnly;
protected boolean nestedUseSavepoint;
/**
* The underlying Connection.
*/
@@ -1117,6 +1119,16 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.rollbackOnly = true;
}
@Override
public boolean isNestedUseSavepoint() {
return nestedUseSavepoint;
}
@Override
public void setNestedUseSavepoint() {
this.nestedUseSavepoint = true;
}
/**
* Rollback the transaction.
*/
@@ -37,6 +37,16 @@ class NoTransaction implements SpiTransaction {
return null;
}
@Override
public boolean isNestedUseSavepoint() {
return false;
}
@Override
public void setNestedUseSavepoint() {
}
@Override
public boolean isActive() {
// always false
@@ -0,0 +1,107 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionProxy;
import io.ebeaninternal.server.lib.util.Str;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
/**
* A nested transaction that represents a Savepoint.
*/
class SavepointTransaction extends SpiTransactionProxy {
private static final int STATE_COMMITTED = 1;
private static final int STATE_ROLLED_BACK = 2;
private final TransactionManager manager;
private final Savepoint savepoint;
private final Connection connection;
private final String logPrefix;
private boolean rollbackOnly;
private int state;
SavepointTransaction(SpiTransaction transaction, TransactionManager manager) throws SQLException {
this.manager = manager;
this.transaction = transaction;
this.connection = transaction.getInternalConnection();
this.savepoint = connection.setSavepoint();
int savepointId = savepoint.getSavepointId();
this.logPrefix = "txn[" + transaction.getId() + "-sp" + savepointId + "] ";
}
@Override
public String getLogPrefix() {
return logPrefix;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
}
@Override
public void setRollbackOnly() {
this.rollbackOnly = true;
}
@Override
public void commit() {
if (rollbackOnly) {
rollbackSavepoint(null);
} else {
commitSavepoint();
}
}
@Override
public void rollback() throws PersistenceException {
rollbackSavepoint(null);
}
@Override
public void rollback(Throwable e) throws PersistenceException {
rollbackSavepoint(e);
}
private void commitSavepoint() {
try {
connection.releaseSavepoint(savepoint);
state = STATE_COMMITTED;
manager.notifyOfCommit(this);
} catch (SQLException e) {
throw new PersistenceException("Error trying to commit/release Savepoint", e);
}
}
private void rollbackSavepoint(Throwable cause) throws PersistenceException {
try {
connection.rollback(savepoint);
state = STATE_ROLLED_BACK;
manager.notifyOfRollback(this, cause);
} catch (SQLException e) {
throw new PersistenceException("Error trying to rollback Savepoint", e);
}
}
@Override
public void end() {
if (state == 0) {
rollback();
}
}
@Override
public void close() {
end();
}
}
@@ -582,27 +582,42 @@ public class TransactionManager implements SpiTransactionManager {
txScope = initTxScope(txScope);
boolean setToScope = false;
ScopedTransaction txnContainer = getActiveScoped();
boolean setToScope;
boolean nestedSavepoint;
SpiTransaction transaction;
if (txnContainer == null) {
setToScope = true;
txnContainer = createScopedTransaction();
setToScope = true;
transaction = null;
nestedSavepoint = false;
} else {
setToScope = false;
transaction = txnContainer.current();
nestedSavepoint = transaction.isNestedUseSavepoint();
}
SpiTransaction transaction = txnContainer.current();
TxType type = txScope.getType();
boolean createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = createTransaction(true, txScope.getIsolationLevel());
initNewTransaction(transaction, txScope);
boolean createTransaction;
if (nestedSavepoint && (type == TxType.REQUIRED || type == TxType.REQUIRES_NEW)) {
createTransaction = true;
transaction = createSavepoint(transaction, this);
} else {
createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = createTransaction(true, txScope.getIsolationLevel());
initNewTransaction(transaction, txScope);
}
}
}
@@ -613,6 +628,14 @@ public class TransactionManager implements SpiTransactionManager {
return txnContainer;
}
private SpiTransaction createSavepoint(SpiTransaction transaction, TransactionManager manager) {
try {
return new SavepointTransaction(transaction, manager);
} catch (SQLException e) {
throw new PersistenceException("Error creating nested Savepoint Transaction", e);
}
}
private void initNewTransaction(SpiTransaction transaction, TxScope txScope) {
if (txScope.isSkipCache()) {
@@ -65,6 +65,8 @@ public class ServerConfigTest {
props.setProperty("notifyL2CacheInForeground", "true");
props.setProperty("idType", "SEQUENCE");
props.setProperty("mappingLocations", "classpath:/foo;bar");
props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention");
serverConfig.loadFromProperties(props);
@@ -72,6 +74,7 @@ public class ServerConfigTest {
assertTrue(serverConfig.isNotifyL2CacheInForeground());
assertTrue(serverConfig.isDbOffline());
assertTrue(serverConfig.isAutoReadOnlyDataSource());
assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
assertEquals(IdType.SEQUENCE, serverConfig.getIdType());
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
+39
View File
@@ -0,0 +1,39 @@
package org.tests.cache;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.tests.model.basic.OCachedInhChildA;
import org.tests.model.basic.OCachedInhRoot;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
/**
* Test class testing deleting/invalidating of cached beans
*/
public class TestCacheManyToOne extends BaseTestCase {
@Test
public void crudInvoice() {
OCachedInhChildA bean = new OCachedInhChildA();
bean.setName("Roland");
bean.setChildAData("ChildData");
Ebean.save(bean);
Ebean.find(OCachedInhRoot.class).select("id").where().idIn(bean.getId()).findEach(bean0 -> {
assertNotNull(bean0);
assertThat(bean0.getName()).isEqualTo("Roland");
assertThat(((OCachedInhChildA)bean0).getChildAData()).isEqualTo("ChildData");
});
Ebean.find(OCachedInhRoot.class).select("id").where().idIn(bean.getId()).findEach(bean1 -> {
assertNotNull(bean1);
assertThat(bean1.getName()).isEqualTo("Roland");
// this will fail if the bean is loaded from cache and the wrong discriminator is used.
assertThat(((OCachedInhChildA)bean1).getChildAData()).isEqualTo("ChildData");
});
}
}
@@ -0,0 +1,51 @@
package org.tests.delete;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class DcDetail {
@Id
long id;
@ManyToOne
DcMaster master;
String description;
long version;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public DcMaster getMaster() {
return master;
}
public void setMaster(DcMaster master) {
this.master = master;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,28 @@
package org.tests.delete;
import io.ebean.event.AbstractBeanPersistListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DcListener extends AbstractBeanPersistListener {
static List<Object> deleted = Collections.synchronizedList(new ArrayList<>());
@Override
public boolean isRegisterFor(Class<?> cls) {
return cls.equals(DcDetail.class);
}
@Override
public void deleted(Object bean) {
deleted.add(bean);
}
static List<Object> deletedBeans() {
List<Object> copy = new ArrayList<>(deleted);
deleted.clear();
return copy;
}
}
@@ -0,0 +1,59 @@
package org.tests.delete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import java.util.List;
@Entity
public class DcMaster {
@Id
long id;
String name;
@OneToMany(mappedBy = "master", cascade = CascadeType.ALL)
List<DcDetail> details;
@Version
long version;
public DcMaster(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<DcDetail> getDetails() {
return details;
}
public void setDetails(List<DcDetail> details) {
this.details = details;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,51 @@
package org.tests.delete;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestDeleteCascadeWithListener extends BaseTestCase {
@Test
public void test() {
DcMaster m0 = new DcMaster("m0");
m0.getDetails().add(new DcDetail());
m0.getDetails().add(new DcDetail());
m0.getDetails().add(new DcDetail());
Ebean.save(m0);
DcMaster found = Ebean.find(DcMaster.class, m0.getId());
LoggedSqlCollector.start();
Ebean.delete(found);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("select t0.id from dc_detail t0 where master_id=?");
assertThat(sql.get(1)).contains("delete from dc_detail where id=?");
assertThat(sql.get(3)).contains("delete from dc_detail where id=?");
assertThat(sql.get(4)).contains("delete from dc_master where id=? and version=?");
awaitListenerPropagation();
List<Object> beans = DcListener.deletedBeans();
assertThat(beans).hasSize(3);
}
private void awaitListenerPropagation() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,30 @@
package org.tests.model.basic;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
/**
* Child bean A
*/
@Entity
@DiscriminatorValue("childA")
public class OCachedInhChildA extends OCachedInhRoot {
String childAData;
/**
* @return the childAData
*/
public String getChildAData() {
return childAData;
}
/**
* @param childAData
* the childAData to set
*/
public void setChildAData(String childAData) {
this.childAData = childAData;
}
}
@@ -0,0 +1,29 @@
package org.tests.model.basic;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
/**
* Child bean B
*/
@Entity
@DiscriminatorValue("childB")
public class OCachedInhChildB extends OCachedInhRoot {
String childBData;
/**
* @return the childBData
*/
public String getChildBData() {
return childBData;
}
/**
* @param childBData
* the childBData to set
*/
public void setChildBData(String childBData) {
this.childBData = childBData;
}
}
@@ -0,0 +1,44 @@
package org.tests.model.basic;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import io.ebean.annotation.Cache;
/**
* Cached entity for inheritance.
*
* @author Roland Praml, FOCONIS AG
*/
@Entity
@Table(name = "o_cached_inherit")
@Inheritance
@Cache
public abstract class OCachedInhRoot {
@Id
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -5,11 +5,13 @@ import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Transaction;
import io.ebean.TxScope;
import org.junit.Test;
import org.tests.model.basic.EBasic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TestNestedSubTransaction extends BaseTestCase {
@@ -56,6 +58,156 @@ public class TestNestedSubTransaction extends BaseTestCase {
}
}
@Test
public void nestedUseSavepoint_doubleNested_rollbackCommit() {
EbeanServer server = server();
EBasic bean = new EBasic("start2");
try (Transaction txn0 = server.beginTransaction()) {
txn0.setRollbackOnly();
txn0.setNestedUseSavepoint();
server.save(bean);
try (Transaction txn1 = server.beginTransaction()) {
bean.setName("updateNested");
server.save(bean);
try (Transaction txn2 = server.beginTransaction()) {
bean.setName("barney");
Ebean.save(bean);
txn2.commit();
}
txn1.rollback();
}
EBasic fresh = server.find(EBasic.class)
.setId(bean.getId())
.setPersistenceContextScope(PersistenceContextScope.QUERY)
.findOne();
assertNotNull(fresh);
assertThat(fresh.getName()).isEqualTo("start2");
}
}
@Test
public void nestedUseSavepoint_doubleNested_commitRollback() {
EbeanServer server = server();
EBasic bean = new EBasic("start3");
try (Transaction txn0 = server.beginTransaction()) {
txn0.setRollbackOnly();
txn0.setNestedUseSavepoint();
server.save(bean);
try (Transaction txn1 = server.beginTransaction()) {
bean.setName("updateNested3");
server.save(bean);
try (Transaction txn2 = server.beginTransaction()) {
bean.setName("barney3");
Ebean.save(bean);
txn2.rollback();
}
txn1.commit();
}
EBasic fresh = server.find(EBasic.class)
.setId(bean.getId())
.setPersistenceContextScope(PersistenceContextScope.QUERY)
.findOne();
assertNotNull(fresh);
assertThat(fresh.getName()).isEqualTo("updateNested3");
}
}
@Test
public void nestedUseSavepoint_nested_RequiresNew() {
EbeanServer server = server();
EBasic bean = new EBasic("start4");
try (Transaction txn0 = server.beginTransaction()) {
txn0.setRollbackOnly();
txn0.setNestedUseSavepoint();
server.save(bean);
try (Transaction txn1 = server.beginTransaction(TxScope.requiresNew())) {
bean.setName("updateNested4");
server.save(bean);
txn1.commit();
}
EBasic fresh = server.find(EBasic.class)
.setId(bean.getId())
.setPersistenceContextScope(PersistenceContextScope.QUERY)
.findOne();
assertNotNull(fresh);
assertThat(fresh.getName()).isEqualTo("updateNested4");
}
EBasic after = server.find(EBasic.class)
.setId(bean.getId())
.findOne();
assertNull(after);
}
@Test
public void nestedUseSavepoint() {
EbeanServer server = server();
EBasic bean = new EBasic("start1");
try (Transaction txn0 = server.beginTransaction()) {
txn0.setRollbackOnly();
txn0.setNestedUseSavepoint();
server.save(bean);
try (Transaction txn1 = server.beginTransaction()) {
bean.setName("updateNested");
server.save(bean);
txn1.commit();
}
EBasic fresh = server.find(EBasic.class, bean.getId());
assertNotNull(fresh);
assertThat(fresh.getName()).isEqualTo("updateNested");
try (Transaction txn2 = server.beginTransaction()) {
bean.setName("barney");
Ebean.save(bean);
txn2.rollback();
}
fresh = server.find(EBasic.class)
.setId(bean.getId())
.setPersistenceContextScope(PersistenceContextScope.QUERY)
.findOne();
assertNotNull(fresh);
assertThat(fresh.getName()).isEqualTo("updateNested");
} finally {
cleanup(bean);
}
}
private void cleanup(EBasic bean) {
Ebean.delete(EBasic.class, bean.getId());
}