我正在玩lambdas并进入我的脑海,我想尝试创建一个简单的数据库/对象映射器作为学习的一部分。
是的,有很多框架已经做到这一点,但这更多是关于学习和我遇到的问题是技术。
首先,我想在枚举中定义所有映射逻辑。
只有一堆字段名称才开始简单明了:
enum ThingColumn {
id, language;
}
这让我创建了以下方法(实现不相关),它为api用户提供了对列的编译检查:
public Collection<Thing> findAll(ThingColumn... columns);
之后我想在枚举中定义更多规则,特别是如何将结果从java.sql.ResultSet
映射到我的Thing
类。
开始简单我创建了一个功能界面:
@FunctionalInterface
static interface ThingResultMapper {
void map(Thing to, ResultSet from, String column) ;
}
并将其添加到枚举:
enum ThingColumn {
id((t, rs, col) -> t.setId(rs.getLong(col))),
language((t, rs, col) ->t.setLanguage(rs.getString(col)));
ThingColumn(ThingResultMapper mapper){..}
}
我创建了一个mapResultSetRow
方法,该方法使用枚举中的lambdas从ResultSet
中提取数据:
public Thing mapResultSetRow(ResultSet rs, ThingColumn... fields) {
Thing t = new Thing();
Stream.of(fields)
.forEach(f -> f.getMapper().map(t, rs, f.name()));
return t;
}
然后,上述findAll
可以使用mapResultSetRow
将相关地图集应用于ResultSet
。干净整洁。
几乎无论如何。我认为枚举非常难看,并且包含很多锅炉板,你必须为每个映射添加lambda。理想情况下,我想这样做:
enum ThingColumn {
id(ResultSet::getLong, Thing::setId),
language(ResultSet::getString, Thing::setLanguage);
}
然而,这当然不能编译,现在我卡住了,非静态/静态的问题..我将首先通过消除一些噪声将其分解:
enum ThingColumn {
id(ResultSet::getLong); // <<- compile error
ThingColumn(Function<String,?> resultSetExtractor) {..}
}
编译错误:Cannot make a static reference to the non-static method getLong(String) from the type ResultSet
。
我想我想要的是要么无法做到,要么可以通过改变枚举构造函数中labmda的签名来实现。
我在这个问题中发现了类似的问题: Limits of static method references in Java 8 Dmitry Ginzburg的回答(向下滚动,不被接受为正确答案)概述了一些问题,但没有解决方案。
感谢您阅读目前为止:)
有什么想法吗?
答案 0 :(得分:5)
第一个示例不起作用,因为您需要处理已检查的SQLException
。这可以很容易地修复。首先,在您的功能界面上声明此异常:
@FunctionalInterface
static interface ThingResultMapper {
void map(Thing to, ResultSet from, String column) throws SQLException;
}
其次,而不是getMapper
在枚举中创建一个处理它的map
方法:
enum ThingColumn {
id((t, rs, col) -> t.setId(rs.getLong(col))),
language((t, rs, col) ->t.setLanguage(rs.getString(col)));
private ThingResultMapper mapper;
ThingColumn(ThingResultMapper mapper){
this.mapper = mapper;
}
public void map(Thing to, ResultSet from) {
try {
mapper.map(to, from, name());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
现在您可以毫无问题地使用它:
public Thing mapResultSetRow(ResultSet rs, ThingColumn... fields) {
Thing t = new Thing();
Stream.of(fields).forEach(f -> f.map(t, rs));
return t;
}
第二种方法的问题是您有不同的数据类型(Long
,String
等)。要解决此问题,您需要一个功能界面来匹配ResultSet::getLong
等方法参考:
@FunctionalInterface
static interface ResultGetter<T> {
T get(ResultSet from, String column) throws SQLException;
}
参数是ResultSet
本身(this
对象,ResultSet.getLong
- 类似方法是非静态的)和列。结果类型可能不同,因此它是通用的。
对于Thing
设置者,您可以使用标准BiConsumer<Thing, T>
类型。您还需要一个通用的参数化构造函数(是的,它们存在!)。此构造函数将创建另一个BiConsumer<Thing, ResultSet>
类型的函数,该函数可以在map
方法中使用。
以下是完整代码(mapResultSetRow
方法与上述相同):
@FunctionalInterface
static interface ResultGetter<T> {
T get(ResultSet from, String column) throws SQLException;
}
enum ThingColumn {
id(ResultSet::getLong, Thing::setId),
language(ResultSet::getString, Thing::setLanguage);
private final BiConsumer<Thing, ResultSet> mapper;
<T> ThingColumn(ResultGetter<T> getter, BiConsumer<Thing, T> setter) {
this.mapper = (t, rs) -> {
try {
setter.accept(t, getter.get(rs, name()));
} catch (SQLException e) {
throw new RuntimeException(e);
}
};
}
public void map(Thing to, ResultSet from) {
this.mapper.accept(to, from);
}
}