为SQL语句字符串使用静态最终字符串的基本原理是什么?

时间:2013-11-22 02:55:39

标签: java constants

似乎很多地方都将SQL字符串的类常量用作最佳实践。

所以而不是:

String sql = "select * from users";
PreparedStatement stmt = conn.prepareStatement(sql);

最好这样做:

private static final String SQL_SELECT_USERS = "select * from users";

void someMethod() {
  ...
  PreparedStatement stmt = conn.prepareStatement(SQL_SELECT_USERS);
  ...
}

后者有什么好处?在我看来,它的可读性较差。

感谢。

5 个答案:

答案 0 :(得分:5)

如果它是短文并且仅在一个地方使用,那么就不需要将其作为字段。在你的情况下,它可能是这个

PreparedStatement stmt = conn.prepareStatement("select * from users");

你可以在JDK源代码中找到很多这样的编码风格,比如这个

    if (is == null) {
        throw new IllegalArgumentException("InputStream cannot be null");
    }

答案 1 :(得分:3)

这里有两个问题。第一个是声明变量final;这提供了一点安全性(你不能在以后偶然修改它,引入一个潜在的间歇性运行时错误),并且它向任何阅读代码的人表明该值应该是一个常数。

将字符串拉出到常量字段(static final)会更有效,并有助于在一个地方收集类中的重要值。如果查询语句隐藏在someMethod()中,则更难找到并且不能在方法之间重用。通过使用类级别常量而不是“魔术值”,IDE和Javadoc可以识别他们的角色,稍后需要更改代码的开发人员可以更轻松地找到它们。

答案 2 :(得分:3)

错误或其他方式无法更改final变量。将语句字符串声明为final可确保避免此错误:

String s = "select * from users";

// many lines of code
s = "temp";
// do something with s
// many lines of code
PreparedStatement stmt = conn.prepareStatement(sql);

无论您在声明s后添加了多少行代码,如果sfinal,编译器会告诉您某些代码何时尝试使用新值进行破坏。< / p>

如果您总是在变量之后立即执行准备好的语句,那么简单的接近将帮助您避免此错误。此外,您的实际示例使用了更好的名称(sql而不仅仅是s)。但是,您仍然不知道在编写代码后谁将编辑代码。

任何时候你都可以使用该语言的一个功能来让编译器帮助你,你应该这样做。在这种情况下,final声明允许编译器保护您免受破坏预定义字符串的人的攻击。不可否认,在这个具体的例子中,好处似乎很小,但一般情况下应该声明常量事物final,我认为没有理由违反这个规则。

至于声明它private,这只是典型的数据隐藏。如果此字符串不是此类的“接口”的一部分,则它应该是私有的;默认情况下,将所有内容设为私有,并仅将界面内容公开。

编辑:还有一点值得考虑。如果你有一个包含SQL的文字字符串,并且你在编写SQL时犯了一些错误,编译器无法帮助你。 "selct * from users"是完全有效的字符串; Java编译器不知道它是一个SQL错误,因此您可以在运行时找到它。

您可以创建SQL片段的常量,并将它们与字符串连接放在一起。关于这一点很重要的是,如果你拼错了一些东西,现在你可能会遇到编译器错误。

private final String SELECT_STAR_FROM = "select * from ";
private final String USERS_TABLE = "users";

// many lines of code
PreparedStatement stmt0 = conn.prepareStatement(SELECT_STAR_FROM + USERS_TABLE);

// this line would fail at run time
PreparedStatement stmt1 = conn.prepareStatement("selct * from users");

// this line fails at compile time and the compiler points you at it
PreparedStatement stmt0 = conn.prepareStatement(SELCT_STAR_FROM + USERS_TABLE);

进行JNI编程时,需要使用隐藏代码指定功能签名。我制作了一堆常量,我的JNI程序将常量连接在一起以构建函数签名。这是C代码,而不是Java,但它说明了与上面相同的想法。

#define JSIG_CONSTRUCTOR "<init>"

如果我写了一个拼写错误并为构造函数写了"<intt>",那么C编译器就无法帮助我;这将是一个运行时错误(JNI将无法找到构造函数)。但是如果我在代码中使用JSIG_CONSTRUCTOR,如果我输了一个错字,编译器会告诉我。

答案 3 :(得分:2)

这只是使用常量而不是文字的非常古老(且非常合理)编程实践的一个示例,例如SQL_SELECT_USERS变量,而不是"select * from users"文字字符串。

在类的代码中使用数字(例如,BUFFER_SIZE代替4096)或任何其他类型的常量值的类似方法是适用的(并且是可取的)(或多个班级)。使用Java关键字声明final中的常量(即,仅初始化一次且永不更改的变量)。

这也不是“不太可读”的策略。相反,如果常量的名称是直观的,那么比在其位置使用的文字更有意义。最重要的是,在声明常量之后,它可以在代码中根据需要多次使用,如果稍后需要更改它的值(确实是非常常见的情况),那么(无论如何)更改只需要发生在一个地方(常数的声明),而不是必须搜索类来进行所有更改。

对于表示数字的常量,使用代码中的幻数来调用实际数而不是单个常量。这是不可取的,因为经过一段时间后,很难记住为什么这样定义一个值。具有直观名称的常量可以解决该问题。

因此,使用常量而不是文字是一种强烈建议采用的编程习惯。

答案 4 :(得分:1)

静态final不再是“安全”,也不比简单地在使用点编码字符串文字更快。 (字符串的数量是相同的,因为文字总是被实现,并且编译器将静态final最终转换为文字。)

使用更全局值的原因是允许字符串只编码一次,而不是重新编码几个位置,并在一个位置收集所有SQL字符串定义。根据环境(以及程序员的能力),这可能是一个好主意或坏主意。一个问题是它可以极大地混淆代码。另一个是如果值在另一个类中定义,它必须是类前缀的,导致一些长名称(如果它没有像这样全局定义那么编码只有一次的优点很容易丢失。)

应该注意的是,使用这种技术“提高安全性”是愚蠢的,然后使用外部提供的数据值无法使用准备好的语句进行操作。