如何使用 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 属性的名称与数据库列名称不完全匹配,因此某些属性未填充。
现在,我通过谷歌搜索对此进行了一些研究,发现可以通过以下方式实现:
- 在编写 SQL 查询时使用列别名(我不喜欢这样做,因为我在一些较大的表中有多个连接,因此需要大量别名)
- 使用 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
。
GenerousBeanProcessor
自 commons-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 处理器或提供列映射。
将您的 POJO 更改为
public class State implements Serializable {
@JsonProperty("state_id")//db column name
private int stateId;
@JsonProperty("state_cd")
private String stateCode;
//and so on ....
}
将 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 字段。
我想使用 Apache DBUtils
库从数据库中填充一个 POJO (State.java)。但是,由于 Bean 属性的名称与数据库列名称不完全匹配,因此某些属性未填充。
现在,我通过谷歌搜索对此进行了一些研究,发现可以通过以下方式实现:
- 在编写 SQL 查询时使用列别名(我不喜欢这样做,因为我在一些较大的表中有多个连接,因此需要大量别名)
- 使用 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
。
GenerousBeanProcessor
自 commons-dbutils.
用法:
// 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 处理器或提供列映射。
将您的 POJO 更改为
public class State implements Serializable { @JsonProperty("state_id")//db column name private int stateId; @JsonProperty("state_cd") private String stateCode; //and so on ....
}
将 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 字段。