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

Provides lock support for Hibernate with Panache #5583

Merged
merged 1 commit into from
Nov 20, 2019
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
44 changes: 17 additions & 27 deletions docs/src/main/asciidoc/hibernate-orm-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -412,55 +412,45 @@ public void create(Parameter parameter){

== Lock management

Panache does not provide direct support for database locking, but you can do it by injecting the `EntityManager` (see first example below) in your entity (or `PanacheRepository`) and creating a specific method that will use the entity manager to lock the entity after retrieval. The entity manager can also be retrieved via `Panache.getEntityManager()` see second example below.
Panache provides direct support for database locking with your entity/repository, using `findById(Object, LockModeType)` or `find().withLock(LockModeType)`.

The following examples contain a `findByIdForUpdate` method that finds the entity by primary key then locks it. The lock will generate a `SELECT ... FOR UPDATE` query (the same principle can be used for other kinds of `find*` methods):
The following examples are for the entity pattern but the same can be used with repositories.

== First: Locking in a PanacheRepository example
== First: Locking using findById().

[source,java]
----
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// inject the EntityManager inside the entity
@Inject
EntityManager entityManager;
public class PersonEndpoint {

@GET
@Transactional
public Person findByIdForUpdate(Long id){
Person person = findById(id);
//lock with the PESSIMISTIC_WRITE mode type : this will generate a SELECT ... FOR UPDATE query
entityManager.lock(person, LockModeType.PESSIMISTIC_WRITE);
Person p = Person.findById(id, LockModeType.PESSIMISTIC_WRITE);
//do something useful, the lock will be released when the transaction ends.
return person;
}

}
----

== Second: Locking in a PanacheEntity example
== Second: Locking in a find().

[source,java]
----
@Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public Status status;

public static Person findByIdForUpdate(Long id){
// get the EntityManager inside the entity
EntityManager entityManager = Panache.getEntityManager();
public class PersonEndpoint {

Person person = findById(id);
//lock with the PESSIMISTIC_WRITE mode type : this will generate a SELECT ... FOR UPDATE query
entityManager.lock(person, LockModeType.PESSIMISTIC_WRITE);
@GET
@Transactional
public Person findByNameForUpdate(String name){
Person p = Person.find("name", name).withLock(LockModeType.PESSIMISTIC_WRITE).findOne();
//do something useful, the lock will be released when the transaction ends.
return person;
}

}
----

This will generate two select queries: one to retrieve the entity and the second to lock it. Be careful that locks are released when the transaction ends, so the method that invokes the lock query must be annotated with the `@Transactional` annotation.

We are currently evaluating adding support for lock management inside Panache. If you are interested, please visit our github issue link:https://github.com/quarkusio/quarkus/issues/2744[#2744] and contribute to the discussion.
Be careful that locks are released when the transaction ends, so the method that invokes the lock query must be annotated with the `@Transactional` annotation.

== Custom IDs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ public void visitEnd() {
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();

mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE,
"findById",
"(Ljava/lang/Object;Ljavax/persistence/LockModeType;)Ljava/lang/Object;",
null,
null);
mv.visitParameter("id", 0);
mv.visitParameter("lockModeType", 0);
mv.visitCode();
mv.visitIntInsn(Opcodes.ALOAD, 0);
mv.visitIntInsn(Opcodes.ALOAD, 1);
mv.visitIntInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
daoBinaryName,
"findById",
"(Ljava/lang/Object;Ljavax/persistence/LockModeType;)" + entitySignature, false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
super.visitEnd();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.stream.Stream;

import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.LockModeType;
import javax.persistence.Transient;

import io.quarkus.hibernate.orm.panache.runtime.JpaOperations;
Expand Down Expand Up @@ -98,6 +99,18 @@ public static <T extends PanacheEntityBase> T findById(Object id) {
throw JpaOperations.implementationInjectionMissing();
}

/**
* Find an entity of this type by ID and lock it.
*
* @param id the ID of the entity to find.
* @param lockModeType the locking strategy to be used when retrieving the entity.
* @return the entity found, or <code>null</code> if not found.
*/
@GenerateBridge(targetReturnTypeErased = true)
public static <T extends PanacheEntityBase> T findById(Object id, LockModeType lockModeType) {
throw JpaOperations.implementationInjectionMissing();
}

/**
* Find entities using a query, with optional indexed parameters.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.stream.Stream;

import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;

Expand Down Expand Up @@ -114,6 +115,14 @@ public interface PanacheQuery<Entity> {
*/
public Page page();

/**
* Define the locking strategy used for this query.
*
* @param lockModeType the locking strategy to be used for this query.
* @return this query, modified
*/
public <T extends Entity> PanacheQuery<T> withLock(LockModeType lockModeType);

// Results

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.Map;
import java.util.stream.Stream;

import javax.persistence.LockModeType;

import io.quarkus.hibernate.orm.panache.runtime.JpaOperations;
import io.quarkus.panache.common.Parameters;
import io.quarkus.panache.common.Sort;
Expand Down Expand Up @@ -99,6 +101,18 @@ public default Entity findById(Id id) {
throw JpaOperations.implementationInjectionMissing();
}

/**
* Find an entity of this type by ID and lock it.
*
* @param id the ID of the entity to find.
* @param lockModeType the locking strategy to be used when retrieving the entity.
* @return the entity found, or <code>null</code> if not found.
*/
@GenerateBridge(targetReturnTypeErased = true)
public default Entity findById(Id id, LockModeType lockModeType) {
throw JpaOperations.implementationInjectionMissing();
}

/**
* Find entities using a query, with optional indexed parameters.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.transaction.SystemException;
Expand Down Expand Up @@ -196,6 +197,10 @@ public static Object findById(Class<?> entityClass, Object id) {
return getEntityManager().find(entityClass, id);
}

public static Object findById(Class<?> entityClass, Object id, LockModeType lockModeType) {
return getEntityManager().find(entityClass, id, lockModeType);
}

public static PanacheQuery<?> find(Class<?> entityClass, String query, Object... params) {
return find(entityClass, query, null, params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;

import io.quarkus.hibernate.orm.panache.PanacheQuery;
Expand Down Expand Up @@ -90,6 +91,12 @@ public Page page() {
return page;
}

@Override
public <T extends Entity> PanacheQuery<T> withLock(LockModeType lockModeType) {
jpaQuery.setLockMode(lockModeType);
return (PanacheQuery<T>) this;
}

// Results

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.stream.Stream;

import javax.inject.Inject;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceException;
Expand Down Expand Up @@ -87,6 +88,10 @@ public String testModel() {
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));

persons = Person.find("name = ?1", "stef").withLock(LockModeType.PESSIMISTIC_READ).list();
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));

persons = Person.list("name = ?1", "stef");
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));
Expand Down Expand Up @@ -133,6 +138,10 @@ public String testModel() {
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());

byId = Person.findById(person.id, LockModeType.PESSIMISTIC_READ);
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());

person.delete();
Assertions.assertEquals(0, Person.count());

Expand Down Expand Up @@ -359,6 +368,10 @@ public String testModelDao() {
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));

persons = personDao.find("name = ?1", "stef").withLock(LockModeType.PESSIMISTIC_READ).list();
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));

persons = personDao.list("name = ?1", "stef");
Assertions.assertEquals(1, persons.size());
Assertions.assertEquals(person, persons.get(0));
Expand Down Expand Up @@ -404,6 +417,9 @@ public String testModelDao() {
Person byId = personDao.findById(person.id);
Assertions.assertEquals(person, byId);

byId = personDao.findById(person.id, LockModeType.PESSIMISTIC_READ);
Assertions.assertEquals(person, byId);

personDao.delete(person);
Assertions.assertEquals(0, personDao.count());

Expand Down