简体   繁体   中英

Dynamic POJO type with MongoDB Java collection

I understand how to use the MongoDB Java driver to read and store POJO's when we know the specific type we are working with. In the project I am working on, the codec provider (and thus codec registry) is built by classnames passed in via properties (there are some hard-coded values). This supports for example logging out certain messages only when doing tests (JUnit).

The trouble is I cannot determine how to work with such a type with the MongoCollection. Below is a toy problem that is based on the real issue (we have a generic "message" interface that all of our POJOs we wish to log implement. The logging function accepts an object of that "message" interface and needs to insert it into the collection).

First we have our interface:

public interface MyInterface {
    int getX();
}

and simple implementation:

public class ImplA implements MyInterface {
    public int getX(){return 1;}
}

Then the code that uses them:

import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import org.bson.codecs.pojo.ClassModel;
import org.bson.codecs.pojo.PojoCodecProvider;

import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

public class Main {

    public static void main(String[] args){
        new Main().run();
    }

    public void run(){
        MongoClient client = MongoClients.create("mongodb://localhost:27018");
        MongoDatabase db = client.getDatabase("db1").withCodecRegistry(fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(),
                fromProviders(PojoCodecProvider.builder().register(
                        ClassModel.builder(MyInterface.class).enableDiscriminator(true).build(),
                        ClassModel.builder(ImplA.class).enableDiscriminator(true).build()
                ).build())
        ));

        MyInterface obj1 = new ImplA();

        db.getCollection("ImplA",ImplA.class).insertOne((ImplA) obj1);
        db.getCollection("ImplA",obj1.getClass()).insertOne(obj1);
        db.getCollection("ImplA",Object.class).insertOne(obj1);
    }
}

While the first insert compiles, the second fails with this error:

Error:Error:line (42)java: incompatible types: Main.MyInterface cannot be converted to capture#1 of ? extends Main.MyInterface

It seems to me this functionality should be possible as the MongoCollection definitely has information on the type. The object passed in has some type, I want the collection for that type and to insert it. If the second insertOne is commented out, the code compiles. The driver throws an exception saying there is no codec for Object; thus, it is definitely determining the codec to use from what is given to get collection.

Update: I had tried various casting with getClass.cast, various generics combinations, but here is an option that works:

MyInterface obj1 = new ImplA();

Class<MyInterface> myC = (Class<MyInterface>) Class.forName(obj1.getClass().getName());
db.getCollection("ImplA",myC).insertOne(obj1);

That can raise a class not found exception, and seems unnecessarily complex.

尝试使用: db.getCollection("ImplA").withDocumentClass(obj1.getClass()).insertOne(obj1)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM