Today, I found a problem that Morphia alwasy stores classname for embedded collection when updating it. Here is my POJO example :
@Entity(noClassnameStored = true) @Indexes({@Index(name="idx_meta" , value="meta.n, meta.v"), @Index(name="idx_meta_creator", value="meta.c")}) public class ResourceDO { @Id private String systemId; ... @Embedded private Set<ResourceMetaDO> meta; ... public Set<ResourceMetaDO> getMetaEntries(){ return meta; } } @Embedded(concreteClass=ResourceMetaDO.class) public class ResourceMetaDO { //name private String n; //value private String v; //creator private String c; ... }
I mapped both ResourceDO.class and ResourceMetaDO.class at the startup of my application using Morphia.map().
When I used AdvancedDatastore.save() or AdvancedDatastore.insert() to save or update resource, all went well.
However when I tried to use the following codes to update the meta :
updateMeta( ResourceDO r ) { Query<ResourceDO> query = ds.createQuery(ResourceDO.class).filter("_id", r.getSystemId() ); UpdateOperations<ResourceDO> update =ds.createUpdateOperations(ResourceDO.class).set("meta", r.getMetaEntries()); dao.getDatastore().update(query, update); }
The meta was stored with classname. When I looked into UpdateOpsImpl.java :
protected void add(UpdateOperator op, String f, Object value, boolean convert) { if (value == null) throw new QueryException("Val cannot be null"); Object val = null; MappedField mf = null; if (validateNames || validateTypes) { StringBuffer sb = new StringBuffer(f); mf = Mapper.validate(clazz, mapr, sb, FilterOperator.EQUAL, val, validateNames, validateTypes); f = sb.toString(); } if (convert) if (UpdateOperator.PULL_ALL.equals(op) && value instanceof List) val = toDBObjList(mf, (List<?>) value); else val = mapr.toMongoObject(mf, null, value); ... }
and Mapper.java
public Object toMongoObject(MappedField mf, MappedClass mc, Object value) { Object mappedValue = value; //convert the value to Key (DBRef) if the field is @Reference or type is Key/DBRef, or if the destination class is an @Entity if ((mf!=null && ( mf.hasAnnotation(Reference.class) || mf.getType().isAssignableFrom(Key.class) || mf.getType().isAssignableFrom(DBRef.class)) ) || (mc != null && mc.getEntityAnnotation() != null)) { try { Key<?> k = (value instanceof Key) ? (Key<?>)value : getKey(value); mappedValue = keyToRef(k); } catch (Exception e) { log.debug("Error converting value(" + value + ") to reference.", e); mappedValue = toMongoObject(value, false); } }//serialized else if (mf!=null && mf.hasAnnotation(Serialized.class)) try { mappedValue = Serializer.serialize(value, !mf.getAnnotation(Serialized.class).disableCompression()); } catch (IOException e) { throw new RuntimeException(e); } //pass-through else if (value instanceof DBObject) mappedValue = value; else { mappedValue = toMongoObject(value, EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf)); if (mappedValue instanceof DBObject && !EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf)) ((DBObject) mappedValue).removeField(CLASS_NAME_FIELDNAME); } return mappedValue; }
and EmbeddedMapper.java
public static boolean shouldSaveClassName(Object rawVal, Object convertedVal, MappedField mf) { if (rawVal == null || mf == null) return true; if (mf.isSingleValue()) return !(mf.getType().equals(rawVal.getClass()) && !(convertedVal instanceof BasicDBList)); else if ( convertedVal != null && convertedVal instanceof DBObject && !mf.getSubClass().isInterface() && !Modifier.isAbstract(mf.getSubClass().getModifiers()) && mf.getSubClass().equals(rawVal.getClass())) return false; else return true; }
It will always store classname for embedded collection even you have definite concreteClass for the embedded field.
So , I swtiched to the following tagging :
@Entity(noClassnameStored = true) public class ResourceMetaDO implements IMetaEntry { @Id private ObjectId id; //name private String n; //value private String v; //creator private String c; ... }
It works now. And note the id filed will not be set automatically by morphia if you only use the ResourceMetaDO class as the embedded field of ResourceDO