将 JDBC ResultSet 映射到对象
Posted
技术标签:
【中文标题】将 JDBC ResultSet 映射到对象【英文标题】:Mapping a JDBC ResultSet to an object 【发布时间】:2014-03-24 05:50:22 【问题描述】:我有一个用户类,它有 16 个属性,例如名字、姓氏、dob、用户名、密码等……这些都存储在 mysql 数据库中,当我想检索用户时,我使用 ResultSet。我想将每一列映射回用户属性,但我这样做的方式似乎非常低效。 比如我在做:
//ResultSet rs;
while(rs.next())
String uid = rs.getString("UserId");
String fname = rs.getString("FirstName");
...
...
...
User u = new User(uid,fname,...);
//ArrayList<User> users
users.add(u);
即我检索所有列,然后通过将所有列值插入用户构造函数来创建用户对象。
有没有人知道一种更快、更整洁的方法?
【问题讨论】:
你的意思。有效吗?是不是太费时间了 查看 Spring JDBC 模板及其 bean 映射器 有很多工具可以让这种任务变得更容易。我认为最好的是 sql2o、JDBI 和 jOOQ Link 【参考方案1】:有推荐使用https://commons.apache.org/proper/commons-dbutils/的答案。行处理器的默认实现,即 db-utils 1.7
中的 org.apache.commons.dbutils.BasicRowProcessor
不是线程安全的。因此,如果您在多线程环境中使用org.apache.commons.dbutils.QueryRunner::query
方法,您应该编写自定义行处理器。它可以通过实现org.apache.commons.dbutils.RowProcessor
接口或扩展org.apache.commons.dbutils.BasicRowProcessor
类来完成。下面通过扩展BasicRowProcessor
给出示例代码:
class PersonResultSetHandler extends BasicRowProcessor
@Override
public <T> List<T> toBeanList(ResultSet rs, Class<? extends T> type)
throws SQLException
//Handle the ResultSet and return a List of Person
List<Person> personList = .....
return (List<T>) personList;
将自定义行处理器传递给适当的org.apache.commons.dbutils.ResultSetHandler
实现。以下代码中使用了BeanListHandler
:
QueryRunner qr = new QueryRunner();
List<Person> personList = qr.query(conn, sqlQuery, new BeanListHandler<Person>(Person.class, new PersonResultSetHandler()));
但是,https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc 是另一种具有更简洁 API 的替代方案。虽然,我不确定它的线程安全方面。
【讨论】:
【参考方案2】:如果您不想使用任何 JPA 提供程序,例如 OpenJPA 或 Hibernate,您可以试试 Apache DbUtils。
http://commons.apache.org/proper/commons-dbutils/examples.html
那么您的代码将如下所示:
QueryRunner run = new QueryRunner(dataSource);
// Use the BeanListHandler implementation to convert all
// ResultSet rows into a List of Person JavaBeans.
ResultSetHandler<List<Person>> h = new BeanListHandler<Person>(Person.class);
// Execute the SQL statement and return the results in a List of
// Person objects generated by the BeanListHandler.
List<Person> persons = run.query("SELECT * FROM Person", h);
【讨论】:
对于没有任何框架的纯 jdbc 逻辑来说似乎是完美的解决方案。凉爽的 ! +1 DbUtils 在很多方面看起来都很好,但它有两个缺点:它依赖于java.desktop
模块,并且它对将 bean 状态存储到 @ 语句中的任务没有帮助987654324@ 声明。【参考方案3】:
我想暗示一下 q2o。它是一个基于 JPA 的 Java 对象映射器,可帮助完成许多繁琐的 SQL 和 JDBC ResultSet 相关任务,但没有 ORM 框架所带来的所有复杂性。在它的帮助下,将 ResultSet 映射到对象就像这样简单:
while(rs.next())
users.add(Q2Obj.fromResultSet(rs, User.class));
More about q2o can be found here.
【讨论】:
【参考方案4】:import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
import com.google.gson.Gson;
public class ObjectMapper
//generic method to convert JDBC resultSet into respective DTo class
@SuppressWarnings("unchecked")
public static Object mapValue(List<Map<String, Object>> rows,Class<?> className) throws Exception
List<Object> response=new ArrayList<>();
Gson gson=new Gson();
for(Map<String, Object> row:rows)
org.json.simple.JSONObject jsonObject = new JSONObject();
jsonObject.putAll(row);
String json=jsonObject.toJSONString();
Object actualObject=gson.fromJson(json, className);
response.add(actualObject);
return response;
public static void main(String args[]) throws Exception
List<Map<String, Object>> rows=new ArrayList<Map<String, Object>>();
//Hardcoded data for testing
Map<String, Object> row1=new HashMap<String, Object>();
row1.put("name", "Raja");
row1.put("age", 22);
row1.put("location", "India");
Map<String, Object> row2=new HashMap<String, Object>();
row2.put("name", "Rani");
row2.put("age", 20);
row2.put("location", "India");
rows.add(row1);
rows.add(row2);
@SuppressWarnings("unchecked")
List<Dto> res=(List<Dto>) mapValue(rows, Dto.class);
public class Dto
private String name;
private Integer age;
private String location;
//getters and setters
试试上面的代码。这可以用作将 JDBC 结果映射到相应 DTO 类的通用方法。
【讨论】:
【参考方案5】:使用 DbUtils...
我对那个库的唯一问题是有时你的 bean 类中有关系,DBUtils 不会映射它。它只映射 bean 类中的属性,如果您有其他复杂的属性(由于 DB 关系而引用其他 bean),您必须创建我所说的“间接设置器”,它们是将值放入那些复杂的设置器属性的属性。
【讨论】:
【参考方案6】:使用@TEH-EMPRAH 想法和来自Cast Object to Generic Type for returning 的通用转换的完整解决方案
import annotations.Column;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.*;
public class ObjectMapper<T>
private Class clazz;
private Map<String, Field> fields = new HashMap<>();
Map<String, String> errors = new HashMap<>();
public DataMapper(Class clazz)
this.clazz = clazz;
List<Field> fieldList = Arrays.asList(clazz.getDeclaredFields());
for (Field field : fieldList)
Column col = field.getAnnotation(Column.class);
if (col != null)
field.setAccessible(true);
fields.put(col.name(), field);
public T map(Map<String, Object> row) throws SQLException
try
T dto = (T) clazz.getConstructor().newInstance();
for (Map.Entry<String, Object> entity : row.entrySet())
if (entity.getValue() == null)
continue; // Don't set DBNULL
String column = entity.getKey();
Field field = fields.get(column);
if (field != null)
field.set(dto, convertInstanceOfObject(entity.getValue()));
return dto;
catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e)
e.printStackTrace();
throw new SQLException("Problem with data Mapping. See logs.");
public List<T> map(List<Map<String, Object>> rows) throws SQLException
List<T> list = new LinkedList<>();
for (Map<String, Object> row : rows)
list.add(map(row));
return list;
private T convertInstanceOfObject(Object o)
try
return (T) o;
catch (ClassCastException e)
return null;
然后就它与数据库的关系而言,我有以下几点:
// connect to database (autocloses)
try (DataConnection conn = ds1.getConnection())
// fetch rows
List<Map<String, Object>> rows = conn.nativeSelect("SELECT * FROM products");
// map rows to class
ObjectMapper<Product> objectMapper = new ObjectMapper<>(Product.class);
List<Product> products = objectMapper.map(rows);
// display the rows
System.out.println(rows);
// display it as products
for (Product prod : products)
System.out.println(prod);
catch (Exception e)
e.printStackTrace();
【讨论】:
代码convertInstanceOfObject
永远不会抛出ClassCastException
,所以没有必要去捕捉它。演员表是未经检查的演员表。相反,如果类型不匹配,则在调用代码将返回值分配给变量时将抛出ClassCastException
。【参考方案7】:
假设您想使用核心 Java,而没有任何战略框架。如果可以保证,实体的字段名称将等于数据库中的列,则可以使用反射 API(否则创建注释并在此处定义映射名称)
按字段名
/**
Class<T> clazz - a list of object types you want to be fetched
ResultSet resultSet - pointer to your retrieved results
*/
List<Field> fields = Arrays.asList(clazz.getDeclaredFields());
for(Field field: fields)
field.setAccessible(true);
List<T> list = new ArrayList<>();
while(resultSet.next())
T dto = clazz.getConstructor().newInstance();
for(Field field: fields)
String name = field.getName();
try
String value = resultSet.getString(name);
field.set(dto, field.getType().getConstructor(String.class).newInstance(value));
catch (Exception e)
e.printStackTrace();
list.add(dto);
通过注释
@Retention(RetentionPolicy.RUNTIME)
public @interface Col
String name();
DTO:
class SomeClass
@Col(name = "column_in_db_name")
private String columnInDbName;
public SomeClass()
// ..
相同,但是
while(resultSet.next())
T dto = clazz.getConstructor().newInstance();
for(Field field: fields)
Col col = field.getAnnotation(Col.class);
if(col!=null)
String name = col.name();
try
String value = resultSet.getString(name);
field.set(dto, field.getType().getConstructor(String.class).newInstance(value));
catch (Exception e)
e.printStackTrace();
list.add(dto);
想法
事实上,遍历所有字段似乎无效,所以我会将映射存储在某个地方,而不是每次都进行迭代。但是,如果我们的T
是一个仅用于传输数据并且不包含大量不必要字段的 DTO,那没关系。最后,它比一直使用样板方法要好得多。
希望这对某人有所帮助。
【讨论】:
该模式与我自己的情况非常相似。只是我在这个模式上的美分。 a)您需要检查您是否可以访问该字段(我通过设置始终可读的字段来覆盖该字段 b)您可以首先开始检查您的类,该类可能 95% 的字段少于您从 db 获得的字段。由于许多原因,一个类不需要一切 c) 快速迭代以检查相等性仅通过 null 和 equals() 可行 d) 我最近放置了一个 CacheManager 构建阅读教程并显着提高了反射的性能 e) 你需要缓存可重复查询结果。【参考方案8】:如果您要检索更多记录,请使用 Statement Fetch Size 。像这样。
Statement statement = connection.createStatement();
statement.setFetchSize(1000);
除此之外,我认为您在性能方面的做法没有问题
就整洁而言。始终使用单独的方法委托将结果集映射到 POJO 对象。以后可以在同一个类中重复使用
喜欢
private User mapResultSet(ResultSet rs)
User user = new User();
// Map Results
return user;
如果 columnName 和 object 的 fieldName 的名称相同,您还可以编写反射实用程序将记录加载回 POJO。并使用 MetaData 读取 columnNames 。但是对于使用反射的小规模项目来说不是问题。但正如我之前所说,你的做法没有任何问题。
【讨论】:
【参考方案9】:无需将 resultSet 值存储到 String 并再次设置到 POJO 类中。而是在您检索时设置。
或者最好的方法是切换到像 hibernate 这样的 ORM 工具,而不是 JDBC,它将你的 POJO 对象直接映射到数据库。
但是现在使用这个:
List<User> users=new ArrayList<User>();
while(rs.next())
User user = new User();
user.setUserId(rs.getString("UserId"));
user.setFName(rs.getString("FirstName"));
...
...
...
users.add(user);
【讨论】:
是的,这样会更简洁更好,因为我不必不必要地创建所有字符串、整数等(也不使用构造函数)。谢谢。 如果结果集包含 300 个字段怎么办? 如果将来添加一些列怎么办?然后,这里也需要进行更改。以上是关于将 JDBC ResultSet 映射到对象的主要内容,如果未能解决你的问题,请参考以下文章
Java中,将ResultSet映射为对象和队列及其他辅助函数
JDBC的ResultSet游标转spark的DataFrame,数据类型的映射以TeraData数据库为例