`
leonzhx
  • 浏览: 771123 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Morphia always stores classname for embedded collection

阅读更多

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

3
3
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics