月度归档:2015年09月

《深入理解 Scala》第五章 — 利用隐式转换编写更有表达力

Scala 的隐式转换系统定义了一套定义良好的查找机制,让编译器能够调整代码。Scala 编译器可以推导下面两种情况:

  • 缺少参数的方法调用或构造器调用;
  • 缺少了的从一种类型到另一种类型的转换。(是指调用某个对象上不存在的方法时,隐式转换自动把对象转换成有该方法的对象)

介绍隐式转换系统

implicit 关键字可通过两种方式使用:1、方法或变量定义;2、方法参数列表。如果关键字用在变量或方法上,等于告诉编译器在隐式解析(implicit resolution)的过程中可以使用这些方法和变量。

隐式解析是指编译器发现代码里的缺少部分信息,而去查找缺少信息的过程。

implicit 关键字用在方法参数列表的开头,是告诉编译器应当通过隐式解析来确定所有的参数值。

scala> def findAnInt(implicit x: Int) = x
findAnInt: (implicit x: Int)Int

scala> findAnInt
<console>:9: error: could not find implicit value for parameter x: Int
              findAnInt
              ^

scala> implicit val test = 5
test: Int = 5

scala> findAnInt
res1: Int = 5

scala> def findTwoInt(implicit a: Int, b: Int) = a + b
findTwoInt: (implicit a: Int, implicit b: Int)Int

scala> findTwoInt  // implicit 作用于整个参数列表
res2: Int = 10

scala> implicit val test2 = 5
test2: Int = 5

scala> findTwoInt
<console>:11: error: ambiguous implicit values:
 both value test of type => Int
 and value test2 of type => Int
 match expected type Int
              findTwoInt
              ^

scala> findTwoInt(2, 5)  // 隐式的方法参数仍然能够显式给出
res4: Int = 7

隐式的方法参数仍然能够显式给出。

继续阅读

隐式类型转换导致全表扫描

最近系统改版了一部分(其实就是重做。。。),上线之后又开始出现数据库 IO 告警了。

DBA 抓出一个全表扫描的 SQL,是把范围在两个钟内的数据找出来做更新的,这个查询的时间字段是有索引的,结果却没有用上,
跟另一个表关联的时候,关联字段也是有索引的,但还是没有用上。

在两个钟内的数据量应该是很小的,相对于总量来说,时间字段的索引没理由不用啊;从主表找出来的数据量不大,再去关联表查询也应该走索引的。
感觉就是执行计划不对了,去找 DBA 做固化。

找 DBA 先用 hint 生成一个走索引的执行计划,然后做了固化。

这个 SQL 是两个钟才执行一次的,结果固化之后值班 DBA 说主表还是全表扫描。找 DBA 再做一次固化,还是主表全表扫描。最后开了 tunning ,提示说有隐式类型转换,主表没法走索引。

大致扫了一下代码,Java 里用的数据类型是 Date 的,而表的列的类型也是 Date,应该不需要做隐式类型转换啊。

问题出在 MyBatis 的参数类型指定为 timestamp 而不是 Date;由于查询需要时分秒,而 Date 会抹去这些值,所以被指定为 timestamp,
而这个类型刚好对应到 Oracle 的 timestamp 类型,所以就出现隐式类型转换。

细节吶。。。

Spring MVC 与线程

最近又被一个连环坑坑惨了,我踩的坑是与 Spring MVC 的线程模型有关的。

我们的系统现在有两个 war 包,分别部署在不同的 JBoss 上,第一个是互联网可访问的,叫 front 吧,第二个是在防火墙之后的,只能通过 front 来访问,叫 backend,是可以访问数据库的。

front 通过 Hessian 调用 backend 的服务。

现在有个需求要求 backend 记录互联网用户的 IP 和其他一些参数,接口太多,不可能每个方法再添加参数,由于 Hessian 也运行是在 HTTP 上的,所以我想在 Hessian 的 HTTP 请求头里把这些需要的参数传过去。

根据 debug,Hessian 采用的是 JDK 的 HessianURLConnectionFactory 来创建连接,处理 HTTP 请求,所以我就扩展了它的方法 public HessianConnection open(URL url) throws IOException ,在里面把参数放到请求头里。由于要取的是客户端实际 IP 等信息,所以还需要获取客户端的请求 HttpServletRequest ,刚好 Spring MVC 里面有个 RequestContextHolder 工具,持有了 HttpServletRequest 。然后就有了下面这行代码:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 。坑就挖成了。

点开 RequestContextHolder.getRequestAttributes() 方法来看看:

public static RequestAttributes getRequestAttributes() {
     RequestAttributes attributes = requestAttributesHolder.get();
     if (attributes == null) {
          attributes = inheritableRequestAttributesHolder.get();
     }
     return attributes;
}

requestAttributesHolder 是这样的:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
          new NamedThreadLocal<RequestAttributes>("Request attributes");

NamedThreadLocal 是这样定义的: public class NamedThreadLocal<T> extends ThreadLocal<T> {
也就是说这个请求是跟线程绑定的,在别的线程是获取不到的。

其他的坑是这样的:
在 front 有个地方用了异步去调用 backend,其实是根本没必要用异步的(因为当前线程发出请求后是在等待响应的);而这个异步调用在测试环境时没有启用,被开关切去走另一个同步的分支,而生产环境确是必须走这个异步分支的。