How to generate String "elegantly" in Java? - java

I want to generate a string such as sql command:
"INSERT INTO xxx VALUES(XXX, XXX, XXX)"
currently I use StringBuilder and some String constant like "INSERT INTO" to concatenate input String parameters for the table name and inserted values.
However, other than performance issue, this plain concatenation looks not elegant.
Is there any other way of doing this?
In my opinion, JDBC's prepared statement is one good example of such a "command template":
PreparedStatement pstmt=connection.createPreparedStatement("INSERT INTO ? VALUES(?,?,?)");
then you can set the table name and inserted value.
pstmt.setString(1,"tableA");
pstmt.setInt(2, 100);
...
However, I can not use prepared statement, since what I want is just String...
And someone give me some hint to use java.util.Regex or JavaCC to produce the String.
But as far as I can see, whatever is chosen for some code elegancy issue, Java String must be generated by something like StringBuilder, right???

You could use String.format():
String.format("insert into %s values('%s', '%s', '%s')", "user", "user123", "pass123", "yellow");
It's worth noting though, that any of these "string building" techniques leave you vulnerable to SQL injection attacks. You should really use JDBC parameterised queries wherever possible.
Edited to add quotes around strings.

Maybe you are looking for java.text.MessageFormat
int planet = 7;
String event = "a disturbance in the Force";
String result = MessageFormat.format(
"At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
planet, new Date(), event);

Have you tried just using '+' ?
String sql = "INSERT INTO " + table
+" VALUES(" + value1 + ", " + value2 + ", " = value3+")";

Given the variety of other answers and none of them met your approval, perhaps you should accept that the actual String generation (sans JPA, PreparedStatement, etc.) is going to be fairly inelegant and create a utility class with static sql generators.
edit Showing an example of how I'd go about this if a pre-existing class such as PreparedStatement weren't an option. It's not the most elegant, but it does what it's supposed to (assuming I typed it all in correctly).
public class SQLUtil {
public static String generateInsertSQL(String tableName, List<CustomParameter> parmList){
StringBuilder sb = new Stringbuilder();
sb.append("insert into ");
sb.append(tableName);
sb.append(" values (");
for (int i = 0; i < parmList.size(); i++){
customParameter parm = parmList.get(i);
switch (parm.getType()) { // enum with your desired sql types
case ParmTypes.String:
sb.append("'");
sb.append(StringEscapeUtils.escapeSql(String.valueOf(parm.getValue())));
sb.append("'");
break;
case ParmTypes.Integer:
sb.append(Integer.valueOf(parm.getValue()));
break;
}
if (i < parmList.size() - 1) sb.append(",");
}
sb.append(")");
return sb.toString();
}
}
This way, your business code will remain relatively elegant and you can play around with the SQL String generation to your heart's content. You can also use this to "guarantee" all your inserts are protected against such attacks as SQL injection.

