Maneira correta de usar StringBuilder em SQL


88

Acabei de encontrar alguma construção de consulta sql como esta no meu projeto:

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();

Isso StringBuilderatinge seu objetivo, ou seja, reduzir o uso de memória?

Duvido, porque no construtor é usado o '+' (operador String concat). Isso consumirá a mesma quantidade de memória que usar String como o código abaixo? s eu entendi, ele difere durante o uso StringBuilder.append().

return "select id1, " + " id2 " + " from " + " table";

Ambas as instruções são iguais no uso de memória ou não? Por favor, esclareça.

Desde já, obrigado!

Editar:

BTW, não é meu código . Encontrei em um projeto antigo. Além disso, a consulta não é tão pequena quanto a do meu exemplo. :)


1
SEGURANÇA SQL: use sempre PreparedStatementou algo semelhante: docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
— Christophe Roussy

Além do uso de memória, por que não usar uma biblioteca SQL builder: stackoverflow.com/q/370818/521799
— Lukas Eder

Respostas:


182

O objetivo de usar StringBuilder, ou seja, reduzir a memória. Isso foi alcançado?

Não, de forma alguma. Esse código não está sendo usado StringBuildercorretamente. (Eu acho que você citou errado, entretanto; certamente não há aspas id2e table?)

Observe que o objetivo (geralmente) é reduzir a rotatividade de memória em vez da memória total usada, para tornar a vida um pouco mais fácil para o coletor de lixo.

Isso levará memória igual ao uso de String como abaixo?

Não, isso causará mais rotatividade de memória do que apenas o concat direto que você citou. (Até / a menos que o otimizador JVM veja que o explícito StringBuilderno código é desnecessário e o otimize, se puder.)

Se o autor desse código quiser usar StringBuilder(há argumentos a favor, mas também contra; consulte a nota no final desta resposta), melhor fazer isso corretamente (aqui estou assumindo que não há aspas id2e table):

StringBuilder sb = new StringBuilder(some_appropriate_size);
sb.append("select id1, ");
sb.append(id2);
sb.append(" from ");
sb.append(table);
return sb.toString();

Observe que listei some_appropriate_sizeno StringBuilderconstrutor, de forma que ele comece com capacidade suficiente para todo o conteúdo que iremos anexar. O tamanho padrão usado se você não especificar um é de 16 caracteres , que geralmente é muito pequeno e resulta na StringBuildernecessidade de fazer realocações para se tornar maior (IIRC, no Sun / Oracle JDK, ele se duplica [ou mais, se sabe que precisa de mais para satisfazer um determinado append] cada vez que fica sem espaço).

Você pode ter ouvido que concatenação irá usar um StringBuilderdebaixo das cobertas se compilado com o compilador Sun / Oracle. Isso é verdade, ele usará um StringBuilderpara a expressão geral. Mas ele usará o construtor padrão, o que significa que na maioria dos casos terá que fazer uma realocação. É mais fácil de ler, no entanto. Observe que isso não é verdade para uma série de concatenações. Então, por exemplo, ele usa um StringBuilder:

return "prefix " + variable1 + " middle " + variable2 + " end";

Isso se traduz aproximadamente em:

StringBuilder tmp = new StringBuilder(); // Using default 16 character size
tmp.append("prefix ");
tmp.append(variable1);
tmp.append(" middle ");
tmp.append(variable2);
tmp.append(" end");
return tmp.toString();

Então está tudo bem, embora o construtor padrão e as realocações subsequentes não sejam ideais, as chances são boas o suficiente - e a concatenação é muito mais legível.

Mas isso é apenas para uma única expressão. Vários StringBuilders são usados ​​para isso:

String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;

Isso acaba se tornando algo assim:

String s;
StringBuilder tmp;
s = "prefix ";
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable1);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" middle ");
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable2);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" end");
s = tmp.toString();
return s;

... o que é muito feio.

É importante lembrar, porém, que em todos os casos , exceto em alguns casos , isso não importa e ir com a legibilidade (o que aumenta a capacidade de manutenção) é preferível, exceto um problema de desempenho específico.


