web-dev-qa-db-fra.com

Mappage d'un jeu de résultats JDBC à un objet

J'ai une classe d'utilisateurs qui a 16 attributs, des choses telles que prénom, nom de famille, dob, nom d'utilisateur, mot de passe, etc. Ils sont tous stockés dans une base de données MySQL et quand je veux récupérer des utilisateurs, j'utilise un ResultSet. Je veux mapper chacune des colonnes sur les attributs utilisateur, mais la façon dont je le fais semble terriblement inefficace. Par exemple, je fais:

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

c'est-à-dire que je récupère toutes les colonnes, puis crée des objets utilisateur en insérant toutes les valeurs de colonne dans le constructeur User.

Quelqu'un connaît-il une façon plus rapide et plus nette de procéder?

25
Quanqai

Pas besoin de stocker les valeurs resultSet dans String et de les redéfinir dans la classe POJO. Au lieu de cela, définissez-le au moment de la récupération.

Ou le meilleur moyen de passer aux outils ORM comme hibernate au lieu de JDBC qui mappe votre objet POJO directement à la base de données.

Mais à partir de maintenant, utilisez ceci:

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);
} 
14
Shoaib Chikate

Si vous ne souhaitez pas utiliser de fournisseur JPA comme openJPA ou hibernate, vous pouvez simplement essayer Apache dbutils.

http://commons.Apache.org/proper/commons-dbutils/examples.html

alors votre code ressemblera à ceci

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);
46
Leo

Supposons que vous souhaitiez utiliser le noyau Java, sans aucun cadre stratégique. Si vous pouvez garantir que le nom de champ d'une entité sera égal à la colonne de la base de données, vous pouvez utiliser l'API Reflection (sinon créez une annotation et définissez le nom du mappage à cet endroit)

Par 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);

    }

Par annotation

@Retention(RetentionPolicy.RUNTIME)
public @interface Col {

    String name();
}

DTO:

class SomeClass {

   @Col(name = "column_in_db_name")
   private String columnInDbName;

   public SomeClass() {}

   // ..

}

Idem, mais

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

    }

Réflexions

En fait, itérer sur tous les champs peut sembler inefficace, donc je stockerais le mappage quelque part, plutôt que d'itérer à chaque fois. Cependant, si notre T est un DTO dans le seul but de transférer des données et ne contient pas de charges de champs inutiles, c'est ok. En fin de compte, c'est beaucoup mieux que d'utiliser des méthodes passe-partout.

J'espère que cela aide quelqu'un.

7
TEH EMPRAH

Solution complète utilisant les idées @ TEH-EMPRAH et le casting générique de Cast objet au type générique pour le retour

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

puis en termes de la façon dont il se connecte avec la base de données, j'ai les éléments suivants:

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

    }

Essayez le code ci-dessus. Cela peut être utilisé comme méthode générique pour mapper le résultat JDBC à la classe DTO respective.

0

Je sais que le questionneur travaille avec MySQL, mais parce que beaucoup de gens sans restriction à MySQL mais avec la même question atterriront sur cette page, je voudrais faire allusion au q2o. Il s'agit d'un mappeur d'objets basé sur JPA Java qui aide à la plupart des tâches fastidieuses liées à SQL et JDBC ResultSet, mais sans toute la complexité d'un cadre ORM. Avec son aide, il mappe un ResultSet à un l'objet est aussi simple que cela:

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

Plus d'informations sur q2o peuvent être trouvées ici.

0
Holger Thurow