Use StringTemplate (http://www.stringtemplate.org/) maybe a good choice:
This looks better, right?
StringTemplate insert = new StringTemplate("INSERT $table$ VALUES ($value; separator=\",\"$)");
insert.setAttribute("table", "aTable");
String[] values = {"1", "1", "'aaa'", "'bbb'"};
for(int i = 0;i < values.length;i++){
insert.setAttribute("value", values[i]);
}
System.out.println(insert.toString());

Related

Java regular expression to prevent SQL injection [duplicate]

I'm trying to put some anti sql injection in place in java and am finding it very difficult to work with the the "replaceAll" string function. Ultimately I need a function that will convert any existing \ to \\, any " to \", any ' to \', and any \n to \\n so that when the string is evaluated by MySQL SQL injections will be blocked.
I've jacked up some code I was working with and all the \\\\\\\\\\\ in the function are making my eyes go nuts. If anyone happens to have an example of this I would greatly appreciate it.
PreparedStatements are the way to go, because they make SQL injection impossible. Here's a simple example taking the user's input as the parameters:
public insertUser(String name, String email) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = setupTheDatabaseConnectionSomehow();
stmt = conn.prepareStatement("INSERT INTO person (name, email) values (?, ?)");
stmt.setString(1, name);
stmt.setString(2, email);
stmt.executeUpdate();
}
finally {
try {
if (stmt != null) { stmt.close(); }
}
catch (Exception e) {
// log this error
}
try {
if (conn != null) { conn.close(); }
}
catch (Exception e) {
// log this error
}
}
}
No matter what characters are in name and email, those characters will be placed directly in the database. They won't affect the INSERT statement in any way.
There are different set methods for different data types -- which one you use depends on what your database fields are. For example, if you have an INTEGER column in the database, you should use a setInt method. The PreparedStatement documentation lists all the different methods available for setting and getting data.
The only way to prevent SQL injection is with parameterized SQL. It simply isn't possible to build a filter that's smarter than the people who hack SQL for a living.
So use parameters for all input, updates, and where clauses. Dynamic SQL is simply an open door for hackers, and that includes dynamic SQL in stored procedures. Parameterize, parameterize, parameterize.
If really you can't use Defense Option 1: Prepared Statements (Parameterized Queries) or Defense Option 2: Stored Procedures, don't build your own tool, use the OWASP Enterprise Security API. From the OWASP ESAPI hosted on Google Code:
Don’t write your own security controls! Reinventing the wheel when it comes to developing security controls for every web application or web service leads to wasted time and massive security holes. The OWASP Enterprise Security API (ESAPI) Toolkits help software developers guard against security‐related design and implementation flaws.
For more details, see Preventing SQL Injection in Java and SQL Injection Prevention Cheat Sheet.
Pay a special attention to Defense Option 3: Escaping All User Supplied Input that introduces the OWASP ESAPI project).
(This is in answer to the OP's comment under the original question; I agree completely that PreparedStatement is the tool for this job, not regexes.)
When you say \n, do you mean the sequence \+n or an actual linefeed character? If it's \+n, the task is pretty straightforward:
s = s.replaceAll("['\"\\\\]", "\\\\$0");
To match one backslash in the input, you put four of them in the regex string. To put one backslash in the output, you put four of them in the replacement string. This is assuming you're creating the regexes and replacements in the form of Java String literals. If you create them any other way (e.g., by reading them from a file), you don't have to do all that double-escaping.
If you have a linefeed character in the input and you want to replace it with an escape sequence, you can make a second pass over the input with this:
s = s.replaceAll("\n", "\\\\n");
Or maybe you want two backslashes (I'm not too clear on that):
s = s.replaceAll("\n", "\\\\\\\\n");
PreparedStatements are the way to go in most, but not all cases. Sometimes you will find yourself in a situation where a query, or a part of it, has to be built and stored as a string for later use. Check out the SQL Injection Prevention Cheat Sheet on the OWASP Site for more details and APIs in different programming languages.
Prepared Statements are the best solution, but if you really need to do it manually you could also use the StringEscapeUtils class from the Apache Commons-Lang library. It has an escapeSql(String) method, which you can use:
import org.apache.commons.lang.StringEscapeUtils;
…
String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);
Using a regular expression to remove text which could cause a SQL injection sounds like the SQL statement is being sent to the database via a Statement rather than a PreparedStatement.
One of the easiest ways to prevent an SQL injection in the first place is to use a PreparedStatement, which accepts data to substitute into a SQL statement using placeholders, which does not rely on string concatenations to create an SQL statement to send to the database.
For more information, Using Prepared Statements from The Java Tutorials would be a good place to start.
You need the following code below. At a glance, this may look like any old code that I made up. However, what I did was look at the source code for http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement.java. Then after that, I carefully looked through the code of setString(int parameterIndex, String x) to find the characters which it escapes and customised this to my own class so that it can be used for the purposes that you need. After all, if this is the list of characters that Oracle escapes, then knowing this is really comforting security-wise. Maybe Oracle need a nudge to add a method similar to this one for the next major Java release.
public class SQLInjectionEscaper {
public static String escapeString(String x, boolean escapeDoubleQuotes) {
StringBuilder sBuilder = new StringBuilder(x.length() * 11/10);
int stringLength = x.length();
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0: /* Must be escaped for 'mysql' */
sBuilder.append('\\');
sBuilder.append('0');
break;
case '\n': /* Must be escaped for logs */
sBuilder.append('\\');
sBuilder.append('n');
break;
case '\r':
sBuilder.append('\\');
sBuilder.append('r');
break;
case '\\':
sBuilder.append('\\');
sBuilder.append('\\');
break;
case '\'':
sBuilder.append('\\');
sBuilder.append('\'');
break;
case '"': /* Better safe than sorry */
if (escapeDoubleQuotes) {
sBuilder.append('\\');
}
sBuilder.append('"');
break;
case '\032': /* This gives problems on Win32 */
sBuilder.append('\\');
sBuilder.append('Z');
break;
case '\u00a5':
case '\u20a9':
// escape characters interpreted as backslash by mysql
// fall through
default:
sBuilder.append(c);
}
}
return sBuilder.toString();
}
}
In case you are dealing with a legacy system, or you have too many places to switch to PreparedStatements in too little time - i.e. if there is an obstacle to using the best practice suggested by other answers, you can try AntiSQLFilter
From:Source
public String MysqlRealScapeString(String str){
String data = null;
if (str != null && str.length() > 0) {
str = str.replace("\\", "\\\\");
str = str.replace("'", "\\'");
str = str.replace("\0", "\\0");
str = str.replace("\n", "\\n");
str = str.replace("\r", "\\r");
str = str.replace("\"", "\\\"");
str = str.replace("\\x1a", "\\Z");
data = str;
}
return data;
}
Most of the people are recommending PreparedStatements, however that requires you to have a direct connection with your Database using the Java Application. But then you'll have everyone else saying that you shouldn't have a direct connection to your database due to security issues, but utilize a Restful API to deal with queries.
In my opinion, as long as you're aware that you have to be careful with what you escape and do It deliberately, there shouldn't be a problem.
My solution is using contains() to check for SQL keywords such as UPDATE or other dangerous characters like = to completely nullify the SQL injection by asking the user to insert other characters on input.
Edit:
You can use this source material from W3Schools about Java Regular Expressions to do this validation on Strings.
After searching an testing alot of solution for prevent sqlmap from sql injection, in case of legacy system which cant apply prepared statments every where.
java-security-cross-site-scripting-xss-and-sql-injection topic
WAS THE SOLUTION
i tried #Richard s solution but did not work in my case.
i used a filter
The goal of this filter is to wrapper the request into an own-coded
wrapper MyHttpRequestWrapper which transforms:
the HTTP parameters with special characters (<, >, ‘, …) into HTML
codes via the org.springframework.web.util.HtmlUtils.htmlEscape(…)
method. Note: There is similar classe in Apache Commons :
org.apache.commons.lang.StringEscapeUtils.escapeHtml(…) the SQL
injection characters (‘, “, …) via the Apache Commons classe
org.apache.commons.lang.StringEscapeUtils.escapeSql(…)
<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>com.huo.filter.RequestWrappingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package com.huo.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletReponse;
import javax.servlet.http.HttpServletRequest;
public class RequestWrappingFilter implements Filter{
public void doFilter(ServletRequest req, ServletReponse res, FilterChain chain) throws IOException, ServletException{
chain.doFilter(new MyHttpRequestWrapper(req), res);
}
public void init(FilterConfig config) throws ServletException{
}
public void destroy() throws ServletException{
}
}
package com.huo.filter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang.StringEscapeUtils;
public class MyHttpRequestWrapper extends HttpServletRequestWrapper{
private Map<String, String[]> escapedParametersValuesMap = new HashMap<String, String[]>();
public MyHttpRequestWrapper(HttpServletRequest req){
super(req);
}
#Override
public String getParameter(String name){
String[] escapedParameterValues = escapedParametersValuesMap.get(name);
String escapedParameterValue = null;
if(escapedParameterValues!=null){
escapedParameterValue = escapedParameterValues[0];
}else{
String parameterValue = super.getParameter(name);
// HTML transformation characters
escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);
// SQL injection characters
escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);
escapedParametersValuesMap.put(name, new String[]{escapedParameterValue});
}//end-else
return escapedParameterValue;
}
#Override
public String[] getParameterValues(String name){
String[] escapedParameterValues = escapedParametersValuesMap.get(name);
if(escapedParameterValues==null){
String[] parametersValues = super.getParameterValues(name);
escapedParameterValue = new String[parametersValues.length];
//
for(int i=0; i<parametersValues.length; i++){
String parameterValue = parametersValues[i];
String escapedParameterValue = parameterValue;
// HTML transformation characters
escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);
// SQL injection characters
escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);
escapedParameterValues[i] = escapedParameterValue;
}//end-for
escapedParametersValuesMap.put(name, escapedParameterValues);
}//end-else
return escapedParameterValues;
}
}
If you are using PL/SQL you can also use DBMS_ASSERT
it can sanitize your input so you can use it without worrying about SQL injections.
see this answer for instance:
https://stackoverflow.com/a/21406499/1726419
You can try sanitize the parameters, (not the 1st option)
Codec ORACLE_CODEC = new OracleCodec();
String user = req.getParameter("user");
String query = "SELECT user_id FROM user_data WHERE user_name = '" +
ESAPI.encoder().encodeForSQL( ORACLE_CODEC, user) + "' ...;
First, ask the question - are double or single quotes, or backslashes needed in user entry fields?
Backslashes - no. Double and single quotes are rarely used in English and they are used differently in Britain than the U.S.
I say remove or replace them and you simplify.
private String scrub(
String parameter,
int length
)
{
String parm = null;
if ( parameter != null && parameter.length() > 0 && parameter.length() < length )
{
parm = parameter
.replace( "\\", " " )
.replace( "\"", " " )
.replace( "\'", " " )
.replace( "\t", " " )
.replace( "\r", " " )
.replace( "\n", " " )
.trim();
}
return parm;
}

