Skip to content
Closed
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 @@ -13,6 +13,7 @@
import org.hibernate.engine.internal.Nullability;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
Expand Down Expand Up @@ -42,10 +43,12 @@
import java.util.Arrays;

import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
import static org.hibernate.engine.internal.ManagedTypeHelper.asManagedEntity;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity;
import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker;
import static org.hibernate.engine.internal.Versioning.getVersion;
import static org.hibernate.engine.internal.Versioning.incrementVersion;
Expand Down Expand Up @@ -223,6 +226,8 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi
else {
final Object entity = event.getEntity();
processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes );
processIfManagedEntity( entity, DefaultReactiveFlushEntityEventListener::useTracker );

final EventSource source = event.getSession();
source.getFactory()
.getCustomEntityDirtinessStrategy()
Expand All @@ -235,6 +240,10 @@ private boolean isUpdateNecessary(final FlushEntityEvent event, final boolean mi
}
}

private static void useTracker(final ManagedEntity entity) {
entity.$$_hibernate_setUseTracker( true );
}

private boolean scheduleUpdate(final FlushEntityEvent event) {
final EntityEntry entry = event.getEntityEntry();
final EventSource session = event.getSession();
Expand Down Expand Up @@ -555,7 +564,7 @@ private static int[] getDirtyProperties(FlushEntityEvent event) {
}
else {
final Object entity = event.getEntity();
return isSelfDirtinessTracker( entity )
return isSelfDirtinessTracker( entity ) && asManagedEntity( entity ).$$_hibernate_useTracker()
? getDirtyPropertiesFromSelfDirtinessTracker( asSelfDirtinessTracker( entity ), event )
: getDirtyPropertiesFromCustomEntityDirtinessStrategy( event );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.it.dirtychecking;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Fruit {
@Id
private int id;

// Dirty checking should not be confused by this initialization.
private String name = "Banana";

public int getId() {
return id;
}

public Fruit setId(final int id) {
this.id = id;
return this;
}

public String getName() {
return name;
}

public Fruit setName(final String name) {
this.name = name;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.it;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletionStage;

import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.reactive.it.dirtychecking.Fruit;

import org.hibernate.testing.SqlStatementTracker;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.vertx.junit5.VertxTestContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;

public class DirtyCheckingIT extends BaseReactiveIT {

private static SqlStatementTracker sqlTracker;

@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();

// Construct a tracker that collects query statements via the SqlStatementLogger framework.
// Pass in configuration properties to hand off any actual logging properties
sqlTracker = new SqlStatementTracker( DirtyCheckingIT::updateQueryFilter, configuration.getProperties() );
return configuration;
}

private static boolean updateQueryFilter(String s) {
return s.toLowerCase().startsWith( "update " );
}

@BeforeEach
public void clearTracker() {
sqlTracker.clear();
}

@Override
protected void addServices(StandardServiceRegistryBuilder builder) {
sqlTracker.registerService( builder );
}

@Override
protected Collection<Class<?>> annotatedEntities() {
return List.of( Fruit.class );
}

@Override
protected CompletionStage<Void> cleanDb() {
// There's only one test, so we don't need to clean the db.
// This prevents extra queries in the log when the test is over.
return voidFuture();
}

@Test
public void testDirtyCheck(VertxTestContext context) {
test(
context,
getMutinySessionFactory()
.withTransaction( s -> s.persist( new Fruit().setId( 5 ).setName( "Apple" ) ) )
.chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( Fruit.class, 5 ) ) )
.invoke( fruit -> {
assertThat( fruit ).hasFieldOrPropertyWithValue( "name", "Apple" );
assertThat( sqlTracker.getLoggedQueries() )
.as( "Dirty field detection failed, unexpected SQL mutation query" )
.isEmpty();
} )
);
}
}