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 1b5a1952d..d96245c4c 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 @@ -773,4 +773,10 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery public int forwardOnlyFetchSize() { return queryEngine.forwardOnlyFetchSize(); } + + public void clearContext() { + if (!transaction.isAutoPersistUpdates()) { + beanDescriptor.contextClear(transaction.persistenceContext()); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestOrmUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestOrmUpdate.java index 03aa874f8..9e93a064f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestOrmUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestOrmUpdate.java @@ -77,10 +77,13 @@ public final class PersistRequestOrmUpdate extends PersistRequest { */ @Override public void postExecute() { + OrmUpdateType ormUpdateType = ormUpdate.ormUpdateType(); + if (OrmUpdateType.INSERT != ormUpdateType && !transaction.isAutoPersistUpdates()) { + beanDescriptor.contextClear(transaction.persistenceContext()); + } if (startNanos > 0) { persistExecute.collectOrmUpdate(label, startNanos); } - OrmUpdateType ormUpdateType = ormUpdate.ormUpdateType(); String tableName = ormUpdate.baseTable(); if (transaction.isLogSummary()) { transaction.logSummary("{0} table[{1}] rows[{2}] bind[{3}]", ormUpdateType, tableName, rowCount, bindLog); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java index e4f0c023f..a5453420f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java @@ -3,10 +3,13 @@ package io.ebeaninternal.server.core; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.persist.BatchControl; import io.ebeaninternal.server.persist.PersistExecute; import io.ebeaninternal.server.persist.TrimLogSql; +import java.util.List; + /** * Persist request specifically for CallableSql. */ @@ -156,6 +159,14 @@ public final class PersistRequestUpdateSql extends PersistRequest { */ @Override public void postExecute() { + if (sqlType != SqlType.SQL_INSERT && !transaction.isAutoPersistUpdates()) { + List> descriptors = server.descriptors(tableName); + if (descriptors != null) { + for (BeanDescriptor descriptor : descriptors) { + descriptor.contextClear(transaction.persistenceContext()); + } + } + } if (startNanos > 0) { persistExecute.collectSqlUpdate(label, startNanos); } 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 081ae5d77..dbb803fc9 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 @@ -2056,6 +2056,13 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { pc.clear(rootBeanType, idValue); } + /** + * Clear a bean from the persistence context. + */ + public void contextClear(PersistenceContext pc) { + pc.clear(rootBeanType); + } + /** * Delete a bean from the persistence context (such that we don't fetch it in the same transaction). */ 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 240ab41a2..c4d179e6d 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 @@ -399,7 +399,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy * Return the BeanDescriptors mapped to the table. */ public List> descriptors(String tableName) { - return tableToDescMap.get(tableName.toLowerCase()); + return tableName == null ? Collections.emptyList() : tableToDescMap.get(tableName.toLowerCase()); } /** 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 969d7ca25..a0dab920a 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 @@ -74,6 +74,9 @@ public final class CQueryEngine { if (request.logSql()) { request.logSql("{0}; --bind({1}) --micros({2}) --rows({3})", query.generatedSql(), query.bindLog(), query.micros(), rows); } + if (rows > 0) { + request.clearContext(); + } return rows; } catch (SQLException e) { throw translate(request, query.bindLog(), query.generatedSql(), e); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java index 85081238b..add07241a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java @@ -61,6 +61,7 @@ final class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery /** * Execute the update or delete statement returning the row count. */ + @SuppressWarnings("resource") public int execute() throws SQLException { long startNano = System.nanoTime(); try { @@ -109,6 +110,7 @@ final class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery pstmt = null; } + @SuppressWarnings("resource") @Override public void profile() { transaction() diff --git a/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java b/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java index bfe12fd8f..eca48dd08 100644 --- a/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java +++ b/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java @@ -8,24 +8,22 @@ import org.tests.model.basic.EBasicVer; import static io.ebean.PersistenceContextScope.QUERY; import static io.ebean.PersistenceContextScope.TRANSACTION; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.*; -public class TestPersistenceContextQueryScope extends BaseTestCase { +class TestPersistenceContextQueryScope extends BaseTestCase { @Test - public void test() { + void test() { EBasicVer bean = new EBasicVer("first"); DB.save(bean); - //DB.cacheManager().setCaching(EBasicVer.class, true); - try (Transaction txn = DB.beginTransaction()) { EBasicVer bean1 = DB.find(EBasicVer.class, bean.getId()); // do an update of the name in the DB + // With #3295 this now automatically clears the persistence context for BasicVer.class int rowCount = DB.sqlUpdate("update e_basicver set name=? where id=?") .setParameter("second") .setParameter(bean.getId()) @@ -33,22 +31,21 @@ public class TestPersistenceContextQueryScope extends BaseTestCase { assertEquals(1, rowCount); - // fetch the bean again... but doesn't hit DB as it - // is in the PersistenceContext which is transaction scoped + // Prior to #3295 this returns the same bean via transaction scoped Persistence Context + // With #3295 this is now a fresh bean EBasicVer bean2 = DB.find(EBasicVer.class) .setId(bean.getId()) .setUseCache(false) // ignore L2 cache .findOne(); // QUERY scope hits the DB (doesn't use the existing transactions persistence context) - // ... also explicitly not use bean cache EBasicVer bean3 = DB.find(EBasicVer.class) .setId(bean.getId()) .setUseCache(false) // ignore L2 cache .setPersistenceContextScope(QUERY) .findOne(); - // TRANsACTION scope ... same as bean2 and does not hit the DB + // TRANSACTION scope ... same as bean2 and does not hit the DB EBasicVer bean5 = DB.find(EBasicVer.class) .setId(bean.getId()) .setUseCache(false) // ignore L2 cache @@ -57,12 +54,177 @@ public class TestPersistenceContextQueryScope extends BaseTestCase { assertEquals("first", bean.getName()); assertEquals("first", bean1.getName()); - assertEquals("first", bean2.getName()); - assertEquals("first", bean5.getName()); - assertSame(bean1, bean2); - assertSame(bean1, bean5); - + assertEquals("second", bean2.getName()); assertEquals("second", bean3.getName()); + assertEquals("second", bean5.getName()); + assertNotSame(bean1, bean2); + assertNotSame(bean1, bean5); + assertSame(bean2, bean5); + assertNotSame(bean3, bean5); + + DB.delete(bean3); + + txn.commit(); + } + } + + @Test + void ormUpdateQuery_expect_clearsContext() { + + EBasicVer bean = new EBasicVer("first"); + DB.save(bean); + + try (Transaction txn = DB.beginTransaction()) { + EBasicVer bean1 = DB.find(EBasicVer.class, bean.getId()); + + // do an update of the name in the DB + // With #3295 this now automatically clears the persistence context for BasicVer.class + int rowCount = DB.update(EBasicVer.class) + .set("name", "second") + .where().idEq(bean.getId()) + .update(); + assertEquals(1, rowCount); + + // Prior to #3295 this returns the same bean via transaction scoped Persistence Context + // With #3295 this is now a fresh bean + EBasicVer bean2 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .findOne(); + + // QUERY scope hits the DB (doesn't use the existing transactions persistence context) + EBasicVer bean3 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(QUERY) + .findOne(); + + // TRANSACTION scope ... same as bean2 and does not hit the DB + EBasicVer bean5 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(TRANSACTION) + .findOne(); + + assertEquals("first", bean.getName()); + assertEquals("first", bean1.getName()); + assertEquals("second", bean2.getName()); + assertEquals("second", bean3.getName()); + assertEquals("second", bean5.getName()); + assertNotSame(bean1, bean2); + assertNotSame(bean1, bean5); + assertSame(bean2, bean5); + assertNotSame(bean3, bean5); + + DB.delete(bean3); + txn.commit(); + } + } + + @Test + void ormUpdate_expect_clearsContext() { + + EBasicVer bean = new EBasicVer("first"); + DB.save(bean); + + try (Transaction txn = DB.beginTransaction()) { + EBasicVer bean1 = DB.find(EBasicVer.class, bean.getId()); + + // do an update of the name in the DB + // With #3295 this now automatically clears the persistence context for BasicVer.class + int rowCount = DB.createUpdate(EBasicVer.class, "update ebasicver set name = ? where id = ?") + .setParameter(1, "second") + .setParameter(2, bean.getId()) + .execute(); + assertEquals(1, rowCount); + + // fetch the bean again... + // Prior to #3295 this returns the same bean via transaction scoped Persistence Context + // With #3295 this is now a fresh bean + EBasicVer bean2 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .findOne(); + + // QUERY scope hits the DB (doesn't use the existing transactions persistence context) + EBasicVer bean3 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(QUERY) + .findOne(); + + // TRANSACTION scope ... same as bean2 and does not hit the DB + EBasicVer bean5 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(TRANSACTION) + .findOne(); + + assertEquals("first", bean.getName()); + assertEquals("first", bean1.getName()); + assertEquals("second", bean2.getName()); + assertEquals("second", bean3.getName()); + assertEquals("second", bean5.getName()); + assertNotSame(bean1, bean2); + assertNotSame(bean1, bean5); + assertSame(bean2, bean5); + assertNotSame(bean3, bean5); + + DB.delete(bean3); + txn.commit(); + } + } + + + @Test + void ormUpdateQueryWithAutoPersist_expect_clearsContext() { + + EBasicVer bean = new EBasicVer("first"); + DB.save(bean); + + try (Transaction txn = DB.beginTransaction()) { + txn.setAutoPersistUpdates(true); + EBasicVer bean1 = DB.find(EBasicVer.class, bean.getId()); + + // do an update of the name in the DB + // With #3295 this now automatically clears the persistence context for BasicVer.class + int rowCount = DB.update(EBasicVer.class) + .set("name", "second") + .where().idEq(bean.getId()) + .update(); + assertEquals(1, rowCount); + + // Prior to #3295 this returns the same bean via transaction scoped Persistence Context + // With #3295 this is now a fresh bean + EBasicVer bean2 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .findOne(); + + // QUERY scope hits the DB (doesn't use the existing transactions persistence context) + EBasicVer bean3 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(QUERY) + .findOne(); + + // TRANSACTION scope ... same as bean2 and does not hit the DB + EBasicVer bean5 = DB.find(EBasicVer.class) + .setId(bean.getId()) + .setUseCache(false) // ignore L2 cache + .setPersistenceContextScope(TRANSACTION) + .findOne(); + + assertEquals("first", bean.getName()); + assertEquals("first", bean1.getName()); + assertEquals("first", bean2.getName()); // This is still first + assertEquals("second", bean3.getName()); + assertEquals("first", bean5.getName()); // This is still first + assertSame(bean1, bean2); // Now the same + assertSame(bean1, bean5); // Now the same + assertSame(bean2, bean5); + assertNotSame(bean3, bean5); + DB.delete(bean3); txn.commit();