Stringbuilder for Constants

In application, we are using lot of SQL queries which we are assigning it in a String constants. So my question is by replacing String with StringBuilder ,whether it can improve the performance as we have to do lot of concatenations for large SQL queries. That is in the below two approaches, which is the best approach:
Approach 1:
String sql= "select * from table1" + " table2 where column = :1";
Approach 2:
StringBuilder sql = new StringBuilder("select * from table1").
append( "table2 where column = :1" );
Can anyone suggest
Use string concatenation (with the + operator).
If the expressions are compile-time constant expressions then they will be evaluated at compile time.
If they are not; e.g.
String sql = "select * from " + tableName() + " where column = :1";
it is still better to let the compiler(s) optimize the concatenation, whether the expression is (runtime) constant or not. They should turn the above into equivalent code using StringBuilder.
The only scenario where you should consider using StringBuilder explicitly is when you are doing things like this:
String sql= "select * from table where ";
for (String part: parts) {
sql += part + " ";
}
Current generation compilers cannot optimize the above.
Finally, do not use StringBuilder to represent constants. They are mutable ... not constant.
Author note: the above examples are for illustration only. Think SQL injection! Use PreparedStatement.
If you really want to use stringbuilder, you can of course to make anywhere- some common utils class / DB utils usually, method like following.
but I dont think it will have better performance (or maybe yes, I guess it will dont be something extra improvement) for this case. From my point of view is looks #Stephen C example better
public static String composeSQLQuery(String columns,String tableName,String restrictions) {
StringBuilder sb = new StringBuilder("Select");
sb.append(" ");
sb.append(columns);
sb.append(" ");
sb.append("from");
sb.append(" ");
sb.append(tableName);
sb.append(" ");
sb.append("where");
sb.append(" ");
sb.append(restrictions);
return sb.toString();
}
In your case, it's no need to use Stringbuilder, because from the jdk5, the compiler automatically convert concentration to stringbuilder.
For example:
String a = "abc" + "def" + "xyz";
is equivalent with:
Stringbuilder a = new Stringbuilder();
a.append("abc");
a.append("def");
a.append("xyz");
But in case the concentration is in the loop, like:
String a = "";
for (int i = 0; i<10; i++){
a += "str";
}
Then, you should need to use Stringbuilder instead, because in this case, java cannot optimize the code and convert the concentration to Stringbuilder automatically.