Certo, está melhor. O uso do construtor sem parâmetros é um pouco infeliz, mas improvável que seja significativo. Eu ainda usaria uma única x + y + zexpressão em vez de, a StringBuildermenos que eu tivesse um bom motivo para suspeitar que seria um problema significativo.
— Jon Skeet

@Crowder tem mais uma dúvida. StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");.... sql.appendLinhas semelhantes têm cerca de 60 linhas. Esta bem?
— Vaandu

1
@Vanathi: ("Pergunta", não "dúvida" - é um erro de tradução comum.) Tudo bem, mas provavelmente resultará em várias realocações, porque StringBuilderinicialmente será alocado espaço suficiente para a string que você passou ao construtor mais 16 caracteres. Portanto, se você anexar mais de 16 caracteres (ouso dizer que sim, se houver 60 anexos!), O StringBuilderterá que realocar pelo menos uma vez e possivelmente várias vezes. Se você tiver uma ideia razoável de quão grande será o resultado final (digamos, 400 caracteres), é melhor fazer sql = new StringBuilder(400);(ou o que quer que seja) do que fazer o appends.
— TJ Crowder

@Vanathi: Que bom que ajudou. Sim, se for 6.000 caracteres, dizer StringBuilderisso com antecedência economizará cerca de oito realocações de memória (assumindo que a string inicial tinha cerca de 10 caracteres, o SB teria sido de 26 para começar, depois dobrado para 52, depois 104, 208, 416, 832, 1664, 3328 e finalmente 6656). Apenas significativo se for um ponto de acesso, mas ainda assim, se você souber com antecedência ... :-)
— TJ Crowder

@TJ Crowder, você quer dizer que não devo usar o operador "+" para um melhor desempenho. certo? então por que Oracal adicionou o operador "+" em seu idioma, você pode explicar? De qualquer forma, meu voto positivo para sua resposta.
— Smit Patel

38

Quando você já tem todas as "peças" que deseja acrescentar, não há sentido em usar StringBuilder. Usando StringBuilder e concatenação na mesma chamada de acordo com o seu código de amostra é ainda pior.

Isso seria melhor:

return "select id1, " + " id2 " + " from " + " table";

Nesse caso, a concatenação de string está realmente acontecendo em tempo de compilação de qualquer maneira, então é equivalente ao ainda mais simples:

return "select id1, id2 from table";

O uso new StringBuilder().append("select id1, ").append(" id2 ")....toString()realmente prejudicará o desempenho neste caso, porque força a concatenação a ser realizada em tempo de execução , em vez de em tempo de compilação . Opa.

Se o código real está construindo uma consulta SQL incluindo valores na consulta, então esse é outro problema separado , que é que você deve usar consultas parametrizadas, especificando os valores nos parâmetros em vez de no SQL.

Eu tenho um artigo sobre String/StringBuffer que escrevi há um tempo - antes de StringBuilderaparecer. Os princípios se aplicam StringBuilderda mesma forma.


10

[[Existem algumas respostas boas aqui, mas acho que ainda faltam um pouco de informação. ]]

return (new StringBuilder("select id1, " + " id2 " + " from " + " table"))
     .toString();

Como você indicou, o exemplo que você deu é simplista, mas vamos analisá-lo de qualquer maneira. O que acontece aqui é que o compilador realmente faz o +trabalho aqui porque "select id1, " + " id2 " + " from " + " table"são todos constantes. Então, isso se transforma em:

return new StringBuilder("select id1,  id2  from  table").toString();

Nesse caso, obviamente, não adianta usar StringBuilder. Você também pode fazer:

// the compiler combines these constant strings
return "select id1, " + " id2 " + " from " + " table";

No entanto, mesmo se você estivesse anexando quaisquer campos ou outros não constantes, o compilador usaria um interno StringBuilder - não há necessidade de você definir um:

// an internal StringBuilder is used here
return "select id1, " + fieldName + " from " + tableName;

Nos bastidores, isso se transforma em código que é aproximadamente equivalente a:

