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 3dfca446b..830b825f5 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 @@ -338,6 +338,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery private int notifyCache(int rows, boolean update) { if (rows > 0) { + beanDescriptor.contextClear(transaction.persistenceContext()); beanDescriptor.cacheUpdateQuery(update, transaction); } return rows; 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..4c6771cbb 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) { + 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..dc6d0b5de 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) { + 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-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java b/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java index 56988bc4b..f82f855d1 100644 --- a/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java +++ b/ebean-test/src/test/java/org/tests/persistencecontext/TestPersistenceContextQueryScope.java @@ -1,5 +1,6 @@ package org.tests.persistencecontext; +import io.ebean.Transaction; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.Test; @@ -7,22 +8,20 @@ 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); - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { EBasicVer bean1 = DB.find(EBasicVer.class, bean.getId()); // do an update of the name in the DB @@ -57,18 +56,118 @@ 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", bean5.getName()); + assertNotSame(bean1, bean2); + assertNotSame(bean1, bean5); assertEquals("second", bean3.getName()); DB.delete(bean3); - DB.commitTransaction(); + txn.commit(); + } + } - } finally { - DB.endTransaction(); + @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 + int rowCount = DB.update(EBasicVer.class) + .set("name", "second") + .where().idEq(bean.getId()) + .update(); + assertEquals(1, rowCount); + + // fetch the bean again... but doesn't hit DB as it + // is in the PersistenceContext which is transaction scoped + 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 + 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); + + 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 + 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... but doesn't hit DB as it + // is in the PersistenceContext which is transaction scoped + 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 + 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); + + DB.delete(bean3); + txn.commit(); } } }