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 @@ -70,4 +70,8 @@ public boolean hasQueryExecutionToBeAddedToStatistics() {
return true;
}

@Override
public boolean upgradeLocks() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,12 @@ default boolean hasQueryExecutionToBeAddedToStatistics() {
return false;
}

/**
* Does this query return objects that might be already cached
* by the session, whose lock mode may need upgrading
*/
default boolean upgradeLocks(){
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ && getEntityKey().getIdentifier().equals( executionContext.getEntityId() ) ) {
}

private void upgradeLockMode(RowProcessingState rowProcessingState) {
if ( lockMode != LockMode.NONE ) {
if ( lockMode != LockMode.NONE && rowProcessingState.upgradeLocks() ) {
final EntityEntry entry =
rowProcessingState.getSession().getPersistenceContextInternal()
.getEntry( entityInstance );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,9 @@ public Initializer resolveInitializer(NavigablePath path) {
public boolean hasCollectionInitializers() {
return this.initializers.hasCollectionInitializers();
}

@Override
public boolean upgradeLocks() {
return executionContext.upgradeLocks();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.hibernate.orm.test.locking;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.stream.Stream;

import org.hibernate.LockMode;

import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Version;

@DomainModel(annotatedClasses = {
OptimisticAndPessimisticLockTest.EntityA.class
})
@SessionFactory
@TestForIssue(jiraKey = "HHH-16461")
public class OptimisticAndPessimisticLockTest {

public Stream<LockMode> pessimisticLockModes() {
return Stream.of(LockMode.UPGRADE_NOWAIT, LockMode.PESSIMISTIC_WRITE, LockMode.PESSIMISTIC_READ, LockMode.PESSIMISTIC_FORCE_INCREMENT);
}

@ParameterizedTest
@MethodSource(value = "pessimisticLockModes")
public void upgradeFromOptimisticToPessimisticLock(LockMode pessimisticLockMode, SessionFactoryScope scope) {
Integer id = scope.fromTransaction( session -> {
EntityA entityA1 = new EntityA();
entityA1.setPropertyA( 1 );
session.persist( entityA1 );
return entityA1.getId();
} );
scope.inTransaction( session -> {
EntityA entityA1 = session.find( EntityA.class, id );

// Do a concurrent change that will update the @Version property
scope.inTransaction( session2 -> {
var concurrentEntityA1 = session2.find( EntityA.class, id );
concurrentEntityA1.setPropertyA( concurrentEntityA1.getPropertyA() + 1 );
} );

// Refresh the entity with concurrent changes and upgrade the lock
session.refresh( entityA1, pessimisticLockMode );

entityA1.setPropertyA( entityA1.getPropertyA() * 2 );
} );
scope.inTransaction( session -> {
EntityA entityA1 = session.find( EntityA.class, id );
assertThat( entityA1.getPropertyA() ).isEqualTo( ( 1 + 1 ) * 2 );
} );
}

@Entity(name = "EntityA")
public static class EntityA {

@Id
@GeneratedValue
Integer id;

@Version
long version;

int propertyA;

public EntityA() {
}

public EntityA(Integer id) {
this.id = id;
}

public Integer getId() {
return id;
}

public long getVersion() {
return version;
}

public int getPropertyA() {
return propertyA;
}

public void setPropertyA(int propertyA) {
this.propertyA = propertyA;
}
}
}