SQL에서 StringBuilder를 사용하는 올바른 방법


88

내 프로젝트에서 다음과 같은 SQL 쿼리 빌드를 찾았습니다.

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

이것이 StringBuilder목표를 달성 합니까 , 즉 메모리 사용량을 줄이는 것입니까?

생성자에서 '+'(문자열 연결 연산자)가 사용되기 때문에 의심합니다. 아래 코드와 같이 String을 사용하는 것과 같은 양의 메모리를 사용합니까? s를 사용할 때 다릅니다 StringBuilder.append().

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

두 문장 모두 메모리 사용량이 같습니까? 명확히하십시오.

미리 감사드립니다!

편집하다:

BTW, 내 코드가 아닙니다 . 오래된 프로젝트에서 찾았습니다. 또한 쿼리는 내 예제의 쿼리만큼 작지 않습니다. :)


1
SQL 보안 : 항상 PreparedStatement또는 유사한 것을 사용하십시오 . docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Christophe Roussy

메모리 사용량 외에도 SQL 빌더 라이브러리를 대신 사용하지 않는 이유 : stackoverflow.com/q/370818/521799
Lukas Eder

답변:


182

StringBuilder를 사용하는 목적, 즉 메모리 감소. 달성 되었습니까?

아니, 전혀. 해당 코드가 StringBuilder올바르게 사용되지 않습니다 . (나는 당신이 그것을 잘못 인용하지만 것 같은데, 확실히이 따옴표없는 id2table?)

목표는 (일반적으로) 사용 된 총 메모리가 아닌 메모리 변동 을 줄여 가비지 수집기의 삶을 조금 더 쉽게 만드는 것입니다.

아래와 같이 String을 사용하는 것과 동일한 메모리가 필요합니까?

아니요, 인용 한 직선 연결보다 더 많은 메모리 변동을 일으킬 것입니다. (JVM 옵티마이 저가 StringBuilder코드 의 명시 적 코드가 불필요하다고 판단하고 가능한 경우이를 최적화하지 않는 한 )

해당 코드의 작성자가 StringBuilder(에 대한 논쟁이 있지만 반대도 있습니다.이 답변의 끝 부분에있는 참고 사항 참조) 올바르게 수행하는 것이 좋습니다 (실제로 id2and 주위에 따옴표가 없다고 가정합니다 table).

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

내가 열거 한 것을 참고 some_appropriate_sizeStringBuilder우리가 추가 할거야 전체 내용에 대한 충분한 용량로 시작 그래서, 생성자입니다. 하나를 지정하지 않은 경우 사용되는 기본 크기는 16 자 입니다. 일반적으로 너무 작아서 StringBuilder자신을 더 크게 만들기 위해 재 할당을해야합니다 (IIRC, Sun / Oracle JDK에서는 두 배가됩니다. 공간이 부족할 때 append마다 특정 ] 을 만족시키기 위해 더 많은 것이 필요하다는 것을 알고 있습니다 .)

Sun / Oracle 컴파일러로 컴파일 한 경우 문자열 연결 내부적으로 a 사용 한다고 들었을 StringBuilder 입니다. 이것은 사실이며 StringBuilder전체 표현에 하나 를 사용 합니다. 그러나 기본 생성자를 사용하므로 대부분의 경우 재 할당을 수행해야합니다. 하지만 읽기가 더 쉽습니다. 이 유의 하지 의 진정한 일련의 연결로. 예를 들어 다음 중 하나를 사용합니다 StringBuilder.

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

대략 다음과 같이 번역됩니다.

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();

그래도 괜찮습니다. 기본 생성자와 후속 재 할당이 이상적이지는 않지만 충분히 좋으며 연결이 훨씬 더 읽기 쉽습니다.

그러나 그것은 하나의 표현에만 해당됩니다. 이를 위해 여러 StringBuilders가 사용됩니다.

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

결국 다음과 같이됩니다.

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;

... 아주 못 생겼습니다.

