Получите выгоду от Embedded Document Mongo Java

У меня есть следующий документ в монго:

>  {    "_id": ObjectId("569afce4b932c542500143ec"),    
>    "date": "2016-1-17T2:31:0Z",    
>    "day": NumberInt(17),    
>    "model1": {
>      "date": "2016-01-17T02:31+0000",
>      "MondayModel": {
>        "gtxdotdot": {
>          "xdotdot": 0,
>          "xdot": 0
>       },
>        "lsxdotdot": {
>          "xdotdot": 0,
>          "xdot": 0
>       },
>        "gtxdot": {
>          "xdotdot": 0,
>          "xdot": 0
>       },
>        "lsxdot": {
>          "xdotdot": 0,
>          "xdot": 0
>       },
>        "modeldotdot": {
>          "mean": 0,
>          "sdvar": 0
>       },
>        "modeldot": {
>          "mean": 0,
>          "sdvar": 0
>       }
>     } 
>     }

Я хочу найти этот документ и извлечь только значения model1.MondayModel.gtxdotdot.xdotdot/xdot/mean/sdvar...

Мой текущий код делает это со следующим:

MongoCursor<Document>  back = collection.find(and(eq("topic",topic),eq("sp",sp))).limit(1).iterator();
if (back.hasNext())
{
    Document doc = back.next();
    Document tmpddc1 = (Document)doc.get("model1");
    Document tmpddc2 = (Document)tmpddc1.get("MondayModel");

    Document tmpddc3 = (Document)tmpddc2.get("gtxdotdot");
    gtxdotdotXdotdot = tmpddc3.getDouble("xdotdot");
    gtxdotdotXdot    = tmpddc3.getDouble("xdot");

             tmpddc3 = (Document)tmpddc2.get("lsxdotdot");
    lsxdotdotXdotdot = tmpddc3.getDouble("xdotdot");
    lsxdotdotXdot    = tmpddc3.getDouble("xdot");

             tmpddc3 = (Document)tmpddc2.get("gtxdot");
    gtxdotXdotdot = tmpddc3.getDouble("xdotdot");
    gtxdotXdot    = tmpddc3.getDouble("xdot");            

             tmpddc3 = (Document)tmpddc2.get("lsxdot");
    lsxdotXdotdot = tmpddc3.getDouble("xdotdot");
    lsxdotXdot    = tmpddc3.getDouble("xdot");  


             tmpddc3 = (Document)tmpddc2.get("modeldotdot");
    modeldotdotXmean = tmpddc3.getDouble("mean");
    modeldotdotXsdvar    = tmpddc3.getDouble("sdvar");             

             tmpddc3 = (Document)tmpddc2.get("modeldot");
    modeldotXmean = tmpddc3.getDouble("mean");
    modeldotXsdvar    = tmpddc3.getDouble("sdvar");                         

}

Вместо того, чтобы думать, что он документ (как указано выше), есть ли способ получить значения, используя точечную нотацию [model1.MondayModel.gtxdotdot.xdotdot]? Что-то вроде:

double value =  doc.getDouble("model1.MondayModel.gtxdotdot.xdotdot");

person DevilCode    schedule 26.01.2016    source источник


Ответы (2)


Я не думаю, что вы можете использовать запись через точку напрямую, но вы можете создать свою собственную вспомогательную функцию.

Решение 1: получить поле с точечной нотацией

public static Object getWithDotNotation( Document document, String dots ) 
     throws MongoException{

    String[] keys = dots.split( "\\." );
    Document doc = document;

    for( int i = 0; i < keys.length - 1; i++ ){
        Object o = doc.get( keys[ i ] );
        if( o == null || !( o instanceof Document ) ){
            throw new MongoException( String.format( 
                    "Field '%s' does not exist or s not a Document", keys[ i ] ) );
        }
        doc = ( Document ) o;
    }//end for

    return doc.get( keys[ keys.length - 1 ] );
}

Затем вы можете использовать его следующим образом:

String dotNotation = "model1.MondayModel.gtxdotdot.xdotdot";

FindIterable<Document> projection = mongoColl.find()
    .projection( fields( include( dotNotation ) ) );

Double value = ( Double ) getWithDotNotation( projection.first(), dotNotation );

System.out.println( value ); // result: 0.0

Это значительно упростит ваш код. Единственное, о чем нужно заботиться, это:

  • если вы не уверены в записи через точку, используйте блок try catch
  • метод getWithDotNotation может возвращать значение null
  • будьте осторожны с принудительным приведением типов, используйте его, только если вы на 100% уверены в типе ваших данных (здесь Double).

Решение 2. Сделайте документ плоским

public static Document flattenDoc( Document document ){

    Document flattened = new Document();
    Queue<Pair<String, Document>> queue = new ArrayDeque<>();
    queue.add( new Pair<>( "", document ) );

    while( !queue.isEmpty() ){
        Pair<String, Document> pair = queue.poll();
        String key = pair.getKey();
        for( Map.Entry<String, Object> entry : pair.getValue().entrySet() ){
            if( entry.getValue() instanceof Document ){
                queue.add( new Pair<>( key + entry.getKey() + ".", ( Document ) entry.getValue() ) );

            }else{
                flattened.put( key + entry.getKey(), entry.getValue() );

            }
        }//end for
    }

    return flattened;
}

С вашими демонстрационными данными результат flattenDoc будет следующим:

Document{{_id=569afce4b932c542500143ec,
date=2016-1-17T2:31:0Z,
day=17,
model1.date=2016-01-17T02:31+0000,
model1.MondayModel.gtxdotdot.xdotdot=0.0,
model1.MondayModel.gtxdotdot.xdot=0.0,
model1.MondayModel.lsxdotdot.xdotdot=0.0,
model1.MondayModel.lsxdotdot.xdot=0.0,
model1.MondayModel.gtxdot.xdotdot=0.0,
model1.MondayModel.gtxdot.xdot=0.0,
model1.MondayModel.lsxdot.xdotdot=0.0,
model1.MondayModel.lsxdot.xdot=0.0,
model1.MondayModel.modeldotdot.mean=0.0,
model1.MondayModel.modeldotdot.sdvar=0.0,
model1.MondayModel.modeldot.mean=0.0,
model1.MondayModel.modeldot.sdvar=0.0}}

Таким образом, вы можете использовать getDouble("model1.MondayModel.gtxdotdot.xdotdot") напрямую. Этот подход может быть более эффективным, если вам нужно получить доступ ко всем полям.

person Derlin    schedule 30.01.2016
comment
Разве это не должно быть for(int i = 0; i < keys.length; i++) вместо этого? - person Nikiforos; 18.04.2018
comment
нет, так как последнее значение является именем свойства документа, а не документа. Поэтому вам нужно остановиться на имени перед последней точкой (надеюсь, это объяснение понятно?) - person Derlin; 18.04.2018

Вы можете сделать это одним из трех способов.

Вы можете использовать структуру агрегации для проецирования значения встроенного поля с помощью записи через точку.

Использование агрегации

 import static com.mongodb.client.model.Aggregates.*;
 import static com.mongodb.client.model.Filters.eq;
 import static com.mongodb.client.model.Projections.computed;
 import static java.util.Arrays.*;
 import static com.mongodb.client.model.Projections.include;

 MongoClient mc = new MongoClient();
 MongoDatabase db = mc.getDatabase("test");
 MongoCollection<Document> collection = db.getCollection("collection");
 Document document = 
     collection.aggregate(asList(
        match(eq("day",17)),
        project(computed("val", "$model1.MondayModel.gtxdotdot.xdotdot")))).
     first();
 Double embeddedField = document.getDouble("val");

Использование различных

 Double embeddedField = collection.distinct("model1.MondayModel.gtxdotdot.xdotdot", eq("day",17), Double.class).first();

Использование поиска

 Document document = collection.find(eq("day",17)).projection(include("model1.MondayModel.gtxdotdot.xdotdot")).first();
 Double embeddedField = document.get("model1", Document.class).get("MondayModel", Document.class).get("gtxdotdot", Document.class).getDouble("xdotdot")
person s7vr    schedule 11.04.2018
comment
Ваши первые два примера относятся к запросу, а не к получению значений из объектов Document. Однако ваша вторая строка вашего примера «Использование поиска» действительна. - person Nikiforos; 11.04.2018
comment
Получение значений напрямую из документа невозможно. См. здесь. В качестве альтернативы я предоставил запрос структуры агрегации, в котором вы можете использовать точечную нотацию для проецирования встроенного значения и просто перейти на сторону Java, чтобы прочитать значение. - person s7vr; 11.04.2018