【问题标题】:How to generate String "elegantly" in Java?如何在 Java 中“优雅地”生成字符串?
【发布时间】:2011-04-27 11:50:48
【问题描述】:

我想生成一个字符串比如sql命令:

   "INSERT INTO xxx VALUES(XXX, XXX, XXX)"

目前我使用StringBuilder 和一些String 常量,如“INSERT INTO”来连接表名和插入值的输入字符串参数。

但是,除了性能问题之外,这种简单的连接看起来并不优雅。 有没有其他方法可以做到这一点?

在我看来,JDBC 的预处理语句就是这种“命令模板”的一个很好的例子:

PreparedStatement pstmt=connection.createPreparedStatement("INSERT INTO ? VALUES(?,?,?)");

然后你可以设置表名和插入值。

pstmt.setString(1,"tableA");
pstmt.setInt(2, 100);
...

但是,我不能使用准备好的语句,因为我想要的只是字符串......

有人给了我一些使用java.util.Regex 或JavaCC 来生成字符串的提示。 但据我所见,无论为某些代码优雅问题选择什么,Java字符串都必须由StringBuilder之类的东西生成,对吧???

【问题讨论】:

  • 请注意,通过连接字符串和变量值生成 SQL 语句会使您的程序容易受到 SQL 注入攻击;您需要自己正确转义这些值以防止这种情况发生。 (PreparedStatement 会自动为您执行此操作)。

标签: java string templates


【解决方案1】:

你可以使用String.format():

String.format("insert into %s values('%s', '%s', '%s')", "user", "user123", "pass123", "yellow");

值得注意的是,任何这些“字符串构建”技术都会使您容易受到 SQL 注入攻击。您应该尽可能使用 JDBC 参数化查询。

编辑为在字符串周围添加引号。

【讨论】:

  • 如果要插入的值的个数变化很大而且事先不知道怎么办?那我每次调用这个方法还是要写格式...
  • 否决票可能是因为该 SQL 不起作用 - 该示例缺少单引号:"insert into %s values('%s', '%s', '%s')"。我没有投反对票,因为这不是直接询问的内容,不相关......
  • 好点卡洛斯,谢谢。我已经进行了相应的编辑。不过不值得投反对票,投反对票的人还没有发言。
  • downvote 是为了推荐对 SQL 注入开放的代码。你在文中提到了,为什么还要鼓励OP呢?
  • 嗯...看,我会因为回答不同的问题而投反对票。 “如何让这段代码更整洁?” “以下是防止 SQL 注入攻击的方法”。嘿嗬。您似乎没有对同一主题的所有其他答案投反对票。
【解决方案2】:

也许你正在寻找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);

【讨论】:

  • 要考虑的一件事是,当查询是静态的时,这很有效,如果查询更加动态,例如一个 select 语句,根据运行时变量具有不同的 where 谓词,那么这种方法可能会变得不那么优雅,因为消息格式上的变量将是谓词语句而不是实际参数。
  • Baran 我知道,我不是在质疑你的方法,我实际上赞成它。我只是指出这一点以防万一,因为问题不清楚。
【解决方案3】:

你试过只用'+'吗?

String sql = "INSERT INTO " + table
           +" VALUES(" + value1 + ", " + value2 + ", " = value3+")";

【讨论】:

  • 它实际上使用了StringBuilder,对于简单的情况它更短更清晰。你需要解释一下你所说的劣等是什么意思。
  • StringBuilder 比使用 += 等运算符的 String 更高效,但是如果您使用普通的 + 并只构建一个字符串,它同样高效(如果编译器可以组合字符串文字)
  • 无论如何,它会比最简单的 SQL 更新快 100 倍以上,因此您不必担心。只需编写尽可能清晰的代码即可。
  • 与 SQL 查询本身的成本(网络操作、磁盘操作等)相比,构建字符串的成本几乎可以肯定是完全无关的。如果这是一个主要问题,那么我建议这样做是过早的优化。
  • 这个答案实际上是我如何为我正在运行的程序构建字符串并将 60,000 行插入到 mysql 数据库表中,即使这不是“最有效的”(我怀疑是否有更高效的 cpu 方式)dty 是正确的,考虑到有多少网络开销将在其中发挥作用,这是一个毫无意义的优化。
【解决方案4】:

鉴于各种其他答案,但没有一个得到您的认可,也许您应该接受实际的字符串生成(无 JPA、PreparedStatement 等)将相当不雅,并使用静态 sql 生成器创建实用程序类.

edit 显示一个示例,说明如果无法选择诸如 PreparedStatement 之类的预先存在的类,我将如何处理此问题。它不是最优雅的,但它做了它应该做的(假设我输入正确)。

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

这样,您的业务代码将保持相对优雅,您可以随心所欲地使用 SQL 字符串生成。您还可以使用它来“保证”您的所有插入都受到保护,免受 SQL 注入等攻击。

【讨论】:

  • 这是正确的。但我想要的只是实际的字符串生成。是否将生成放在这样的 util 类中不是我现在最关心的问题。谢谢
  • +1 表示“您应该接受实际的字符串生成将相当不雅”,并将丑陋从业务代码转移到实用程序中。
  • 我扩展了我的示例以显示实际代码。它涉及创建一个枚举来跟踪您的参数类型和一个名为CustomParameter 的类,在我看来,它只有字段来保存类型(枚举)和一个Object 的值。不幸的是,这意味着包装你的原始类型,但考虑到你提出的限制,我认为这是最干净的方法。
  • 当我在考虑这个问题时,您可能希望向CustomParameter 添加一个字段以包含列名。这样你就可以改变你的插入,只添加某些列而不是所有列。
【解决方案5】:

使用 StringTemplate (http://www.stringtemplate.org/) 可能是一个不错的选择:

这看起来更好,对吧?

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-21
    • 2011-08-07
    • 2015-03-22
    相关资源
    最近更新 更多