그러나 극소수의 경우를 제외 하고는 중요하지 않으며 특정 성능 문제를 제외하고는 가독성 (유지 보수성을 향상)을 선호 한다는 점을 기억하는 것이 중요합니다 .


그래, 그게 낫다. 매개 변수없는 생성자의 사용은 약간 안타깝지만 그다지 중요하지는 않습니다. 나는 그것이 중요한 문제라고 의심할만한 정당한 이유 x + y + zStringBuilder있지 않는 한 여전히 단일 표현을 사용합니다 .
Jon Skeet

@Crowder는 한 가지 더 의심이 있습니다. StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");.... 비슷한 sql.append줄은 약 60 줄입니다. 괜찮아?
Vaandu

1
@Vanathi : ( "질문", "의심"이 아님-일반적인 오역입니다.) 괜찮지 만 StringBuilder생성자를 전달한 문자열에 16자를 더한 충분한 공간이 처음에 할당 되기 때문에 아마도 여러 번 재 할당 될 것입니다 . 따라서 16 자 이상을 추가하면 (감히 그렇습니다. 60 개가 추가되면!) StringBuilder는 적어도 한 번, 가능하면 여러 번 재 할당해야합니다. 최종 결과가 얼마나 클지 (예 : 400 자) 합리적인 아이디어 sql = new StringBuilder(400);가있는 경우 appends 를 수행하는 것이 가장 좋습니다 .
TJ Crowder

@Vanathi : 도움이되어 기쁩니다. 예, 6,000 자일 경우 StringBuilder사전에 약 8 개의 메모리 재 할당을 절약 할 수 있다고 말합니다 (초기 문자열이 약 10 자라고 가정하면 SB는 처음부터 26 개, 그다음에 52 개, 104, 208 개, 416, 832, 1664, 3328 및 마지막으로 6656). 당신이 미리 알고 있다면 ... :-)이 여전히 핫스팟이지만, 경우에만 의미
TJ 크라우

@TJ Crowder는 더 나은 성능을 위해 "+"연산자를 사용해서는 안된다는 뜻입니다. 권리? 그렇다면 오라 칼이 그들의 언어에 "+"연산자를 추가 한 이유는 무엇입니까?
Smit Patel

38

추가하려는 "조각"이 이미 모두있는 경우에는 사용할 필요가 없습니다 StringBuilder. 사용 StringBuilder 샘플 코드에 따라 동일한 호출에서 문자열 연결은 더욱 심각하다.

이것이 더 나을 것입니다.

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

이 경우 문자열 연결은 실제로 컴파일 타임에 발생 하므로 훨씬 더 단순한 것과 같습니다.

return "select id1, id2 from table";

사용하는 new StringBuilder().append("select id1, ").append(" id2 ")....toString()것입니다 사실 을 방해 가 연결이 수행하는 강제하기 때문에,이 경우 성능을 실행 시간 대신에, 컴파일 시간. 죄송합니다.

실제 코드를 포함하여 SQL 쿼리를 작성하는 경우 값을 쿼리에서, 그 다른의 분리 는 오히려 SQL에 비해 매개 변수의 값을 지정하는 매개 변수화 된 쿼리를 사용한다는 것입니다 문제.

나는 오래 전에 내가 쓴 / StringStringBuffer 대한 기사StringBuilder 가 있습니다. 원칙 StringBuilder은 동일한 방식으로 적용됩니다 .


10

[[여기에 좋은 답변이 있지만 아직 정보가 부족한 것 같습니다. ]]

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

당신이 지적한 바와 같이, 당신이 제시하는 예는 단순하지만 어쨌든 그것을 분석해 봅시다. 여기서 일어나는 일은 컴파일러가 실제로 모든 상수 +이기 때문에 여기서 작업을 수행한다는 것 "select id1, " + " id2 " + " from " + " table"입니다. 그래서 이것은 다음과 같이 변합니다.

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

이 경우 분명히 StringBuilder. 다음을 수행하는 것이 좋습니다.

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

