使用反射将 Mongo DBObject 转换为实体 POJO
Converting Mongo DBObject to Entity POJO using Reflection
首先我想说是的 - 我知道有像 Morphia 和 Spring Data for MongoDB 这样的 ORM。我不是要重新发明 weel - 只是为了学习。所以我的 AbstractRepository 背后的基本思想是封装所有存储库之间共享的逻辑。 Subclasses(特定实体的存储库)将 Entity class 传递给 .
使用反射将实体 bean (POJO) 转换为 DBObject 非常简单。将 DBObject 转换为实体 bean 会出现问题。原因?我需要将 DBObject 中的任何字段类型转换为实体 bean 属性 类型。这就是我被困的地方。我无法在 AbstractRepository 方法 T getEntityFromDBObject(DBObject object)
中获取实体 bean class
我可以将实体 class 传递给此方法,但这会破坏多态性的目的。另一种方法是声明 private T type
属性 然后使用 Field 读取类型。定义额外的 属性 只是为了让我可以阅读听起来不对。
所以问题是——您如何使用尽可能少的参数使用反射将 DBObject 映射到 POJO。再次是这个想法:
public abstract class AbstractRepository<T> {
T getEntityFromDBObject(DBObject object) {
....
}
}
具体的存储库如下所示:
public class EntityRepository extends AbstractRepository<T> {
}
谢谢!
注意:忽略复杂的关系和引用。假设它不需要支持对另一个 DBObject 或 POJO 的引用。
您需要构建类型 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);
}
如果你不想在每个实体的存储库中实现方法.fillSpecificFields()
,那么你需要使用反射来设置每个字段(包括ID等常见字段,所以不要设置他们手动)。
如果是这种情况,您已经将实体 class 作为受保护的属性,因此每个实体的存储库都可以使用它。您需要遍历 ALL 它的字段,包括在 superclasses 中声明的字段(我相信您必须使用方法 .getFields()
而不是 .getDeclaredFields()
) 并通过反射设置值。
附带说明一下,我真的不知道那个 DBObject
实例中有什么数据,以什么格式出现,所以如果从中提取字段值的结果很重要,请告诉我.
首先,我很抱歉在将近两个月后才回复您的评论。我确实设法自己弄明白了,下面是我如何实施(和测试)它,所以也许有人会使用它:
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);
}
}
您无意中遇到了对象映射问题。有一些图书馆可以帮助解决这个问题。您可以查看 ModelMapper(此处为作者)。
首先我想说是的 - 我知道有像 Morphia 和 Spring Data for MongoDB 这样的 ORM。我不是要重新发明 weel - 只是为了学习。所以我的 AbstractRepository 背后的基本思想是封装所有存储库之间共享的逻辑。 Subclasses(特定实体的存储库)将 Entity class 传递给 .
使用反射将实体 bean (POJO) 转换为 DBObject 非常简单。将 DBObject 转换为实体 bean 会出现问题。原因?我需要将 DBObject 中的任何字段类型转换为实体 bean 属性 类型。这就是我被困的地方。我无法在 AbstractRepository 方法 T getEntityFromDBObject(DBObject object)
我可以将实体 class 传递给此方法,但这会破坏多态性的目的。另一种方法是声明 private T type
属性 然后使用 Field 读取类型。定义额外的 属性 只是为了让我可以阅读听起来不对。
所以问题是——您如何使用尽可能少的参数使用反射将 DBObject 映射到 POJO。再次是这个想法:
public abstract class AbstractRepository<T> {
T getEntityFromDBObject(DBObject object) {
....
}
}
具体的存储库如下所示:
public class EntityRepository extends AbstractRepository<T> {
}
谢谢!
注意:忽略复杂的关系和引用。假设它不需要支持对另一个 DBObject 或 POJO 的引用。
您需要构建类型 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);
}
如果你不想在每个实体的存储库中实现方法.fillSpecificFields()
,那么你需要使用反射来设置每个字段(包括ID等常见字段,所以不要设置他们手动)。
如果是这种情况,您已经将实体 class 作为受保护的属性,因此每个实体的存储库都可以使用它。您需要遍历 ALL 它的字段,包括在 superclasses 中声明的字段(我相信您必须使用方法 .getFields()
而不是 .getDeclaredFields()
) 并通过反射设置值。
附带说明一下,我真的不知道那个 DBObject
实例中有什么数据,以什么格式出现,所以如果从中提取字段值的结果很重要,请告诉我.
首先,我很抱歉在将近两个月后才回复您的评论。我确实设法自己弄明白了,下面是我如何实施(和测试)它,所以也许有人会使用它:
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);
}
}
您无意中遇到了对象映射问题。有一些图书馆可以帮助解决这个问题。您可以查看 ModelMapper(此处为作者)。