Je sais que vous êtes principalement préoccupé UPDATEet 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 INSERTdé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 UPDATEinstructions. Mais dans le cas d' INSERTinstructions, 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 cn'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 NULLet DEFAULT( jOOQ , par exemple étant une exception ici). Ils ne voient que Java nullet 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
DEFAULTclause
- Il peut être généré par un déclencheur
Retour aux UPDATEdé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 UPDATEinstructions 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"
UPDATEest 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.