it-swarm.com.de

Wie kann ich LockModeType.PESSIMISTIC_WRITE aktivieren, wenn ich Objekte mit Spring Data JPA suche?

Wie kann ich das Äquivalent zu diesem Code erreichen:

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

... aber mit Spring- und Spring-Data-JPA-Annotationen?

Die Basis meines bestehenden Codes ist:

@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);
  }
}

Aber ich weiß nicht, wie ich angeben soll, dass alles in der updateWidgetStock -Methode mit einem pessimistischen Sperrensatz erfolgen soll.

Es gibt eine JPA-Annotation von Spring Data org.springframework.data.jpa.repository.Lock, Mit der Sie LockModeType festlegen können, aber ich weiß nicht, ob es gültig ist, sie in die updateWidgetStock -Methode einzufügen. Es klingt eher wie eine Anmerkung auf dem WidgetRepository, weil das Javadoc sagt:

org.springframework.data.jpa.repository
@ Ziel (Wert = METHODE)
@ Retention (Wert = RUNTIME)
@ Dokumentiert
public @interface Lock
Anmerkung, mit der der LockModeType angegeben wird, der beim Ausführen der Abfrage verwendet werden soll. Es wird ausgewertet, wenn Sie Query für eine Abfragemethode verwenden oder wenn Sie die Abfrage vom Methodennamen ableiten.

... das scheint also nicht hilfreich zu sein.

Wie kann ich meine updateWidgetStock() -Methode mit LockModeType.PESSIMISTIC_WRITE Ausführen lassen?

48
Greg Kopff

@Lock Wird von CRUD-Methoden ab Version 1.6 von Spring Data JPA unterstützt (tatsächlich ist bereits ein Meilenstein verfügbar). Siehe dieses Ticket für weitere Details.

Mit dieser Version deklarieren Sie einfach Folgendes:

interface WidgetRepository extends Repository<Widget, Long> {

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

Dadurch wendet der CRUD-Implementierungsteil des Backing-Repository-Proxys den konfigurierten LockModeType auf den find(…) -Aufruf des EntityManager an.

65
Oliver Drotbohm

Wenn Sie die Standardmethode findOne() nicht überschreiben möchten, können Sie eine Sperre in Ihrer benutzerdefinierten Methode erwerben, indem Sie die Abfrage select ... for update Folgendermaßen verwenden:

/**
 * 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);
}

Wenn Sie jedoch PostgreSQL verwenden, kann es etwas komplizierter werden, wenn Sie das Sperrzeitlimit festlegen möchten, um Deadlocks zu vermeiden. PostgreSQL ignoriert die in JPA-Eigenschaften oder in der Annotation javax.persistence.lock.timeout Festgelegte Standardeigenschaft @QueryHint.

Ich konnte es nur zum Laufen bringen, indem ich ein benutzerdefiniertes Repository erstellte und das Zeitlimit manuell einstellte, bevor ich eine Entität sperrte. Es ist nicht schön, aber es funktioniert zumindest:

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;
}

}

15
petr.vlcek

Wenn Sie Spring Data 1.6 oder höher verwenden können, ignorieren Sie diese Antwort und lesen Sie Olivers Antwort.

Die pessimistischen Anmerkungen von Spring Data @Lock Gelten (wie Sie bereits betont haben) nur für Abfragen. Ich kenne keine Anmerkungen, die sich auf eine gesamte Transaktion auswirken könnten. Sie können entweder eine findByOnePessimistic -Methode erstellen, die findByOne mit einer pessimistischen Sperre aufruft, oder Sie können findByOne ändern, um immer eine pessimistische Sperre zu erhalten.

Wenn Sie Ihre eigene Lösung implementieren möchten, können Sie dies wahrscheinlich tun. Unter der Haube wird die Annotation @Lock Von LockModePopulatingMethodIntercceptor verarbeitet, was Folgendes bewirkt:

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

Sie können einen statischen Sperrenmanager mit einer Mitgliedsvariablen ThreadLocal<LockMode> Erstellen und dann in jedem Repository, das bindResource aufruft, einen Aspekt um jede Methode wickeln, wobei der Sperrmodus in ThreadLocal. Auf diese Weise können Sie den Sperrmodus pro Thread festlegen. Sie können dann eine eigene Annotation @MethodLockMode Erstellen, die die Methode in einen Aspekt einwickelt, der den threadspezifischen Sperrmodus vor dem Ausführen der Methode festlegt und ihn nach dem Ausführen der Methode löscht.

14
Pace