그러나 필드 또는 기타 비 상수를 추가하는 경우에도 컴파일러는 내부를 사용하므로 StringBuilder정의 할 필요가 없습니다.

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

내부적으로 이것은 대략 다음과 같은 코드로 바뀝니다.

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

실제로 StringBuilder 직접 사용해야하는 유일한 시간 은 조건부 코드가있을 때입니다. 예를 들어, 다음과 같은 코드는 a에 절실합니다 StringBuilder.

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

+첫번째 라인의 사용은 하나의 StringBuilder예입니다. 그런 다음 +=다른 StringBuilder인스턴스를 사용 합니다. 다음을 수행하는 것이 더 효율적입니다.

// 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();

a를 사용하는 또 다른 시간 StringBuilder은 여러 메서드 호출에서 문자열을 만들 때입니다. 그런 다음 StringBuilder인수 를받는 메서드를 만들 수 있습니다 .

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

를 사용하는 경우 동시에 StringBuilder의 모든 사용을 관찰해야합니다 +.

sb.append("select " + fieldName);

그러면 +다른 내부 StringBuilder가 생성됩니다. 물론 이것은 다음과 같아야합니다.

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

마지막으로 @TJrowder가 지적했듯이 항상 StringBuilder. 이렇게하면 char[]내부 버퍼의 크기를 늘릴 때 생성되는 개체 수가 절약됩니다 .


4

문자열 작성기를 사용하는 목적이 적어도 전체 범위는 달성되지 않았다고 추측하는 것이 맞습니다.

그러나 컴파일러가 표현식을 볼 때 "select id1, " + " id2 " + " from " + " table"실제로 StringBuilder비하인드 씬을 생성 하고 추가 하는 코드를 방출 하므로 최종 결과는 그렇게 나쁘지 않습니다.

그러나 물론 그 코드를 보는 사람은 그것이 일종의 지연이라고 생각할 수밖에 없습니다.


2

게시 한 코드에는 StringBuilder를 잘못 사용하고 있으므로 이점이 없습니다. 두 경우 모두 동일한 문자열을 빌드합니다. StringBuilder를 +사용하면 append메서드를 사용하여 문자열에 대한 작업을 피할 수 있습니다 . 다음과 같이 사용해야합니다.

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

Java에서 문자열 유형은 변경 불가능한 문자 시퀀스이므로 두 개의 문자열을 추가하면 VM이 두 피연산자가 연결된 새 문자열 값을 만듭니다.

StringBuilder는 새로운 String 객체를 생성하지 않고 다른 값이나 변수를 연결하는 데 사용할 수있는 가변 문자 시퀀스를 제공하므로 때때로 문자열로 작업하는 것보다 더 효율적일 수 있습니다.

이는 다른 메소드 내에서 매개 변수로 전달 된 char 시퀀스의 내용을 변경하는 등 몇 가지 유용한 기능을 제공합니다. 이는 문자열로는 수행 할 수 없습니다.

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);
}

http://docs.oracle.com/javase/tutorial/java/data/buffers.html 에서 자세한 정보


1
아니, 안돼. +어쨌든 동일한 코드로 변환되는을 사용 하는 것 보다 읽기가 어렵습니다. StringBuilder단일 표현식에서 모든 연결을 수행 할 수 없을 때 유용하지만이 경우에는 그렇지 않습니다.
Jon Skeet

1
질문의 문자열이 예로 게시되었음을 이해합니다. 단일 상수 "select id1, id2 from table"에서 정의 할 수 있기 때문에 StringBuilder를 사용하거나 다른 조각을 추가하지 않고 이와 같은 "고정 된"문자열을 빌드하는 것은 의미가 없습니다
Tomas Narros

그러나 변수에서 상수가 아닌 값이 있더라도 사용하면 여전히 단일 값 StringBuilder을 사용합니다 return "select id1, " + foo + "something else" + bar;. 그러니 그렇게하지 않는 이유는 무엇입니까? 이 질문은 모든 것이 StringBuilder주변 을 통과해야한다는 표시를 제공하지 않습니다 .
Jon Skeet

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.