Hibernate OGM ~ 鉴别多态对象(抽象超类)

Hibernate OGM ~ Discriminating Polymorphic Objects (Abstract Superclasses)

我在 Java 中有一个 MongoDB 的数据模型,它使用抽象超级 classes 来区分多态对象,如下所示:

[Object that gets saved in the database]
└[List<AbstractSuperclass>]
 ├[SubclassA extends AbstractSuperclass]
 ├[SubclassB extends AbstractSuperclass]
 ├[SubclassA extends AbstractSuperclass]
 ├[SubclassC extends AbstractSuperclass]
 ├[SubclassC extends AbstractSuperclass]
 └[SubclassD extends AbstractSuperclass]

当我只使用 Java MongoDB 驱动程序时,如果我使用 @BsonDiscriminator 注释就可以正常工作。

但是,我还不能让它与 Hibernate 一起工作。我尝试了各种注释,所有这些都产生了不同程度的失败。我尝试过的事情包括:

虽然我可能做错了,所以如果您认为以上任何一项可以解决问题,请随时提出建议。

为了轻松重现这种情况,我创建了一个简单的示例项目,以简化的方式复制数据结构并设法重现问题:

让 GameCharacter class 成为我要与其所有子项一起存储的对象。据我了解,这意味着 GameCharacter 应该是这里唯一的 @Entity,因为所有其他对象仅与 GameCharacter 有关。

@Entity
public class GameCharacter {
    @Id
    public String _id;
    public String name;
    public Weapon weapon;
    public Armor armor;
    @OneToMany
    public List<Item> inventory;
    
    public GameCharacter() {
        
    }
    
    public GameCharacter(String name, Weapon weapon, Armor armor, String _id, List<Item> inventory) {
        this._id = _id;
        this.name = name;
        this.weapon = weapon;
        this.armor = armor;
        this.inventory = inventory;
    }
}

让 Item 成为库存项目的抽象超class。

@BsonDiscriminator
@Entity
@DiscriminatorColumn(name="ITEM_TYPE")
public abstract class Item {
    @Id
    public String name;
    public int amount;
    
    public Item() {
        
    }
    
    public Item(String name, int amount) {
        this.name = name;
        this.amount = amount;
    }
}

让 Potion 成为填充库存并继承抽象项目 class 的几个潜在 classes 之一。

@Embeddable
@DiscriminatorValue("POTION")
public class Potion extends Item {
    
    public Potion() {
    } 
    
    public Potion(String name, int amount) {
        super(name, amount);
    }
}

以上代码示例是我最接近可行的解决方案。这样,至少 Hibernate OGM 可以保存和读取数据库。但是,它没有正确保存它们,因为在检查 MongoDB 中的数据时,结果是这样的:

_id:"sylv01"
weapon:Object
    [...]
name:Sylvia the Hero
armor:Object
    [...]
inventory:Array
    0:Hi-Potion
    1:Mega Ether

...wheras 应该是这样的(由 Java MongoDB 驱动程序保存):

_id:"sylv01"
weapon:Object
    [...]
name:Sylvia the Hero
armor:Object
    [...]
inventory:Array
    0:Object
        _t:"dataObjects.Potion"
        amount:3
        name:"Hi-Potion"
    1:Object
        _t:"dataObjects.Ether"
        amount:5
        name:"Mega Ether"

我认为这可能是因为我必须将 Item 注释为 @Entity,即使在我的数据模型中它实际上更像是 @Embeddable@AbstractSuperclass。但是,如果我这样做,并且可能将 GameCharacter 中的 @OneToMany 注释换成 @ElementCollection,我觉得这样更合适,我会得到如下错误:

无论如何,这是一个当前无法通过上述设置进行测试的案例:

    @Test
    void writeAndReadShouldSaveInventoryCorrectly() throws SecurityException, IllegalStateException, NotSupportedException, SystemException, HeuristicMixedException, HeuristicRollbackException, RollbackException {
        
        GameCharacter sylvia = GameCharacters.sylvia();
        
        OgmAccessor.write(sylvia, entityManagerFactory);
        
        transactionManager.begin();
        GameCharacter loadedGameCharacter
            = entityManager.find(GameCharacter.class, sylvia._id);
        transactionManager.commit();
        
        int actualInventorySize = loadedGameCharacter.inventory.size();
        assertEquals(2, actualInventorySize);
    }

此测试当前因以下错误而崩溃:

    javax.persistence.EntityNotFoundException: Unable to find dataObjects.Item with id Hi-Potion

...我认为这是因为我必须将库存物品设为 @Entity 才能做到这一点。

然而,我真正想要的是一个解决方案,当我提交 GameCharacter 时,与 GameCharacter 相关的所有内容都会被保存,因为它包含的子对象在 GameCharacter 之外没有任何意义,因此对他们来说没有意义作为单独的实体存在。

我感觉我已经接近解决方案了,但我无法弄清楚我在这里和哪里做错了什么。因此,我们将不胜感激。


编辑:

我在项目中使用的依赖项是:

plugins {
    id 'java-library'
}

repositories {

    jcenter()
}

dependencies {
    // Use JUnit test framework
    testCompile("org.junit.jupiter:junit-jupiter:5.6.0")
    testCompile("org.junit.platform:junit-platform-runner:1.6.0")
    
    //MongoDB Driver
    compile 'org.mongodb:mongodb-driver:3.6.0'
    compile 'commons-logging:commons-logging:1.2'
    
    //Logging
    compile 'log4j:log4j:1.2.17'
    compile 'org.slf4j:slf4j-log4j12:1.7.25'
    compile 'commons-logging:commons-logging:1.2'
    
    //Versioning
    compile group: 'org.hibernate.ogm', name: 'hibernate-ogm-mongodb', version: '5.4.0.Final'
    compile group: 'org.jboss.narayana.jta', name: 'narayana-jta', version: '5.9.2.Final'
    compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
}


编辑:

我将其添加到 public github 存储库中,因此任何有兴趣的人都可以尝试看看是否找到了可用的版本。测试项目还包含 MongoDB Docker 容器的配置,可以使用文件夹 /mongo_db.

中的 docker-compose up 启动该容器

https://github.com/Kira-Cesonia/MongoDBVersioningTest

已更新: JPA 不支持可嵌入对象的层次结构。另外,您使用的是 @OneToMany 可嵌入对象而不是实体。

可以使用的映射如下:

@Entity
public class GameCharacter {
    ...

    @ElementCollection
    public List<Item> inventory;

    ...
}

@BsonDiscriminator
@Embeddable
public class Item {
    public String type;
    public String name;
    public int amount;

    public Item() {
    }

    public Item(String name, int amount, String type) {
        this.name = name;
        this.amount = amount;
        this.type = type;
    }
}