如何使用 apache-commons-dbutils 将数据库列名称映射到 Java class 字段

How to map database column names to Java class fields using apache-commons-dbutils

我想使用 Apache DBUtils 库从数据库中填充一个 POJO (State.java)。但是,由于 Bean 属性的名称与数据库列名称不完全匹配,因此某些属性未填充。

现在,我通过谷歌搜索对此进行了一些研究,发现可以通过以下方式实现:

  1. 在编写 SQL 查询时使用列别名(我不喜欢这样做,因为我在一些较大的表中有多个连接,因此需要大量别名)
  2. 使用 BeanProcessor(在任何地方都找不到很好的例子)

任何人都可以提供一个很好的例子来说明如何使用 BeanProcessor 将列名映射到属性吗?调整我提供的示例会更好。

DB Table

CREATE TABLE public.states (
  state_id INTEGER DEFAULT nextval('states_seq'::regclass) NOT NULL,
  state_cd VARCHAR(2) NOT NULL,
  name VARCHAR(100) NOT NULL,
  tax_pct NUMERIC(10,2) DEFAULT 0.00 NOT NULL,
  active CHAR(1) DEFAULT 'Y'::bpchar NOT NULL,
) 

State.java

  public class State implements Serializable {

    private int stateId;
    private String stateCode;
    private String name;
    private BigDecimal taxPct = new BigDecimal(0);
    private Date expiryDate;
    private String createdBy;
    private Date createdOn;
    private String active;

    //getters and setters here
}

Main.java

    public class Main {

    public static void main(String[] args) {
        String url = "jdbc:postgresql://gsi-547576.gsiccorp.net:5432/istore-db";
        String driver = "org.postgresql.Driver";
        String user = "postgres";
        String pwd = "postgres";
        Connection conn = null;
        List<State> states = null;

        try {
            DbUtils.loadDriver(driver);
            conn = DriverManager.getConnection(url, user, pwd);

            states = (List<State>) new QueryRunner().query(conn, "select * from states a where a.active='Y'", new BeanListHandler(State.class);

            System.out.println("states::  " + states);

        } catch (SQLException ex) {
            ex.printStackTrace();
        } finally {
            DbUtils.closeQuietly(conn);
        }
    }

}

如果您查看 BeanProcessor 的 Java 文档:

protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException

The positions in the returned array represent column numbers. The values stored at each position represent the index in the PropertyDescriptor[] for the bean property that matches the column name. If no bean property was found for a column, the position is set to PROPERTY_NOT_FOUND. Parameters: rsmd - The ResultSetMetaData containing column information. props - The bean property descriptors. Returns: An int[] with column index to property index mappings. The 0th element is meaningless because JDBC column indexing starts at 1.

看起来您需要创建一个从 BeanProcessor 扩展并覆盖 mapColumnsToProperties 方法的 class,如下所示:

public class StateBeanProcessor extends BeanProcessor {

    @Override
    protected  int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {
          int[] mapping = super.mapColumnsToProperties(rsmd, props);
          /*Map database columns to fields in the order in which they appear
            1st column in the DB will be mapped to 1st field in the Java
            class and so on.. */
          for(int i=0;i<mapping.length;++i) {
             mapping[i]=i;
          }
      }
  }

然后您可以将上面的 StateBeanProcessor 插入到您的代码中,如下所示:

states = (List<State>) new QueryRunner().query(conn, "select * from states", new BeanListHandler(State.class,new BasicRowProcessor(new StateBeanProcessor())));

免责声明:我没有测试过这段代码。它旨在向您展示可以组合在一起以具有自定义字段映射的点点滴滴。如果您发现它有问题,可以告诉我,以便我进行调查。

我来自中国,我的英语不是很好; 但这个问题,这是我的决心: 因为dbutils是开源的,你可以修改源代码,用maven做一个jar只给你,我只修改BeanProcessor class,你可以添加一个名为changeColumnName的方法,如下所示:

public  String changeColumnName(String columnName){
        if(columnName == null){
            return null;
        }
        if(columnName.contains("_")){
            char[] cs = columnName.toCharArray();
            int flag = -1;
            for(int index=0;index<columnName.toCharArray().length;index++){
                if(cs[index] == '_'){
                    flag = index;
                    break;
                }
            }
            columnName = columnName.substring(0, flag) + columnName.substring(flag+1,flag+2).toUpperCase() + columnName.substring(flag+2);
            return changeColumnName(columnName);
        }else{
            return columnName;
        }
    }

并且在方法中

protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
        PropertyDescriptor[] props) throws SQLException {

    int cols = rsmd.getColumnCount();
    int[] columnToProperty = new int[cols + 1];
    Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

    for (int col = 1; col <= cols; col++) {
        String columnName = rsmd.getColumnLabel(col);
        if (null == columnName || 0 == columnName.length()) {
          columnName = rsmd.getColumnName(col);
        }
        String propertyName = columnToPropertyOverrides.get(columnName);
        if (propertyName == null) {
            propertyName = changeColumnName(columnName);//add here
        }
        for (int i = 0; i < props.length; i++) {

            if (propertyName.equalsIgnoreCase(props[i].getName())) {
                columnToProperty[col] = i;
                break;
            }
        }
    }

    return columnToProperty;
}

可以解决problem.welcome 和我一起讨论。我的google邮箱是guomin.bai@gmail.com

Map<String,String> mapColumnsToProperties = new HashMap<>();
//mapping you database to entity here;
mapColumnsToProperties.put("database_column","entity_property");
BeanProcessor beanProcessor = new BeanProcessor(mapColumnsToProperties);
RowProcessor rowProcessor = new BasicRowProcessor( beanProcessor); 
ResultSetHandler<List<Entity>> resultSetHandler = new BeanListHandler<Entity>(Entity.class,rowProcessor);
List<Entity> entityLists = queryRunner.query(findListSQL, resultSetHandler);    

我已经回答过类似的so question here

您可以使用 GenerousBeanProcessor,它是 BeanProcessor 的子类,它会忽略列名中的下划线和区分大小写。对于这种特殊情况,您不必实施自己的自定义 BeanProcessor

GenerousBeanProcessorcommons-dbutils.

1.6 版起可用

用法:

// TODO initialize
QueryRunner queryRunner = null;

ResultSetHandler<List<State>> resultSetHandler =
                new BeanListHandler<State>(State.class, new BasicRowProcessor(new GenerousBeanProcessor()));

// best practice is mentioning only required columns in the query
final List<State> states = queryRunner.query("select * from states a where a.active='Y'", resultSetHandler);

for (State state : states) {
    System.out.println(state.getStateId());
}

这可以使用 MapListHandler 和 Jackson 轻松完成,无需定义自定义 bean 处理器或提供列映射。

  1. 将您的 POJO 更改为

     public class State implements Serializable {
    
     @JsonProperty("state_id")//db column name
     private int stateId;
    
     @JsonProperty("state_cd")
     private String stateCode;
    
     //and so on ....
    

    }

  2. 将 MapListHandler 与 Jackson 结合使用来获取响应并将其映射到 POJO。

ObjectMapper objectMapper = new ObjectMapper();
List<State> stateList = new QueryRunner().query(connection, query, new MapListHandler())
.stream().map(response -> objectMapper.convertValue(response, State.class))
.collect(Collectors.toList());

在这种方法中,您首先要获取 List>,这是一个记录列表,每条记录都来自列名称和值的映射,可以映射到 POJO 使用Jackson 的 convertValue 方法(将使用@JsonProperty 注释将列映射到 POJO 字段)。

这种方法可以让您更好地控制将数据库列映射到 POJO 字段。