简体   繁体   English

使用反射将Mongo DBObject转换为实体POJO

[英]Converting Mongo DBObject to Entity POJO using Reflection

First I want to say that yes - I know there are ORMs like Morphia and Spring Data for MongoDB. 首先,我想说的是-我知道有针对MongoDB的ORM,例如Morphia和Spring Data。 I'm not trying to reinvent the weel - just to learn. 我不是在尝试重新发明-只是为了学习。 So basic idea behind my AbstractRepository is to encapsulate logic that's shared between all repositories. 因此,AbstractRepository的基本思想是封装所有存储库之间共享的逻辑。 Subclasses (repositories for specific entities) passes Entity class to . 子类(特定实体的存储库)将Entity类传递给。

Converting entity beans (POJOs) to DBObject using Reflection was pretty streightforward. 使用Reflection将实体Bean(POJO)转换为DBObject非常简单。 Problem comes with converting DBObject to entity bean. 问题是将DBObject转换为实体bean。 Reason? 原因? I need to convert whatever field type in DBObject to entity bean property type. 我需要将DBObject中的任何字段类型转换为实体bean属性类型。 And this is where I'm stuck. 这就是我遇到的问题。 I'm unable to get entity bean class in AbstractRepository method T getEntityFromDBObject(DBObject object) 我无法在AbstractRepository方法T getEntityFromDBObject(DBObject object)获得实体bean类T getEntityFromDBObject(DBObject object)

I could pass entity class to this method but that would defeat the purpose of polymorphism. 我可以将实体类传递给此方法,但这会破坏多态性的目的。 Another way would be to declare private T type property and then read type using Field. 另一种方法是声明private T type属性,然后使用Field读取类型。 Defining additional property just so I can read doesn't sound right. 仅仅定义附加属性以便我阅读听起来不对。

So the question is - how would you map DBObject to POJO using reflection using less parameteres possible. 因此,问题是-如何使用尽可能少的参数通过反射将DBObject映射到POJO。 Once again this is the idea: 再次是这个想法:

public  abstract class AbstractRepository<T> {
   T getEntityFromDBObject(DBObject object) {
      ....
   }
}

And specific repository would look like this: 特定的存储库如下所示:

public class EntityRepository extends AbstractRepository<T> {
}

Thanks! 谢谢!

Note: Ignore complex relations and references. 注意:忽略复杂的关系和引用。 Let's say it doesn't need to support references to another DBObjects or POJOs. 假设不需要支持对另一个DBObject或POJO的引用。

You need to build an instance of type T and fill it with the data that comes in ´DBObject´: 您需要构建一个T类型的实例,并用“ DBObject”中的数据填充它:

public abstract class AbstractRepository<T> {

    protected final Class<T> entityClass;

    protected AbstractRepository() {
        // Don't remember if this reflection stuff throws any exception
        // If it does, try-catch and throw RuntimeException 
        // (or assign null to entityClass)
        // Anyways, it's impossible that such exception occurs here
        Type t = this.getClass().getGenericSuperclass();
        this.entityClass = ((Class<T>)((ParameterizedType)t).getActualTypeArguments()[0]);
    }

    T getEntityFromDBObject(DBObject object) {
        // Use reflection to create an entity instance
        // Let's suppose all entities have a public no-args constructor (they should!)
        T entity = (T) this.entityClass.getConstructor().newInstance();

        // Now fill entity with DBObject's data
        // This is the place to fill common fields only, i.e. an ID
        // So maybe T could extend some abstract BaseEntity that provides setters for these common fields
        // Again, all this reflection stuff needs to be done within a try-catch block because of checked exceptions
        // Wrap the original exception in a RuntimeException and throw this one instead
        // (or maybe your own specific runtime exception for this case)

        // Now let specialized repositories fill specific fields
        this.fillSpecificFields(entity, object);

        return entity;
    }

    protected abstract void fillSpecificFields(T entity, DBObject object);

}

If you don't want to implement the method .fillSpecificFields() in every entity's repository, then you'd need to use reflection to set every field (including common ones such as ID, so don't set them manually). 如果您不想在每个实体的存储库中实现.fillSpecificFields()方法,则需要使用反射来设置每个字段(包括ID等常见字段,因此请不要手动设置)。

If this is the case, you already have the entity class as a protected attribute, so it's available to every entity's repository. 在这种情况下,您已经具有实体类作为受保护的属性,因此每个实体的存储库都可以使用它。 You need to iterate over ALL its fields, including the ones declared in superclasses (I believe you have to use method .getFields() instead of .getDeclaredFields() ) and set the values via reflection. 您需要遍历其所有字段,包括在超类中声明的字段(我相信您必须使用方法.getFields()而不是.getDeclaredFields() )并通过反射设置值。