Building PreparedStatement in Java With Variable Number of Columns for Inserting Data into Database [duplicate]

This question already has answers here:
How to insert values in a table with dynamic columns Jdbc/Mysql
(2 answers)
Closed 5 years ago.
What is a good design pattern to achieve this without endless code?
Given the scenario whereby the user may input 1...100 columns, maybe 23 one time, 32 on another insert, and 99 fields on another insert etc. All of which may be different fields each time too.
The PreparedStatement in Java needs to know what column names to enter first, how many ?'s to put into the values part of the INSERT query, the data types of the database field names to ensure the correct setInt and setString etc are entered.
For less than around 10 columns, you can kind of get around this challenge with the following logic;
1) If variableEnteredForFieldName is not null, then append to the relevant parts of the query in the form of a String builder type setup;
fieldName_1
?
2) Do the same for all entered field names
3) Strip out the final trailing , that will naturally be present in both the field names and the ?s
4) Create the PreparedStatement
5) Run through the same input parameters again to determine of the variableEnteredForFieldName is not null, if not null, then run a setInt or setString based on the known data type that the database requires and set this to the correct index number for the ?s.
As long as the query builder logic and the query filler logic have the names/values in the correct order in part 1 and part 2, then all works well. It does however mean duplicating the entire code that relates to this logic, one for generating the SQL to use when creating the PreparedStatement and another for filling the PreparedStatement.
This is manageable for a small number of input parameters, but this soon gets unmanageable for larger number of input parameters.
Is there a better design pattern to achieve the same logic?
The code below is an outline of all of the above for reference;
String fieldName1 = request.getParameter("fieldName1");
String fieldName2 = request.getParameter("fieldName2");
//Build Query
String fieldNames = "";
String fieldQuestionMarks = "";
if (fieldName1 != null) {
fieldNames = fieldNames + " FIELD_NAME_1 ,";
fieldQuestionMarks = fieldQuestionMarks + " ? ,";
}
if (fieldName2 != null) {
fieldNames = fieldNames + " FIELD_NAME_2 ,";
fieldQuestionMarks = fieldQuestionMarks + " ? ,";
}
//Trim the trailing ,
fieldNames = fieldNames.substring(1, fieldNames.length() - 1);
fieldQuestionMarks = fieldQuestionMarks.substring(1, fieldQuestionMarks.length() - 1);
try {
String completeCreateQuery = "INSERT INTO TABLE_NAME ( " + fieldNames + " ) VALUES ( " + fieldQuestionMarks + " );";
Connection con = DriverManager.getConnection(connectionURL, user, password);
PreparedStatement preparedStatement = con.prepareStatement(completeCreateQuery);
int parameterIndex = 1;
//Fill Query
if (fieldName1 != null) {
preparedStatement.setString(parameterIndex, fieldName1);
parameterIndex++;
}
if (fieldName2 != null) {
preparedStatement.setInt(parameterIndex, Integer.parseInt(fieldName2));
parameterIndex++;
}
}
As you can see, it's do-able. But even with just 2 optional fields, this code is huge.
The way I see it, if user is able to omit any of the columns from the list, then all columns are optional, and can be safely set to NULL during an insert. Therefore, all you need is one prepared statement with the "monster" INSERT, with all columns listed; then during the actual insert operation, you loop though the user-provided data, setting values for the columns provided, and calling setNull() for omitted columns. You'll need to maintain a structure somewhere (your DAO class most likely) mapping column names to their order in the SQL statement.

