将 JDBC 结果集映射到对象



我有一个用户类,它有 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);
} 

即我检索所有列,然后通过将所有列值插入 User 构造函数来创建用户对象。

有谁知道一种更快、更整洁的方法?

如果你不想使用任何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);

无需将 resultSet 值存储到 String 中,然后再次设置为 POJO 类。而是在您检索时设置。

或者最好的方法是切换到ORM工具,如hibernate,而不是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);
} 

假设你想使用核心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,仅用于传输数据并且不包含大量不必要的字段,那没关系。最后,它比一直使用样板方法要好得多。

希望这对某人有所帮助。

使用 @TEH-EMPRAH 思想和从强制转换对象到泛型类型的泛型转换以返回的完整解决方案

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();
}
我想

在q2o上暗示一下。它是一个基于JPA的Java对象映射器,有助于完成许多繁琐的SQL和JDBC ResultSet相关任务,但没有ORM框架附带的所有复杂性。在它的帮助下,将结果集映射到对象就像这样简单:

while(rs.next()) {
    users.add(Q2Obj.fromResultSet(rs, User.class));
}

有关 q2o 的更多信息,请参见此处。

谢谢你@@TEH-EMPRAH。他的解决方案"按字段名称"将是最终的:

/**
     * Method help to convert SQL request data to your custom DTO Java class object.   
     * Requirements: fields of your Java class should have Type: String and have the same name as in sql table
     *
     * @param resultSet     - sql-request result
     * @param clazz - Your DTO Class for mapping
     * @return <T> List <T> - List of converted DTO java class objects
     */
    public static <T> List <T> convertSQLResultSetToObject(ResultSet resultSet, Class<T> clazz) throws SQLException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        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);
        }
        return list;
    }

有答案建议使用 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 的替代方案。虽然,我不确定它的线程安全方面。

将结果集获取为映射后,您可以使用 Jackson 对象映射器轻松将映射到 Cnvert 到对象。

using DbUtils...

我对那个库的唯一问题是,有时你的 bean 类中有关系,DBUtils 不会映射它。它只映射 bean 类中的属性,如果您有其他复杂属性(由于数据库关系引用其他 bean),则必须创建我所说的"间接资源库",它们是将值放入这些复杂属性的属性中的资源库。

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 类的通用方法。

如果要检索更多数量的记录,请使用语句提取大小。 像这样。

Statement statement = connection.createStatement();
statement.setFetchSize(1000); 

除此之外,我认为您在性能方面的工作方式没有问题

在整洁方面。始终使用单独的方法委托将结果集映射到 POJO 对象。以后可以在同一类中重用

喜欢

private User mapResultSet(ResultSet rs){
     User user = new User();
     // Map Results
     return user;
}

如果 columnName 和对象的字段名称相同,您还可以编写反射实用程序以将记录加载回 POJO。 并使用元数据读取列名称。 但对于使用反射的小规模项目来说,使用反射不是问题。 但正如我之前所说,你的做法没有错。

相关内容

  • 没有找到相关文章

最新更新