对于地图内的bean,Spring mongo读取转换失败

时间:2013-07-15 04:12:35

标签: mongodb spring-data spring-data-mongodb

我有一个非常奇怪的情况,即我将修改后的bean保存到mongodb后读取转换失败。它导致 java.lang.ClassCastException:java.util.LinkedHashMap无法强制转换为kam.albert.lab.TestConversion $ Person

所以错误的情况是:

  1. 创建一个SimpleObject并使用Person
  2. 填充其地图
  3. 将simpleObject存储到mongo
  4. 找到simpleObject并从地图中获取Person实例,触发 java.lang.ClassCastException:java.util.LinkedHashMap无法转换为kam.albert.lab.TestConversion $ Person
  5. 所以我假设,当从地图内部读取Person时,Person的读取转换似乎失败,尽管在第一次插入时写入转换成功。我可以在mongo中看到数据字段是正确的,如写入转换器中所定义的那样。 另外在另一个实验中,我将Person放在地图中,并且所有读写转换工作正常。仅当我们将此实例放在地图中时,才进行读取转换似乎存在问题。 来源如下:

    package kam.albert.lab;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.data.mongodb.core.MongoOperations;
    import org.springframework.data.mongodb.core.query.Criteria;
    import org.springframework.data.mongodb.core.query.Query;
    import org.springframework.data.mongodb.core.query.Update;
    import org.springframework.stereotype.Component;
    
    import com.mongodb.BasicDBObject;
    import com.mongodb.DBObject;
    
    @Component
    public class TestConversion {
    
        @Autowired private MongoOperations ops;
    
        public static class Person {
            private String firstName, lastName;
            public Person(String first, String last) {
                this.firstName = first;
                this.lastName = last;
            }
        }
        public static class PersonWriteConverter implements Converter<Person, DBObject> {
            @Override
            public DBObject convert(Person person) {
                DBObject dbObject = new BasicDBObject();
                dbObject.put("first", person.firstName);
                dbObject.put("last", person.lastName);
                return dbObject;
            }
    
        }
        public static class PersonReadConverter implements Converter<DBObject, Person> {
            @Override
            public Person convert(DBObject dbo) {
                return new Person((String)dbo.get("first"), (String)dbo.get("last"));
            }
        }
        public static class SimpleObject {
            private String id = UUID.randomUUID().toString();
            private Map<Object, Object> map = new HashMap<>();
            public SimpleObject addPerson(String first, String last) {
                this.map.put(String.valueOf(this.map.size()), new Person(first, last));
                return this;
            }
        }
    
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "test-conversion-context.xml"
            );
            TestConversion bean = ctx.getBean(TestConversion.class);
            bean.cleanup();
            String id = bean.testWrite();
            bean.testRead(id);
            bean.testUpdate(id); // this causes the read below to fail
            bean.testRead(id);
        }
    
        private void cleanup() {
            this.ops.dropCollection("testconv");
        }
    
        private String testWrite() {
            SimpleObject simpleObject = new SimpleObject();
            simpleObject.addPerson("albert", "kam");
            this.ops.insert(simpleObject, "testconv");
            return simpleObject.id;
        }
    
        private void testRead(String id) {
            SimpleObject simpleObject = this.ops.findById(id, SimpleObject.class, "testconv");
            System.out.println("read success : " + simpleObject.map);
        }
    
        private void testUpdate(String id) {
            SimpleObject simpleObject = this.ops.findById(id, SimpleObject.class, "testconv");
            Person person = (Person) simpleObject.map.get("0"); // this causes exception !
            person.firstName = "a new first name";
    //      simpleObject.addPerson("new", "person"); // this is fine
            Update update = new Update().set("map", simpleObject.map);
            this.ops.updateFirst(Query.query(Criteria.where("_id").is(id)), update, "testconv");
        }
    }
    

    test-conversion-context.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:lang="http://www.springframework.org/schema/lang" 
        xmlns:mongo="http://www.springframework.org/schema/data/mongo"
        xmlns:p="http://www.springframework.org/schema/p" 
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:task="http://www.springframework.org/schema/task"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
            http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.1.xsd
            http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd">
    
        <!-- default id = mongo, host = localhost, and port = 27017 no nested options 
            for now -->
        <mongo:mongo>
            <mongo:options />
        </mongo:mongo>
    
        <!-- to translate any exceptions from @Repository annotated classes -->
        <context:annotation-config />
    
        <mongo:db-factory dbname="glasswing" mongo-ref="mongo" />
    
        <util:constant id="writeConcern" static-field="com.mongodb.WriteConcern.SAFE" />
        <util:constant id="writeResultChecking" static-field="org.springframework.data.mongodb.core.WriteResultChecking.EXCEPTION" />
    
        <bean id="ops" class="org.springframework.data.mongodb.core.MongoTemplate">
            <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
            <constructor-arg name="mongoConverter" ref="mappingConverter" />
    
            <property name="writeConcern" ref="writeConcern" />
            <property name="writeResultChecking" ref="writeResultChecking" />
        </bean>
    
        <mongo:mapping-converter base-package="kam.albert.domain.converter">
            <mongo:custom-converters>
                <mongo:converter><bean class="kam.albert.lab.TestConversion.PersonWriteConverter" /></mongo:converter>
                <mongo:converter><bean class="kam.albert.lab.TestConversion.PersonReadConverter" /></mongo:converter>
            </mongo:custom-converters>
        </mongo:mapping-converter>
    
        <context:spring-configured />
    
        <context:load-time-weaver/>
    
        <bean id="testConversion" class="kam.albert.lab.TestConversion" />  
     </beans>
    

2 个答案:

答案 0 :(得分:1)

这按设计工作。如果您注册自定义Converter实施,则必须提供完整的DBObject。我们的地图基础设施将不再对待它。

这意味着,如果您提供自定义转换器,则需要为创建的DBObject配备必要的类型信息。否则,在多态方案中,我们将无法解析要创建的类型(因此又不知道需要调用的潜在自定义转换器)。

我创建了一个sample test case,表明多态方案有效如果你让写入转换器用类型信息填充DBObject

答案 1 :(得分:0)