Jdbi 和继承:条件映射?
Jdbi and Inheritance: Conditional Mapping?
我有一个名为 Tags
的 table,它将“标记”存储为一行,而不管它们代表什么特定子类。有些行代表 modbus 标签,有些是 snmp,有些是其他协议。所有从 Tag 继承的 类 都将它们的数据存储在这个 table 中,未使用的列仅包含空值。
目前,我有像 getAllModBusTags()
这样的 DAO 方法,其中包含一条指令 mapToBean(ModBusTag.class)
。最终,所有 Tag 的子类 都从数据库中获取(每个协议一次获取),然后添加到超类型 Tag 的 ArrayList 中。
我的问题是,Jdbi 是否有一种简单的方法来执行行的条件映射,这样如果一行包含特定值,它就会映射到 ModBusTag.class 但如果一行包含不同的值,它映射到 SNMPTag.class,依此类推?
我的最终目标是用一个 select 语句从数据库中获取每个标签,逐行自动映射到正确的 bean,然后将所有这些子类 bean 存储在一个列表中超类型标签。
单一类型的示例方法:
@Override
public List<SNMPTag> getSNMPTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1 AND Protocol = 'SNMP'")
.mapToBean(SNMPTag.class)
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
一些糟糕的伪代码表明我想做什么:
@Override
public List<Tag> getAllTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1")
.mapRows(row -> row.Protocol.equals("SNMP").mapToBean(SNMPTag.class)
.mapRows(row -> row.Protocol.equals("ModBus").mapToBean(ModBusTag.class)
//etc
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
您可以使用 RowMapper
和一些自定义代码来实现您的需要,我们在我们的项目中成功地使用了这种方法。这是此技术的简化一般示例:
public class PolymorphicRowMapper implements RowMapper<Parent> {
@Override
public Parent map(ResultSet rs, StatementContext ctx) throws SQLException {
Type type = Type.valueOf(rs.getString("type"));
if (type == Type.A) {
return mapTo(rs, ctx, ChildA.class);
} else if (type == Type.B) {
return mapTo(rs, ctx, ChildB.class);
}
throw new IllegalStateException("Could not resolve mapping strategy for object");
}
private static <T extends Parent> T mapTo(
ResultSet rs,
StatementContext ctx,
Class<T> targetClass
) throws SQLException {
return ctx.getConfig().get(Mappers.class)
.findFor(targetClass)
.orElseThrow(() ->
new NoSuchMapperException(String.format("No mapper registered for %s class", targetClass))
)
.map(rs, ctx);
}
}
public static void main(String[] args) {
var jdbi = Jdbi.create("...")
.registerRowMapper(BeanMapper.factory(ChildA.class))
.registerRowMapper(BeanMapper.factory(ChildB.class));
try (Handle handle = jdbi.open()) {
handle.createQuery("SELECT * FROM table")
.map(new PolymorphicRowMapper());
}
}
public enum Type {
A, B
}
public abstract class Parent {
final Type type;
protected Parent(final Type type) {
this.type = type;
}
}
public class ChildA extends Parent {
public ChildA() {
super(Type.A);
}
}
public class ChildB extends Parent {
public ChildB() {
super(Type.B);
}
}
我有一个名为 Tags
的 table,它将“标记”存储为一行,而不管它们代表什么特定子类。有些行代表 modbus 标签,有些是 snmp,有些是其他协议。所有从 Tag 继承的 类 都将它们的数据存储在这个 table 中,未使用的列仅包含空值。
目前,我有像 getAllModBusTags()
这样的 DAO 方法,其中包含一条指令 mapToBean(ModBusTag.class)
。最终,所有 Tag 的子类 都从数据库中获取(每个协议一次获取),然后添加到超类型 Tag 的 ArrayList 中。
我的问题是,Jdbi 是否有一种简单的方法来执行行的条件映射,这样如果一行包含特定值,它就会映射到 ModBusTag.class 但如果一行包含不同的值,它映射到 SNMPTag.class,依此类推?
我的最终目标是用一个 select 语句从数据库中获取每个标签,逐行自动映射到正确的 bean,然后将所有这些子类 bean 存储在一个列表中超类型标签。
单一类型的示例方法:
@Override
public List<SNMPTag> getSNMPTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1 AND Protocol = 'SNMP'")
.mapToBean(SNMPTag.class)
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
一些糟糕的伪代码表明我想做什么:
@Override
public List<Tag> getAllTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1")
.mapRows(row -> row.Protocol.equals("SNMP").mapToBean(SNMPTag.class)
.mapRows(row -> row.Protocol.equals("ModBus").mapToBean(ModBusTag.class)
//etc
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
您可以使用 RowMapper
和一些自定义代码来实现您的需要,我们在我们的项目中成功地使用了这种方法。这是此技术的简化一般示例:
public class PolymorphicRowMapper implements RowMapper<Parent> {
@Override
public Parent map(ResultSet rs, StatementContext ctx) throws SQLException {
Type type = Type.valueOf(rs.getString("type"));
if (type == Type.A) {
return mapTo(rs, ctx, ChildA.class);
} else if (type == Type.B) {
return mapTo(rs, ctx, ChildB.class);
}
throw new IllegalStateException("Could not resolve mapping strategy for object");
}
private static <T extends Parent> T mapTo(
ResultSet rs,
StatementContext ctx,
Class<T> targetClass
) throws SQLException {
return ctx.getConfig().get(Mappers.class)
.findFor(targetClass)
.orElseThrow(() ->
new NoSuchMapperException(String.format("No mapper registered for %s class", targetClass))
)
.map(rs, ctx);
}
}
public static void main(String[] args) {
var jdbi = Jdbi.create("...")
.registerRowMapper(BeanMapper.factory(ChildA.class))
.registerRowMapper(BeanMapper.factory(ChildB.class));
try (Handle handle = jdbi.open()) {
handle.createQuery("SELECT * FROM table")
.map(new PolymorphicRowMapper());
}
}
public enum Type {
A, B
}
public abstract class Parent {
final Type type;
protected Parent(final Type type) {
this.type = type;
}
}
public class ChildA extends Parent {
public ChildA() {
super(Type.A);
}
}
public class ChildB extends Parent {
public ChildB() {
super(Type.B);
}
}