Sei que você se preocupa UPDATEprincipalmente com o desempenho, mas como colega de manutenção de "ORM", deixe-me dar outra perspectiva sobre o problema de distinguir entre valores "alterados" , "nulos" e "padrão" , que são três coisas diferentes no SQL, mas possivelmente apenas uma coisa no Java e na maioria dos ORMs:
Traduzindo sua justificativa para INSERTdeclarações
Seus argumentos a favor da capacidade de armazenamento e cache de instruções são verdadeiros da mesma maneira para INSERTinstruções e para UPDATEinstruções. Mas, no caso de INSERTdeclarações, a omissão de uma coluna da declaração tem uma semântica diferente da que em UPDATE. Significa aplicar DEFAULT. Os dois seguintes são semanticamente equivalentes:
INSERT INTO t (a, b) VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);
Isso não é verdade para UPDATE, onde os dois primeiros são semanticamente equivalentes e o terceiro tem um significado totalmente diferente:
-- 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;
A maioria das APIs de clientes de banco de dados, incluindo JDBC e, consequentemente, JPA, não permite vincular uma DEFAULTexpressão a uma variável de ligação - principalmente porque os servidores também não permitem isso. Se você deseja reutilizar a mesma instrução SQL pelos motivos mencionados de capacidade de manipulação e armazenamento em cache, use a seguinte instrução nos dois casos (supondo que (a, b, c)todas as colunas estejam t):
INSERT INTO t (a, b, c) VALUES (?, ?, ?);
E como cnão está definido, você provavelmente ligaria o Java nullà terceira variável de ligação, porque muitos ORMs também não podem distinguir entre NULLe DEFAULT( jOOQ , por exemplo, sendo uma exceção aqui). Eles apenas veem Java nulle não sabem se isso significa NULL(como no valor desconhecido) ou DEFAULT(como no valor não inicializado).
Em muitos casos, essa distinção não importa, mas, se sua coluna c estiver usando algum dos seguintes recursos, a instrução estará simplesmente errada :
- Tem uma
DEFAULTcláusula
- Pode ser gerado por um gatilho
Voltar para UPDATEdeclarações
Embora o acima seja verdadeiro para todos os bancos de dados, posso garantir que o problema do acionador também é verdadeiro para o banco de dados Oracle. Considere o seguinte SQL:
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;
Ao executar o procedimento acima, você verá a seguinte saída:
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
Como você pode ver, a instrução que sempre atualiza todas as colunas sempre aciona o gatilho para todas as colunas, enquanto as instruções que atualizam apenas as colunas que foram alteradas acionam apenas os gatilhos que estão ouvindo essas alterações específicas.
Em outras palavras:
O comportamento atual do Hibernate que você está descrevendo é incompleto e pode até ser considerado errado na presença de gatilhos (e provavelmente outras ferramentas).
Pessoalmente, acho que seu argumento de otimização de cache de consulta é superestimado no caso de SQL dinâmico. Claro, haverá mais algumas consultas nesse cache e um pouco mais de trabalho de análise a ser feito, mas isso geralmente não é um problema para UPDATEinstruções dinâmicas , muito menos do que para SELECT.
O lote é certamente um problema, mas, na minha opinião, uma única atualização não deve ser normalizada para atualizar todas as colunas apenas porque existe uma pequena possibilidade de a declaração ser recuperável. Provavelmente, o ORM pode coletar sub-lotes de instruções idênticas consecutivas e colocá-las em lote em vez do "lote inteiro" (caso o ORM seja capaz de rastrear a diferença entre "alterado" , "nulo" e "padrão"
UPDATEé praticamente equivalente a umDELETE+INSERT(porque você realmente criar uma nova V ersão da linha). A sobrecarga é alta e aumenta com o número de índices , especialmente se muitas das colunas que as compõem forem realmente atualizadas e a árvore (ou qualquer outra coisa) usada para representar o índice precisar de uma alteração significativa. Não é o número de colunas atualizadas que é relevante, mas se você atualiza uma parte da coluna de um índice.