Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3295 Clear PersistenceContext on execution of bulk updates or deletes #3301

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,10 @@ public boolean isInlineSqlUpdateLimit() {
public int forwardOnlyFetchSize() {
return queryEngine.forwardOnlyFetchSize();
}

public void clearContext() {
if (!transaction.isAutoPersistUpdates()) {
beanDescriptor.contextClear(transaction.persistenceContext());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ public void setBindLog(String bindLog) {
*/
@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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
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.
*/
Expand Down Expand Up @@ -156,6 +159,14 @@ public void logSqlBatchBind() {
*/
@Override
public void postExecute() {
if (sqlType != SqlType.SQL_INSERT && !transaction.isAutoPersistUpdates()) {
List<BeanDescriptor<?>> descriptors = server.descriptors(tableName);
if (descriptors != null) {
for (BeanDescriptor<?> descriptor : descriptors) {
descriptor.contextClear(transaction.persistenceContext());
}
}
}
if (startNanos > 0) {
persistExecute.collectSqlUpdate(label, startNanos);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,13 @@ public void contextClear(PersistenceContext pc, Object idValue) {
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).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ public void cacheNotify(TransactionEventTable.TableIUD tableIUD, CacheChangeSet
* Return the BeanDescriptors mapped to the table.
*/
public List<BeanDescriptor<?>> descriptors(String tableName) {
return tableToDescMap.get(tableName.toLowerCase());
return tableName == null ? Collections.emptyList() : tableToDescMap.get(tableName.toLowerCase());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ private <T> int executeUpdate(OrmQueryRequest<T> request, CQueryUpdate query) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public String generatedSql() {
/**
* Execute the update or delete statement returning the row count.
*/
@SuppressWarnings("resource")
public int execute() throws SQLException {
long startNano = System.nanoTime();
try {
Expand Down Expand Up @@ -109,6 +110,7 @@ private void close() {
pstmt = null;
}

@SuppressWarnings("resource")
@Override
public void profile() {
transaction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,152 @@

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())
.execute();

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)
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 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)
// ... 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
Expand All @@ -57,12 +162,69 @@ public void test() {

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 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();
Expand Down
Loading