Je sais que vous êtes principalement préoccupé UPDATE
et surtout par les performances, mais en tant que responsable de la maintenance "ORM", permettez-moi de vous donner une autre perspective sur le problème de la distinction entre les valeurs "modifiées" , "nulles" et "par défaut" , qui sont trois choses différentes en SQL, mais peut-être une seule chose en Java et dans la plupart des ORM:
Traduire votre justification en INSERT
déclarations
Vos arguments en faveur de la batchabilité et de la mise en cache des instructions sont valables de la même manière pour INSERT
instructions que pour les UPDATE
instructions. Mais dans le cas d' INSERT
instructions, l'omission d'une colonne de l'instruction a une sémantique différente de celle de UPDATE
. Cela signifie appliquer DEFAULT
. Les deux suivants sont sémantiquement équivalents:
INSERT INTO t (a, b) VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);
Ce n'est pas vrai pour UPDATE
, où les deux premiers sont sémantiquement équivalents, et le troisième a une signification entièrement différente:
-- These are the same
UPDATE t SET a = 1, b = 2;
UPDATE t SET a = 1, b = 2, c = c;
-- This is different!
UPDATE t SET a = 1, b = 2, c = DEFAULT;
La plupart des API clientes de base de données, y compris JDBC, et par conséquent, JPA, ne permettent pas de lier un DEFAULT
expression à une variable de liaison - principalement parce que les serveurs ne le permettent pas non plus. Si vous souhaitez réutiliser la même instruction SQL pour les raisons de batchability et de mise en cache des instructions susmentionnées, vous devez utiliser l'instruction suivante dans les deux cas (en supposant que (a, b, c)
toutes les colonnes figurent dans t
):
INSERT INTO t (a, b, c) VALUES (?, ?, ?);
Et comme ce c
n'est pas défini, vous lieriez probablement Java null
à la troisième variable de liaison, car de nombreux ORM ne peuvent pas non plus faire la distinction entre NULL
et DEFAULT
( jOOQ , par exemple étant une exception ici). Ils ne voient que Java null
et ne savent pas si cela signifie NULL
(comme dans la valeur inconnue) ou DEFAULT
(comme dans la valeur non initialisée).
Dans de nombreux cas, cette distinction n'a pas d'importance, mais si votre colonne c utilise l'une des fonctionnalités suivantes, l'instruction est tout simplement erronée :
- Il a une
DEFAULT
clause
- Il peut être généré par un déclencheur
Retour aux UPDATE
déclarations
Bien que ce qui précède soit vrai pour toutes les bases de données, je peux vous assurer que le problème de déclenchement est également vrai pour la base de données Oracle. Considérez le SQL suivant:
CREATE TABLE x (a INT PRIMARY KEY, b INT, c INT, d INT);
INSERT INTO x VALUES (1, 1, 1, 1);
CREATE OR REPLACE TRIGGER t
BEFORE UPDATE OF c, d
ON x
BEGIN
IF updating('c') THEN
dbms_output.put_line('Updating c');
END IF;
IF updating('d') THEN
dbms_output.put_line('Updating d');
END IF;
END;
/
SET SERVEROUTPUT ON
UPDATE x SET b = 1 WHERE a = 1;
UPDATE x SET c = 1 WHERE a = 1;
UPDATE x SET d = 1 WHERE a = 1;
UPDATE x SET b = 1, c = 1, d = 1 WHERE a = 1;
Lorsque vous exécutez ce qui précède, vous verrez la sortie suivante:
table X created.
1 rows inserted.
TRIGGER T compiled
1 rows updated.
1 rows updated.
Updating c
1 rows updated.
Updating d
1 rows updated.
Updating c
Updating d
Comme vous pouvez le voir, l'instruction qui met toujours à jour toutes les colonnes déclenche toujours le déclencheur pour toutes les colonnes, tandis que les instructions qui mettent à jour uniquement les colonnes qui ont changé ne déclenchent que les déclencheurs qui écoutent ces modifications spécifiques.
En d'autres termes:
Le comportement actuel d'Hibernate que vous décrivez est incomplet et pourrait même être considéré comme incorrect en présence de déclencheurs (et probablement d'autres outils).
Personnellement, je pense que votre argument d'optimisation du cache de requête est surfait dans le cas de SQL dynamique. Bien sûr, il y aura quelques requêtes supplémentaires dans un tel cache et un peu plus de travail d'analyse à effectuer, mais ce n'est généralement pas un problème pour les UPDATE
instructions dynamiques , beaucoup moins que pour SELECT
.
Le traitement par lots est certainement un problème, mais à mon avis, une seule mise à jour ne devrait pas être normalisée pour mettre à jour toutes les colonnes simplement parce qu'il y a une légère possibilité que l'instruction soit groupable. Il y a de fortes chances que l'ORM puisse collecter des sous-lots d'instructions identiques consécutives et les regrouper au lieu du "lot entier" (dans le cas où l'ORM est même capable de suivre la différence entre "modifié" , "nul" et "par défaut"
UPDATE
est pratiquement équivalent à unDELETE
+INSERT
(parce que vous créez en fait une nouvelle V ersion de la ligne). Les frais généraux sont élevés et augmentent avec le nombre d' index , surtout si la plupart des colonnes qui les composent sont réellement mises à jour, et l' arborescence (ou autre) utilisée pour représenter l'index a besoin d'un changement significatif. Ce n'est pas le nombre de colonnes mises à jour qui importe, mais la mise à jour d'une partie de colonne d'un index.