Адреса $unwind из коллекции Person с MongoDB с использованием Jongo

Привет, у меня проблемы с использованием Jongo для получения списка адресов из моей коллекции людей с использованием оператора $unwind.

Как видите, я определил класс Person следующим образом:

public class Person {
    @Id
    private long personId;
    private String name;
    private int age;
    private List<Address> addresses;
//getters and setters

и класс Address определяется как:

public class Address {
    private String houseNumber;
    private String road;
    private String town;
    private String postalCode;
//gettes and setters

Запрос довольно прост:

public List<Address> getAddressByPersonId(long id) {
    List<Address> list = persons.aggregate("{$project:{addresses:1}}")
                                    .and("{$match:{_id:#}}",id)
                                    .and("{$unwind: '$addresses'}")
                                    .as(Address.class);
    return list;
}

Моя коллекция:

> db.persons.find()
{ "name" : "Bob", "age" : 34, "addresses" : [   {       "houseNumber" : "12",
"road" : "High Street",         "town" : "Small Town",  "postalCode" : "BC2 3DE"
 },     {       "houseNumber" : "12",   "road" : "High Street",         "town" :
 "Small Town",  "postalCode" : "BC2 3DE" } ], "_id" : NumberLong(1) }
>

У меня есть тест JUnit для этого:

    @Before
    public void setUp(){
        service = new PersonServiceImpl();

        //store a person into the database (manually) before the tests
        MongoCollection persons = Database.getInstance().getCollection(CollectionNames.PERSONS);
        //create a new person
        Person p = new Person();
        p.setPersonId(1L);
        p.setName("Bob");
        p.setAge(34);
        //create two addresses for this person
        Address a = new Address();


        a.setHouseNumber("33");
        a.setRoad("Fake Road");
        a.setPostalCode("AB1 2CD");
        a.setTown("Big Town");
        p.addAddress(a);

        a.setHouseNumber("12");
        a.setRoad("High Street");
        a.setPostalCode("BC2 3DE");
        a.setTown("Small Town");
        p.addAddress(a);

        persons.save(p);
    }

    @After
    public void tearDown(){
        service = null;
    }

    @Test
    public void getAddressesByPersonIdTest(){

        List<Address> list = service.getAddressByPersonId(1L);
        for (Address item : list){
            item.print();
        }

        Assert.assertTrue(list.size() > 0);
    }
}

Какие выходы

### Address: null null, null, null 
### Address: null null, null, null 

Я не очень понимаю, в чем проблема. По-видимому, тест не терпит неудачу (поэтому list.size() больше нуля... но печатает null). Метод print() проверен и работает.

Я хотел бы получить два адреса Боба, однако запрос возвращает нулевые объекты. Я что-то упускаю? Должен ли я использовать $unwind по-другому? Пожалуйста, предложите


person nuvio    schedule 08.11.2013    source источник
comment
вы пытаетесь преобразовать объекты боба в объекты адреса - адреса встроены в объект верхнего уровня.   -  person Asya Kamsky    schedule 11.11.2013


Ответы (2)


Aggregation Framework возвращает точно такой же формат, как и в нем — после раскрутки единственная разница заключается в том, сколько адресов встроено в поле addresses. В вашем случае вы получаете два документа:

{ "_id": 1,
  "name" : "Bob", 
  "age" : 34, 
  "addresses" : { "houseNumber" : "12", 
                  "road" : "High Street",
                  "town" : "Small Town",
                  "postalCode" : "BC2 3DE"
  }
}

Это не очень эффективно преобразует класс Address, потому что, как вы можете видеть, объект Address является значением поля addresses, а не объектом верхнего уровня.

Что непонятно, так это зачем вообще нужно агрегировать - вы получаете список адресов для человека. Когда вы выполняете простой find() для человека по идентификатору, поле «адреса» уже представляет собой List из Address объектов.

В MongoDB find() имеет возможность вернуть только выбранные поля или исключить именованные поля (по аналогии с SQL SELECT * не так эффективен, как SELECT col1, col2), поэтому, если вы сделаете эквивалент Java db.persons.find({filter-condition}, {"_id":0, "addresses":1}) вы получите документ с только список адресов.

person Asya Kamsky    schedule 11.11.2013
comment
Да, я понимаю это, я хочу сказать, что если у меня есть Человек с полем «адреса», полем «история покупок», полем «история поиска», полем «кредитные карты» (просто в качестве примера)... Зачем мне отображать весь объект верхнего уровня, когда мне нужны только адреса? Pheraps, мой подход совершенно неверен, но ответ @Shad решает мой вопрос. Спасибо - person nuvio; 17.11.2013
comment
Я не уверен, знаете ли вы об этом, но вам не обязательно получать весь документ при запросе MongoDB. find() принимает два аргумента, первый — это фильтр — как предложение where, второй — список полей, которые нужно включить или исключить. Все, что вам нужно, это указать, что вы хотите вернуть { _id:0, address:1}, и вы получите обратно просто список адресов. - person Asya Kamsky; 17.11.2013

Я хотел бы добавить дополнительную информацию для решения проблемы с помощью Jongo.

Я хотел отобразить результаты Mongo прямо в список объектов Addresses, избегая ненужного создания экземпляров объектов Person. Представьте, что мой класс Person имеет эти дополнительные поля:

...
List<Address> addresses; 
List<Purchases> purchases; 
List<Payments> creditcards;
List<Product> watchlist;
//etc
...

Однако нам нужно только получить 'addresses'. Таким образом, первый подход может быть:

Решение 1)

public List<Address> getAddressByPersonId(long id) {
        List<Address> list = persons.aggregate("{$match: {_id: #}}", id)
                .and("{$project: {addresses: 1, _id: 0}}")
                .and("{$unwind: '$addresses'}")
                .and("{$project: {houseNumber:'$addresses.houseNumber', road:'$addresses.road', town:'$addresses.town', postalCode:'$addresses.postalCode'}}")
                .as(Address.class);
        return list;
    }

Плюсы: мне нужен только список Addresses, меньше памяти. Минусы: мне нужно $project каждое поле класса Address, иначе оно не будет отображено в Jongo (null). Как указал @Asya Kamsky, это не масштабируемое решение.

Решение 2)

public List<Address> getAddressByPersonId(long id) {
        List<Person> list = persons.aggregate("{$match:{_id:#}}",id).and("{$project: {addresses: 1, _id: 0}}")
                .as(Person.class);

        return list.get(0).getAddresses();
    }

За: мне не нужно указывать, какие поля списка adddress нам нужны. Так что это масштабируемое решение, если я хочу добавить дополнительное поле. Минусы: мне нужно выделить весь объект Person, что может привести к значительным накладным расходам при работе со сложными объектами и большим количеством записей в подобных сценариях.

Если у вас есть другие рабочие (Jongo) решения, поделитесь ими.

person nuvio    schedule 18.11.2013