StringBuilder sb = new StringBuilder("select id1, ");
sb.append(fieldName).append(" from ").append(tableName);
return sb.toString();

Realmente, a única vez que você precisa usar StringBuilder diretamente é quando você tem código condicional. Por exemplo, o código que se parece com o seguinte está desesperado por um StringBuilder:

// 1 StringBuilder used in this line
String query = "select id1, " + fieldName + " from " + tableName;
if (where != null) {
   // another StringBuilder used here
   query += ' ' + where;
}

O +na primeira linha usa uma StringBuilderinstância. Então o +=usa outroStringBuilder instância. É mais eficiente fazer:

// choose a good starting size to lower chances of reallocation
StringBuilder sb = new StringBuilder(64);
sb.append("select id1, ").append(fieldName).append(" from ").append(tableName);
// conditional code
if (where != null) {
   sb.append(' ').append(where);
}
return sb.toString();

Outra vez que uso um StringBuilderé quando estou construindo uma string a partir de várias chamadas de método. Então, posso criar métodos que recebam um StringBuilderargumento:

private void addWhere(StringBuilder sb) {
   if (where != null) {
      sb.append(' ').append(where);
   }
}

Quando estiver usando um StringBuilder, você deve observar se há uso de +ao mesmo tempo:

sb.append("select " + fieldName);

Isso +vai causar outro internoStringBuilder seja criado. Claro que deveria ser:

sb.append("select ").append(fieldName);

Por fim, como @TJrowder aponta, você sempre deve tentar adivinhar o tamanho do StringBuilder. Isso economizará no número de char[]objetos criados ao aumentar o tamanho do buffer interno.


4

Você está correto ao supor que o objetivo de usar o construtor de cordas não foi alcançado, pelo menos não em toda a sua extensão.

No entanto, quando o compilador vê a expressão, "select id1, " + " id2 " + " from " + " table"ele emite um código que, na verdade, cria um StringBuildersegundo plano e o anexa, de modo que o resultado final não é tão ruim assim.

Mas é claro que qualquer pessoa que olhar para esse código vai pensar que ele é meio retardado.


2

No código que você postou, não haveria vantagens, pois você está usando incorretamente o StringBuilder. Você constrói a mesma String em ambos os casos. Usando StringBuilder, você pode evitar a +operação em Strings usando o appendmétodo. Você deve usá-lo desta forma:

return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();

Em Java, o tipo String é uma sequência inmutável de caracteres, portanto, quando você adiciona duas Strings, a VM cria um novo valor String com ambos os operandos concatenados.

StringBuilder fornece uma sequência mutável de caracteres, que você pode usar para conciliar diferentes valores ou variáveis ​​sem criar novos objetos String e, portanto, às vezes pode ser mais eficiente do que trabalhar com strings

Isso fornece alguns recursos úteis, como alterar o conteúdo de uma sequência de caracteres passada como parâmetro dentro de outro método, o que você não pode fazer com Strings.

private void addWhereClause(StringBuilder sql, String column, String value) {
   //WARNING: only as an example, never append directly a value to a SQL String, or you'll be exposed to SQL Injection
   sql.append(" where ").append(column).append(" = ").append(value);
}

Mais informações em http://docs.oracle.com/javase/tutorial/java/data/buffers.html


1
Não, você não deveria. É menos legível do que usar +, que será convertido no mesmo código de qualquer maneira. StringBuilderé útil quando você não pode realizar toda a concatenação em uma única expressão, mas não neste caso.
— Jon Skeet

1
Eu entendo que a string em questão é postada como exemplo. Não faria sentido construir uma string "fixa" como esta nem com StringBuilder nem adicionando fragmentos diferentes, já que você poderia apenas defini-la em uma única constante "select id1, id2 from table"
— Tomas Narros

Mas mesmo que houvesse valores não constantes nas variáveis, ainda usaria um único StringBuilderse você fosse usar return "select id1, " + foo + "something else" + bar;- então por que não fazer isso? A pergunta não fornece nenhuma indicação de que algo precisa ser divulgado StringBuilder.
— Jon Skeet

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.