Se você está tentando fazer o processamento de strings, você não está realmente gerando uma consulta SQL. Você está gerando uma string que pode produzir uma consulta SQL. Há um nível de indireção que abre um lote de espaço para erros e bugs. É um tanto surpreendente, já que na maioria dos contextos estamos felizes em interagir com algo programaticamente. Por exemplo, se tivermos alguma estrutura de lista e quisermos adicionar um item, geralmente não o fazemos:
List<Integer> list = /* a list of 1, 2, 3 */
String strList = list.toString(); /* to get "[1, 2, 3]" */
strList = /* manipulate strList to become "[1, 2, 5, 3]" */
list = parseList(strList);
Se alguém sugerir isso, você diria que é um pouco ridículo e que deve ser feito:
List<Integer> list = /* ... */;
list.add(5, position=2);
Isso interage com a estrutura de dados em seu nível conceitual. Ele não apresenta nenhuma dependência de como essa estrutura pode ser impressa ou analisada. Essas são decisões completamente ortogonais.
Sua primeira abordagem é como o primeiro exemplo (apenas um pouco pior): você está supondo que pode construir programaticamente a string que será analisada corretamente como a consulta desejada. Isso depende do analisador e de um monte de lógica de processamento de string.
A segunda abordagem de usar consultas preparadas é muito mais parecida com a segunda amostra. Quando você usa uma consulta preparada, basicamente analisa uma pseudo-consulta que é legal, mas tem alguns marcadores de posição nela e, em seguida, usa uma API para substituir corretamente alguns valores nela. Você não envolve mais o processo de análise e não precisa se preocupar com o processamento de strings.
Em geral, é muito mais fácil e menos propenso a erros interagir com as coisas em seu nível conceitual. Uma consulta não é uma string, uma consulta é o que você obtém quando você analisa uma string, ou constrói uma programaticamente (ou qualquer outro método que permita criar uma).
Há uma boa analogia aqui entre as macros de estilo C que fazem a substituição de texto simples e as macros no estilo Lisp que fazem a geração de código arbitrário. Com macros no estilo C, você pode substituir o texto no código-fonte, e isso significa que você tem a capacidade de introduzir erros sintáticos ou comportamento enganoso. Com macros Lisp, você está gerando código na forma que o compilador o processa (ou seja, você está retornando as estruturas de dados reais que o compilador processa, não o texto que o leitor deve processar antes que o compilador possa obtê-lo) . Com uma macro Lisp, você não pode gerar algo que seria um erro de análise. Por exemplo, você não pode gerar (let ((a b) a .
Mesmo com as macros Lisp, você ainda pode gerar códigos ruins, porque você não precisa necessariamente estar ciente da estrutura que deveria estar lá. Por exemplo, em Lisp, (let ((ab)) a) significa "estabelecer uma nova ligação léxica da variável a com o valor da variável b e, em seguida, retornar o valor de a" e < strong> (let (ab) a) significa "estabelecer novas ligações lexicais das variáveis aebe inicializá-las para nil e, em seguida, retornar o valor de a." Esses são ambos sintaticamente corretos, mas significam coisas diferentes. Para evitar esse problema, você pode usar mais funções semânticas e fazer algo como:
Variable a = new Variable("a");
Variable b = new Variable("b");
Let let = new Let();
let.getBindings().add(new LetBinding(a,b));
let.setBody(a);
return let;
Com algo assim, é impossível retornar algo que é sintaticamente inválido, e é muito mais difícil retornar algo que não seja o que você queria.