我十分惊讶的发现,我近的一篇文章——《Java开发者写SQL时常犯的10个错误》——近在我的博客和我的合作伙伴DZone上非常的受欢迎。(这篇博客)的流行程度说明了几件事:
  SQL在专业的Java开发中多么重要。
  基本的SQL知识被忘掉(的情况)普遍存在。
  通过embracing SQL,你能了解像 jOOQ或MyBatis这样的以SQL为中心的库正好反应了市场的需要。 令人惊喜的是有用户提到了我博客上贴的一篇“SLICK’s mailing list”,SLICK是Scala中的一个不以SQL为中心的数据库访问库,像LINQ(还有LINQ-to-SQL),它侧重语言整合,而不是SQL语句的产生。
  无论如何,我之前仓促列出的常见错误还没列完。因此我为你另外准备了10个没那么常见的,但Java开发者在写SQL语句时同样爱犯的错误。
  1、不用PreparedStatements
  有意思的是,在JDBC出现了许多年后的,这个错误依然出现在博客、论坛和邮件列表中,即便要记住和理解它是一件很简单的事。开发者不使用PreparedStatements的原因可能有如下几个:
  他们对PreparedStatements不了解
  他们认为使用PreparedStatements太慢了
  他们认为写PreparedStatements太费力
  来吧,我们来破除上面的谣言。96%的案例中,用PreparedStatement比静态声明语句更好。为什么呢?是下面这些简单的原因:
  使用内联绑定值(inlining bind values)可以从源头上避免糟糕的语句引起语法错误。
  使用内联绑定值可以避免糟糕的语句引起的SQL注入漏洞。
  当插入更多“复杂的”数据类型(比如时间戳、二进制数据等等)时,可以避免边缘现象(edge-cases)。
  你若保持PreparedStatements的连接开启状态而不马上关闭,只要重新绑定新值可以进行复用。
  你可以在更多复杂的数据库里使用adaptive cursor sharing——自适应游标共享(Oracle的说法)。这可以帮你在每次新设定绑定值时阻止SQL语句硬解析。
  (译者注:硬解析的弊端。硬解析即整个SQL语句的执行需要完完全全的解析,生成执行计划。而硬解析,生成执行计划需要耗用CPU资源,以及SGA资源。在此不得不提的是对库缓存中 闩的使用。闩是锁的细化,可以理解为是一种轻量级的串行化设备。当进程申请到闩后,则这些闩用于保护共享内存的数在同一时刻不会被两个以上的进程修改。在 硬解析时,需要申请闩的使用,而闩的数量在有限的情况下需要等待。大量的闩的使用由此造成需要使用闩的进程排队越频繁,性能则逾低下)
  某些特殊情况下你需要对值进行内联绑定,这是为了给基于成本的性能优化器提示该查询将要涉及的数据集。典型的情况是用“常量”判断:
  DELETED = 1
  STATUS = 42
  而不应该用一个“变量”判断:
  FIRST_NAME LIKE “Jon%”
  AMOUNT > 19.95
  要注意的是,现代数据库已经实现了绑定数据窥探(bind-variable peeking)。因此,默认情况下,你也可以为你所有的查询参数使用绑定值。在你写嵌入的JPQL或嵌入的SQL时,用JPA CriteriaQuery或者jOOQ这类高层次的API可以很容易也很清晰的帮你生成PreparedStatements语句并绑定值。
  更多的背景资料:
  绑定数据窥探(bind-variable peeking)的附加说明:这个主题有一篇有趣的文章,它出自Oracle大师Tanel Poder之手。
  游标分享。StackOverflow上一个有趣的问题。
  解决方案:
  默认情况下,总是使用PreparedStatements来代替静态声明语句,而永远不要在你的SQL语句嵌入内联绑定值。
  2、返回太多列
  这个错误发生的非常频繁,它不光会影响你的数据库执行计划,也会对你的Java应用造成不好的影响。让我们先看看对后者的影响:
  对Java程序的不良影响:
  如 果你为了满足不同DAO层之间的数据复用而select *或者默认的50个列,这样将会有大量的数据从数据库读入到JDBC结果集中,即使你不从结果集读取数据,它也被传递到了线路上并被JDBC驱动器加载到 了内存中。如果你知道你只需要2-3列数据的话,这造成了严重的IO和内存的浪费。
  这个(问题的严重性)都是显而易见的,要小心……
  对数据库执行计划的不良影响:
  这 些影响事实上可能比对Java应用的影响还要严重。当复杂的数据库要针对你的查询请求计算出佳执行计划时,它会进行大量的SQL转换(SQL transformation )。还好,请求中的一部分可以被略去,因为它们对SQL连映射或过滤条件起不了什么作用。我近写了一篇博客来讲述这个问题:元数据模式会对Oracle查询转换产生怎样的影响。
  现在,给你展示一个错误的例子。想一想有两个视图的复杂查询:
  SELECT *
  FROM  customer_view c
  JOIN  order_view o
  ON  c.cust_id = o.cust_id
  每个关联了上述关联表引用的视图也可能再次关联其他表的数据,像 CUSTOMER_ADDRESS、ORDER_HISTORY、ORDER_SETTLEMENT等等。进行select * 映射时,你的数据库除了把所有连接表都加载进来以外别无选择,实际上,你感兴趣的数据可能只有这些:
  SELECT c.first_name, c.last_name, o.amount
  FROM  customer_view c
  JOIN  order_view o
  ON  c.cust_id = o.cust_id
  一个好的数据库会在转换你的SQL语句时自动移除那些不需要的连接,这样数据库只需要较少的IO和内存消耗。
  解决方案:
  永远不要用select *(这样的查询)。也不要在执行不同请求时复用相同的映射。尽量尝试减少映射到你所真正需要的数据。
  需要注意的是,想在对象-关系映射(ORMs)上达成这个目标有些难。