Java8的Lambda表达式确实是一个非常好的特性,但是在哪些场合下使用,其实还是需要仔细考虑的。我们当然不能为了使用而使用,而是需要找到切实有用的场合。在JDBC编程中,例如查询语句,首先需要进行查询参数绑定,其次是处理返回的结果集,这两步操作是每个查询都不同的,而获取JDBC连接,准备PreparedStatement,以及释放资源则都是完全相同的,这就是一个Lambda表达式应用的绝佳场景。
在讨论具体的实现细节之前,想先讨论一下JDBC的问题。目前相信90%以上的Java程序员都不会再用JDBC了,基本都采用某种OR映射机制,如Hibernate、Persistent API等,采用所谓OO的方式来操作数据库。但是实际上这种方式在理论上很好,在实际应用中是有很大问题的,尤其在目前大数据的情况下,将数据和程序绑在一起是非常不可取的,同时采用OR映射之后,抽象出的数据操作在功能和性能方面,都完全不能与SQL相比,因此直接使用JDBC还是值得考虑的。现在的语言之争,基本是该语言下最流行的框架之争,Java语言下基本就是SSH和JBOSS,由于这两个异常流行和笨重的框架,使Java基本淡出了互联网应用开发领域。其实结合Java的NIO 2以及最近的Lambda表达式、Stream API的新特性,完全有可能写比Node.js更优秀的框架,只可惜java开发社区在自己的思维惯性下,很难使这种可能变为现实。
好了,言归正传,我们首先假设我们需要查询数据库中的t_user表,其中有user_id, user_name, nick_name, birthday等字段,我们需要构造一个SQL查询,找出user_id小于10000的记录。
首先我们先定义值对象:
public class UserInfo {
public long getUserId() {
return userId;
}
public void setUserId(long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Calendar getBirthday() {
return birthday;
}
public void setBirthday(Calendar birthday) {
this.birthday = birthday;
}
private long userId = 0;
private String userName = null;
private String nickName = null;
private Calendar birthday = null;
}
其次我们定义获取和释放JDBC连接的函数:
private Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/WkyDb?user=wky&password=wky123&" +
"useUnicode=true&characterEncoding=utf-8&" +
"autoReconnect=true&failOverReadOnly=false");
} catch (Exception e) {
System.exit(0);
}
return conn;
}
private void closeConnection(Connection conn) {
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
下面我们来定义参数绑定函数式接口:
@FunctionalInterface
public interface FParamBinder<T> {
public void bindParams(PreparedStatement stmt, T param) throws SQLException;
}
接下来我们定义接收返回结果的函数式接口:
@FunctionalInterface
public interface FResetSetter<T> {
public List<T> getResultSet(ResultSet rst) throws SQLException;
}
下面是SQL查询的具体实现函数:
public <T> List<T> executeQuery(String sql, FParamBinder<T> binder, T cond, FResetSetter<T> setter) {
Connection conn = getConnection();
PreparedStatement stmt = null;
ResultSet rst = null;
List<UserInfo> items = new ArrayList<UserInfo>();
UserInfo item = null;
List<T> recs = null;
try {
stmt = conn.prepareStatement(sql);
binder.bindParams(stmt, cond); // 调用参数绑定函数式接口
rst = stmt.executeQuery();
recs = setter.getResultSet(rst); // 获取返回结果集
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rst != null) {
rst.close();
}
if (stmt != null) {
stmt.close();
}
} catch (Exception ex) {}
}
closeConnection(conn);
return recs;
}
这个函数实现与普通的JDBC编程中的查询不同,参数绑定和接收结果集这两部分都是通过传入的函数式接口变量来实现,因此代码显得非常简洁。
下面是其他函数调用SQL查询时对上面函数的调用,如下所示:
public void testIt() {
String sql = "select user_id, user_name, nick_name, birthday from t_user where user_id<?";
FParamBinder<UserInfo> binder = (PreparedStatement stmt, UserInfo param) -> {
stmt.setLong(1, param.getUserId());
};
UserInfo cond = new UserInfo();
cond.setUserId(10000000L);
FResetSetter<UserInfo> setter = (ResultSet rst) -> {
List<UserInfo> items = new ArrayList<UserInfo>();
UserInfo item = null;
while (rst.next()) {
item = new UserInfo();
item.setUserId(rst.getLong(1));
item.setUserName(rst.getString(2));
items.add(item);
}
return items;
};
List<UserInfo> recs = executeQuery(sql, binder, cond, setter);
System.out.println("size=" + recs.size() + "!");
for (UserInfo u : recs) {
System.out.println("U:" + u.getUserName() + "-" + u.getUserId() + "!");
}
}
如上所述,调用者需要指定查询SQL语句,定义参数绑定函数式接口变量,结果集接收函数式接口变量,然后调用上面的executeQuery方法,就可以获取到结果集,然后就可以进行下一步的操作了。