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 一起工作。我尝试了各种注释,所有这些都产生了不同程度的失败。我尝试过的事情包括:
@AbstractSuperclass
@DiscriminatorColumn
和 @DiscriminatorValue
@OneToMany
虽然我可能做错了,所以如果您认为以上任何一项可以解决问题,请随时提出建议。
为了轻松重现这种情况,我创建了一个简单的示例项目,以简化的方式复制数据结构并设法重现问题:
让 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
,我觉得这样更合适,我会得到如下错误:
- javax.persistence.PersistenceException: org.hibernate.InstantiationException: 无法实例化抽象 class 或接口: : dataObjects.Item
- org.hibernate.MappingException:无法确定类型:dataObjects.Item,在 table:GameCharacter_inventory,对于列:[org.hibernate.mapping.Column(库存)]
无论如何,这是一个当前无法通过上述设置进行测试的案例:
@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
启动该容器
已更新:
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;
}
}
我在 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 一起工作。我尝试了各种注释,所有这些都产生了不同程度的失败。我尝试过的事情包括:
@AbstractSuperclass
@DiscriminatorColumn
和@DiscriminatorValue
@OneToMany
虽然我可能做错了,所以如果您认为以上任何一项可以解决问题,请随时提出建议。
为了轻松重现这种情况,我创建了一个简单的示例项目,以简化的方式复制数据结构并设法重现问题:
让 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
,我觉得这样更合适,我会得到如下错误:
- javax.persistence.PersistenceException: org.hibernate.InstantiationException: 无法实例化抽象 class 或接口: : dataObjects.Item
- org.hibernate.MappingException:无法确定类型:dataObjects.Item,在 table:GameCharacter_inventory,对于列:[org.hibernate.mapping.Column(库存)]
无论如何,这是一个当前无法通过上述设置进行测试的案例:
@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
启动该容器
已更新:
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;
}
}