Use a variable within quotation marks of an output?

Since it seems that I try to learn how to work with SQL and Java the hard way my Question is:
Is it possible to use a variable IN BETWEEN Quotation marks?
I know that if you use the output you can work like this:
System.out.println(_name + " "+_points+ " "+_ID);
Is there a way to make it all in only one Quotation Mark pair?
Something like this:
System.out.println("_name _points _ID");
If yes, how do I mark them so that the Compiler knows that it is a Variable that he should print?
The reason why I want to know it is simple, I try to use executeUpdate
stmt.executeUpdate("INSERT INTO usertable VALUES("+_name+")");
and want it without the addition signs in there.
No you cannot use a variable inside a String literal. There are a couple of options though.
The first is the way you are currently doing it using concatenation with the + sign:
String query = "INSERT INTO table VALUES(" + name + ")";
Another way is to use String.format
String query = String.format("INSERT INTO table VALUES(%s)", name);
But the preferred method for SQL to avoid SQL Injection attacks is using a PreparedStatement:
String query = "INSERT INTO table VALUES(?)";
PreparedStatement statement = con.prepareStatement(query);
statement.setString(1, name);
statement.executeUpdate();
If you have a variable and want to pass it to your query statement with the quotation just add the quotes to your command. If the quotes are single quotes you don't need to scape then but if it is, you gona need to:
stmt.executeUpdate("INSERT INTO usertable VALUES('"+_name+"')");
^
|_Just add the quotes inside the
string
If it is a double quote (which I think is hardly the case) you need to scape then. Scaping is a way to tell the compiler that that specific string is special
stmt.executeUpdate("INSERT INTO usertable VALUES(\""+_name+"\")");
^
|_See the slash before the double quote?
But since you are learning you should learn the proper way to do it, because use variables with quotations will make your code prone to SQL Injection
So The better way to do it is to use Prepared Statements and language willl take care of the quotes for you. It would be:
String sql = "INSERT INTO usertable VALUES (?)"
preparedStatement = dbConnection.prepareStatement(sql);
preparedStatement.setString(1, "name you want");
See here a complete example: http://www.mkyong.com/jdbc/jdbc-preparestatement-example-select-list-of-the-records/
You can use preparedStatement:
Example :
query :
private static String SQL_INSERT_NEW_RULE = "INSERT INTO Table (A, B) VALUES (?, ?)";
then you can put them like this:
PreparedStatement pStmt=null;
pStmt = conn.prepareStatement(SQL_INSERT_NEW_RULE);
int index = 1;
pStmt.setString(index++, "value for A");
pStmt.setLong(index++, "Value for B");

