Отображение набора результатов JDBC для объекта

У меня есть пользовательский класс, который имеет 16 атрибутов, такие как имя, фамилия, док, имя пользователя, пароль и т.д. Все они хранятся в базе данных 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);
} 

i.e Я извлекаю все столбцы, а затем создаю пользовательские объекты, вставляя все значения столбцов в конструктор User.

Кто-нибудь знает о более быстром, более аккуратном способе сделать это?

Ответ 1

Нет необходимости хранить результат. Установить значения в 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);
} 

Ответ 2

Если вы не хотите использовать какого-либо поставщика JPA, такого как openJPA или спящий режим, вы можете просто попробовать 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);

Ответ 3

Предположим, вы хотите использовать базовую Java, без каких-либо стратегических рамок. Если вы можете гарантировать, что имя этого поля будет равно столбцу в базе данных, вы можете использовать Reflection API (в противном случае создать аннотацию и определить там имя сопоставления)

По FieldName

/**

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 с целью передачи данных и не будет содержать загрузок ненужных полей, это нормально. В конце концов, это намного лучше, чем использование стандартных методов.

Надеюсь, это поможет кому-то.

Ответ 4

Полное решение, использующее идеи @TEH-EMPRAH и общее преобразование из Cast Object в Generic Type для возврата

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

Ответ 5

Возможно ли это? попробуйте получить resultMap, определенный в Mybatis xml, а затем преобразуйте родной ResultSet JDBC в список через некоторую функцию MyBatis

Ответ 6

Я знаю, что спрашивающий работает с MySQL, но, поскольку на эту страницу попадет много людей без ограничений для MySQL, но с одним и тем же вопросом, я хотел бы намекнуть на q2o. Это средство отображения Java-объектов на основе JPA, которое помогает справиться со многими утомительными задачами, связанными с SQL и JDBC ResultSet, но, несмотря на всю сложность инфраструктуры ORM, поставляется вместе с ней. С его помощью отображение ResultSet на объект так же просто, как это:

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

Больше о q2o можно найти здесь.

Ответ 7

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.

Ответ 8

Используйте размер выборки Statement, если вы получаете большее количество записей. как это.

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

Кроме того, я не вижу проблемы с тем, как вы это делаете с точки зрения производительности

С точки зрения Нита. Всегда используйте отдельный делегат метода для сопоставления результатов с объектом POJO. которые могут быть повторно использованы позже в том же классе

like

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

Если у вас одинаковое имя для имени columnName и objectName, вы также можете написать утилиту отражения для загрузки записей обратно в POJO. и используйте MetaData для чтения имен столбцов. но для небольших проектов, использующих отражение, не проблема. но, как я уже сказал, нет ничего плохого в том, как вы это делаете.

Ответ 9

используя DbUtils...

Единственная проблема, с которой я столкнулся в этой библиотеке, заключалась в том, что иногда у вас есть отношения в классах bean-компонентов, DBUtils не отображает это. Он отображает только свойства в классе bean-компонента, если у вас есть другие сложные свойства (ссылающиеся на другие bean-объекты из-за отношений с БД), вам придется создавать "косвенные установщики", как я их называю, которые являются установщиками, которые помещают значения в эти сложные свойства свойства.