As a side note, I really don't know what data comes in that DBObject instance, and in what format, so please let me know if extracting fields' values from it results to be non trivial. 附带说明一下,我真的不知道该DBObject实例中包含哪些数据以及采用哪种格式,所以请让我知道从中提取字段的值是否很简单。

First I want to apologies for answering to your comments almost two months later. 首先,我想为将近两个月后回答您的评论道歉。 I did managed to figure it out on my own and here is how I've implemented it (and tested) so maybe someone will make a use of it: 我确实设法自己弄清了它,这是我实现(和测试)它的方式,因此也许有人会使用它:

public abstract class AbstractRepository<T> {

@Inject
private MongoConnectionProvider provider;

// Keeps current repository collection name
protected String collectionName;

@PostConstruct
public abstract void initialize();

public String getCollectionName() {
    return this.collectionName;
}

protected void setCollectionName(String collectionName) {
    this.collectionName = collectionName;
}

protected DBCollection getConnection() {
    DB conn = this.provider.getConnection();
    DBCollection collection = conn.getCollection(this.collectionName);
    return collection;
}

private void putFieldToDbObject(T source, DBObject target, Field field) {
    // TODO: Think more elegant solution for this
    try {
        field.setAccessible(true);
        // Should we cast String to ObjectId
        if (field.getName() == "id" && field.get(source) != null
                || field.isAnnotationPresent(DBRef.class)) {
            String key = field.getName().equals("id") ? "_id" : field.getName();
            target.put(key, new ObjectId(field.get(source).toString()));
        } else {
            if(!field.getName().equals("id")) {
                target.put(field.getName(), field.get(source));
            }
        }
    } catch (IllegalArgumentException | IllegalAccessException exception) {
        // TODO Auto-generated catch block
        exception.printStackTrace();
    } finally {
        field.setAccessible(false);
    }

}

@SuppressWarnings("rawtypes")
protected DBObject getDbObject(T entity) {
    DBObject result = new BasicDBObject();
    // Get entity class
    Class entityClass = entity.getClass();
    Field[] fields = entityClass.getDeclaredFields();
    // Update DBobject with entity data
    for (Field field : fields) {
        this.putFieldToDbObject(entity, result, field);
    }
    return result;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public T getEntityFromDBObject(DBObject object) throws MappingException {
        Type superclass = this.getClass().getGenericSuperclass();
        Type entityClass = ((ParameterizedType) superclass).getActualTypeArguments()[0];
        T entity;
    try {
        entity = ((Class<T>) entityClass).newInstance();
        // Map fields to entity properties
        Set<String> keySet = object.keySet();
        for(String key : keySet) {
            String fieldName = key.equals("_id") ? "id" : key;
            Field field = ((Class<T>) entityClass).getDeclaredField(fieldName);
            field.setAccessible(true);
            if(object.get(key).getClass().getSimpleName().equals("ObjectId")) {
                field.set(entity, object.get(key).toString());
            } else {
                // Get field type
                Type fieldType = field.getType();
                Object fieldValue = object.get(key);
                Class objectType = object.get(key).getClass();
                if(!fieldType.equals(objectType)) {
                    // Let's try to convert source type to destination type
                    try {
                        fieldValue = (((Class) fieldType).getConstructor(objectType)).newInstance(object.get(key));
                    } catch (NoSuchMethodException exception) {
                        // Let's try to use String as "man-in-the-middle"
                        String objectValue = object.get(key).toString();
                        // Get constructor for destination type that take String as parameter
                        Constructor constructor = ((Class) fieldType).getConstructor(String.class);
                        fieldValue = constructor.newInstance(objectValue);
                    }
                }
                field.set(entity, fieldValue);
            }

            field.setAccessible(false);
        }
    } catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) {
        throw new MappingException(e.getMessage(), MappingExceptionCode.UNKNOWN_ERROR);
    }

    return entity;
}

public List<T> getAll() {
    DBCollection conn = this.getConnection();
    DBCursor cursor = conn.find();
    List<T> result = new LinkedList<T>();
    while (cursor.hasNext()) {
        DBObject obj = cursor.next();
        try {
            result.add(this.getEntityFromDBObject(obj));
        } catch (MappingException e) {

        }
    }
    return result;
}

public T getOneById(String id) {
    DBObject idRef = new BasicDBObject().append("_id", new ObjectId(id));
    DBCollection conn = this.getConnection();
    DBObject resultObj = conn.findOne(idRef);
    T result = null;
    try {
        result = this.getEntityFromDBObject(resultObj);
    } catch (MappingException e) {

    }

    return result;

}

public void save(T entity) {
    DBObject object = this.getDbObject(entity);
    DBCollection collection = this.getConnection();
    collection.save(object);
}

}

You've stumbled onto the problem of object mapping. 您偶然发现了对象映射问题。 There are a few libraries out there that look to help with this. 有一些库可以帮助您解决此问题。 You might check out ModelMapper (author here). 您可以签出ModelMapper (作者在这里)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM