Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 750 Vote(s) - 3.49 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to enable LockModeType.PESSIMISTIC_WRITE when looking up entities with Spring Data JPA?

#1
How can I achieve the equivalent of this code:

tx.begin();
Widget w = em.find(Widget.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();

... but using Spring and Spring-Data-JPA annotations?

The basis of my existing code is:

@Service
@Transactional(readOnly = true)
public class WidgetServiceImpl implements WidgetService
{
/** The spring-data widget repository which extends CrudRepository<Widget, Long>. */
@Autowired
private WidgetRepository repo;

@Transactional(readOnly = false)
public void updateWidgetStock(Long id, int count)
{
Widget w = this.repo.findOne(id);
w.decrementBy(4);
this.repo.save(w);
}
}

But I don't know how to specify that everything in the `updateWidgetStock` method should be done with a pessimistic lock set.

There is a Spring Data JPA annotation `org.springframework.data.jpa.repository.Lock` which allows you to set a `LockModeType`, but I don't know if it's valid to put it on the `updateWidgetStock` method. It sounds more like an annotation on the `WidgetRepository`, because the Javadoc says:

> org.springframework.data.jpa.repository <br>
> @Target(value=METHOD) <br>
> @Retention(value=RUNTIME) <br>
> @Documented <br>
> public @interface Lock <br>
> Annotation used to specify the LockModeType to be used when executing the query. It will be evaluated when using Query on a query method or if you derive the query from the method name.

... so that doesn't seem to be helpful.

How can I make my `updateWidgetStock()` method execute with `LockModeType.PESSIMISTIC_WRITE` set?
Reply

#2
**If you are able to use Spring Data 1.6 or greater than ignore this answer and refer to Oliver's answer.**

The Spring Data pessimistic `@Lock` annotations only apply (as you pointed out) to queries. There are not annotations I know of which can affect an entire transaction. You can either create a `findByOnePessimistic` method which calls `findByOne` with a pessimistic lock or you can change `findByOne` to always obtain a pessimistic lock.

If you wanted to implement your own solution you probably could. Under the hood the `@Lock` annotation is processed by `LockModePopulatingMethodIntercceptor` which does the following:

TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);

You could create some static lock manager which had a `ThreadLocal<LockMode>` member variable and then have an aspect wrapped around every method in every repository which called `bindResource` with the lock mode set in the `ThreadLocal`. This would allow you to set the lock mode on a per-thread basis. You could then create your own `@MethodLockMode` annotation which would wrap the method in an aspect which sets the thread-specific lock mode before running the method and clears it after running the method.

Reply

#3
If you don't want to override standard `findOne()` method, you can acquire a lock in your custom method by using `select ... for update` query just like this:

/**
* Repository for Wallet.
*/
public interface WalletRepository extends CrudRepository<Wallet, Long>, JpaSpecificationExecutor<Wallet> {

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select w from Wallet w where w.id = :id")
Wallet findOneForUpdate(@Param("id") Long id);
}

However, if you are using PostgreSQL, things can get a little complicated when you want to set lock timeout to avoid deadlocks. PostgreSQL ignores standard property `javax.persistence.lock.timeout` set in JPA properties or in `@QueryHint` annotation.

The only way I could get it working was to create a custom repository and set timeout manually before locking an entity. It's not nice but at least it's working:

public class WalletRepositoryImpl implements WalletRepositoryCustom {

@PersistenceContext
private EntityManager em;


@Override
public Wallet findOneForUpdate(Long id) {
// explicitly set lock timeout (necessary in PostgreSQL)
em.createNativeQuery("set local lock_timeout to '2s';").executeUpdate();

Wallet wallet = em.find(Wallet.class, id);

if (wallet != null) {
em.lock(wallet, LockModeType.PESSIMISTIC_WRITE);
}

return wallet;
}
}
Reply

#4
`@Lock` is supported on CRUD methods as of version 1.6 of Spring Data JPA (in fact, there's already a [milestone][1] available). See this [ticket][2] for more details.

With that version you simply declare the following:
```
interface WidgetRepository extends Repository<Widget, Long> {

@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
```

This will cause the CRUD implementation part of the backing repository proxy to apply the configured `LockModeType` to the `find(…)` call on the `EntityManager`.

[1]:

[To see links please register here]

[2]:

[To see links please register here]


Reply



Forum Jump:


Users browsing this thread:
3 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through