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