Correct way to use StringBuilder in SQL

I just found some sql query build like this in my project:
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
Does this StringBuilder achieve its aim, i.e reducing memory usage?
I doubt that, because in the constructor the '+' (String concat operator) is used. Will that take the same amount of memory as using String like the code below? s I understood, it differs when using StringBuilder.append().
return "select id1, " + " id2 " + " from " + " table";
Are both statements equal in memory usage or not? Please clarify.
Edit:
BTW, it is not my code. Found it in an old project. Also, the query is not so small as the one in my example. :)
The aim of using StringBuilder, i.e reducing memory. Is it achieved?
No, not at all. That code is not using StringBuilder correctly. (I think you've misquoted it, though; surely there aren't quotes around id2 and table?)
Note that the aim (usually) is to reduce memory churn rather than total memory used, to make life a bit easier on the garbage collector.
Will that take memory equal to using String like below?
No, it'll cause more memory churn than just the straight concat you quoted. (Until/unless the JVM optimizer sees that the explicit StringBuilder in the code is unnecessary and optimizes it out, if it can.)
If the author of that code wants to use StringBuilder (there are arguments for, but also against; see note at the end of this answer), better to do it properly (here I'm assuming there aren't actually quotes around id2 and table):
StringBuilder sb = new StringBuilder(some_appropriate_size);
sb.append("select id1, ");
sb.append(id2);
sb.append(" from ");
sb.append(table);
return sb.toString();
Note that I've listed some_appropriate_size in the StringBuilder constructor, so that it starts out with enough capacity for the full content we're going to append. The default size used if you don't specify one is 16 characters, which is usually too small and results in the StringBuilder having to do reallocations to make itself bigger (IIRC, in the Sun/Oracle JDK, it doubles itself [or more, if it knows it needs more to satisfy a specific append] each time it runs out of room).
You may have heard that string concatenation will use a StringBuilder under the covers if compiled with the Sun/Oracle compiler. This is true, it will use one StringBuilder for the overall expression. But it will use the default constructor, which means in the majority of cases, it will have to do a reallocation. It's easier to read, though. Note that this is not true of a series of concatenations. So for instance, this uses one StringBuilder:
return "prefix " + variable1 + " middle " + variable2 + " end";
It roughly translates to:
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();
So that's okay, although the default constructor and subsequent reallocation(s) isn't ideal, the odds are it's good enough — and the concatenation is a lot more readable.
But that's only for a single expression. Multiple StringBuilders are used for this:
String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;
That ends up becoming something like this:
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;
...which is pretty ugly.
It's important to remember, though, that in all but a very few cases it doesn't matter and going with readability (which enhances maintainability) is preferred barring a specific performance issue.
When you already have all the "pieces" you wish to append, there is no point in using StringBuilder at all. Using StringBuilder and string concatenation in the same call as per your sample code is even worse.
This would be better:
return "select id1, " + " id2 " + " from " + " table";
In this case, the string concatenation is actually happening at compile-time anyway, so it's equivalent to the even-simpler:
return "select id1, id2 from table";
Using new StringBuilder().append("select id1, ").append(" id2 ")....toString() will actually hinder performance in this case, because it forces the concatenation to be performed at execution time, instead of at compile time. Oops.
If the real code is building a SQL query by including values in the query, then that's another separate issue, which is that you should be using parameterized queries, specifying the values in the parameters rather than in the SQL.
I have an article on String / StringBuffer which I wrote a while ago - before StringBuilder came along. The principles apply to StringBuilder in the same way though.
[[ There are some good answers here but I find that they still are lacking a bit of information. ]]
return (new StringBuilder("select id1, " + " id2 " + " from " + " table"))
.toString();
So as you point out, the example you give is a simplistic but let's analyze it anyway. What happens here is the compiler actually does the + work here because "select id1, " + " id2 " + " from " + " table" are all constants. So this turns into:
return new StringBuilder("select id1, id2 from table").toString();
In this case, obviously, there is no point in using StringBuilder. You might as well do:
// the compiler combines these constant strings
return "select id1, " + " id2 " + " from " + " table";
However, even if you were appending any fields or other non-constants then the compiler would use an internal StringBuilder -- there's no need for you to define one:
// an internal StringBuilder is used here
return "select id1, " + fieldName + " from " + tableName;
Under the covers, this turns into code that is approximately equivalent to:
StringBuilder sb = new StringBuilder("select id1, ");
sb.append(fieldName).append(" from ").append(tableName);
return sb.toString();
Really the only time you need to use StringBuilder directly is when you have conditional code. For example, code that looks like the following is desperate for a StringBuilder:
// 1 StringBuilder used in this line
String query = "select id1, " + fieldName + " from " + tableName;
if (where != null) {
// another StringBuilder used here
query += ' ' + where;
}
The + in the first line uses one StringBuilder instance. Then the += uses another StringBuilder instance. It is more efficient to do:
// 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();
Another time that I use a StringBuilder is when I'm building a string from a number of method calls. Then I can create methods that take a StringBuilder argument:
private void addWhere(StringBuilder sb) {
if (where != null) {
sb.append(' ').append(where);
}
}
When you are using a StringBuilder, you should watch for any usage of + at the same time:
sb.append("select " + fieldName);
That + will cause another internal StringBuilder to be created. This should of course be:
sb.append("select ").append(fieldName);
Lastly, as #T.J.rowder points out, you should always make a guess at the size of the StringBuilder. This will save on the number of char[] objects created when growing the size of the internal buffer.
You are correct in guessing that the aim of using string builder is not achieved, at least not to its full extent.
However, when the compiler sees the expression "select id1, " + " id2 " + " from " + " table" it emits code which actually creates a StringBuilder behind the scenes and appends to it, so the end result is not that bad afterall.
But of course anyone looking at that code is bound to think that it is kind of retarded.
In the code you have posted there would be no advantages, as you are misusing the StringBuilder. You build the same String in both cases. Using StringBuilder you can avoid the + operation on Strings using the append method.
You should use it this way:
return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();
In Java, the String type is an inmutable sequence of characters, so when you add two Strings the VM creates a new String value with both operands concatenated.
StringBuilder provides a mutable sequence of characters, which you can use to concat different values or variables without creating new String objects, and so it can sometimes be more efficient than working with strings
This provides some useful features, as changing the content of a char sequence passed as parameter inside another method, which you can't do with 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);
}
More info at http://docs.oracle.com/javase/tutorial/java/data/buffers.html
You could also use MessageFormat too

Categories