[PYTHON] django-rest-framework Das Django-Modell verhindert gleichzeitige Datenaktualisierungen mit optimistischer exklusiver Kontrolle über PostgreSQL

Einführung

Das Django-Modell verfügt nicht über einen Mechanismus für eine optimistische ausschließliche Steuerung. (Kein Recht?) Dieser Abschnitt beschreibt ein Implementierungsbeispiel für eine optimistische exklusive Steuerung in PostgreSQL.

Hinweis)

Erstellen eines Parallelitätskontrollmodells

Xmin-Spalte abrufen

Erstellen Sie eine Unterklasse, die den Abfrageausdruck erweitert, um die PostgreSQL-Systemspalten abzurufen.

from django.db.models import Expression, PositiveIntegerField


class XMin(Expression):
    output_field = PositiveIntegerField()

    def as_postgresql(self, compiler, connection):
        return f'"{compiler.query.base_table}"."xmin"', ()

Erstellen einer Concurrency Control Manager-Klasse

Überschreiben Sie get_queryset und fügen Sie eine Spalte mit der Zeilenversion (row_version) mit annotate hinzu.

from django.db.models import Manager


class ConcurrentManager(Manager):
    def get_queryset(self):
        super_query = super().get_queryset().annotate(row_version=XMin())
        return super_query

Ausnahmebehandlung zum Zeitpunkt der gleichzeitigen Ausführung

Wenn während der Parallelitätskontrolle ein Fehler auftritt, wird die folgende benutzerdefinierte Ausnahme ausgegeben.

class DbUpdateConcurrencyException(Exception):
    pass

Erstellen eines Parallelitätskontrollmodells

Ändern Sie die Manager-Klasse für die Parallelitätskontrolle, um die Methode "save" zu überschreiben und die Parallelitätskontrolle zu implementieren. Wenn die zu aktualisierende Zeile nicht gefunden wird, geben Sie eine "DbUpdateConcurrencyException" aus.

from django.db.models import Model


class ConcurrentModel(Model):
    objects = ConcurrentManager()

    class Meta:
        abstract = True

        base_manager_name = 'objects'

    def save(self, **kwargs):
        cls = self.__class__
        if self.pk and not kwargs.get('force_insert', None):
            rows = cls.objects.filter(
                pk=self.pk, row_version=self.row_version)
            if not rows:
                raise DbUpdateConcurrencyException(cls.__name__, self.pk)

        super().save(**kwargs)

Modellwechsel

Ändern Sie die Vererbungsquelle von "Model" in "ConcurrentModel".

class Customer(ConcurrentModel):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    code = models.CharField(verbose_name='Code', help_text='Code', max_length=10)
    name = models.CharField(verbose_name='Name', help_text='Name', max_length=50)

Wechsel des Serializers

Fügen Sie eine Zeilenversion hinzu (row_version).

class CustomerSerializer(DynamicFieldsModelSerializer):
    row_version = serializers.IntegerField()

    class Meta:
        model = Customer

        fields = (
            'id',
            'code',
            'name',
            'row_version',
        )

Funktionsprüfung der Parallelitätskontrolle

Daten bekommen

Sie können bestätigen, dass die Zeilenversion erhalten wurde.

curl -s -X GET "http://localhost:18000/api/customers/" -H "accept: application/json" | jq .
[
  {
    "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
    "code": "001",
    "name": "test1",
    "row_version": 588
  },
  {
    "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx2",
    "code": "002",
    "name": "test2",
    "row_version": 592
  }
]

Datenerfassung der ersten Zeile

curl -s -X GET "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" | jq .
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
  "code": "001",
  "name": "test",
  "row_version": 588
}

Wenn Sie eine andere Zeilenversion angeben

500 wird zurückgegeben, weil aufgrund der Parallelitätskontrolle ein Fehler auftritt.

curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 0}"

<!doctype html>
<html lang="en">
<head>
  <title>Server Error (500)</title>
</head>
<body>
  <h1>Server Error (500)</h1><p></p>
</body>
</html>

Bei gleicher Zeilenversion

Ich konnte mich erfolgreich registrieren.

curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 588}" | jq .

{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
  "code": "001",
  "name": "test2",
  "row_version": 588
}

Fehlerbehandlung

Wenn nichts unternommen wird, wird ein Fehler von 500 zurückgegeben. Steuern Sie ihn daher so, dass er mit 400 zurückgegeben wird. django-rest-framework bietet die Möglichkeit, die Ausnahmebehandlung anzupassen.

Verwenden Sie diese Option, um die Reaktion von Fehlern bei der Parallelitätskontrolle zu steuern, die in der API-Ansicht auftreten.

api/handlers.custom_exception_handler


from rest_framework import status
from rest_framework.validators import ValidationError
from rest_framework.response import Response
from rest_framework.views import exception_handler
from xxxxx import DbUpdateConcurrencyException


def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if isinstance(exc, DbUpdateConcurrencyException):
        return Response(ValidationError({'db_update_concurrency': ['Es wurde von einem anderen Benutzer geändert.']}).detail, status=status.HTTP_400_BAD_REQUEST)

    return response

app/settings.py


REST_FRAMEWORK = {
  'EXCEPTION_HANDLER': 'api.handlers.custom_exception_handler',
}

Funktionsprüfung 2

Wenn Sie eine andere Zeilenversion angeben

Es wird bei 400 in der folgenden Form zurückgegeben.

curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 0}"

{
  "db_update_concurrency": [
    "Es wurde von einem anderen Benutzer geändert."
  ]
}

Recommended Posts

django-rest-framework Das Django-Modell verhindert gleichzeitige Datenaktualisierungen mit optimistischer exklusiver Kontrolle über PostgreSQL
Verwenden Sie Django, um Tweet-Daten zu speichern
Linke äußere Verbindung im Django-Modell
Generieren Sie automatisch ein Modellbeziehungsdiagramm mit Django