我在使用没有JoinTables的单向OneToMany JPA映射时遇到了困难。
包括嵌套的完整实体看起来基本上是这样的:
Parent
└ ChildLvl1
└ ChildLvl2
└ Attribute
问题是我可以很好地插入一个复杂的实体,当我用repository.findAll()
检索它时它工作正常,结果对象是预期的,但当我使用api方法repository.getOne(Long)
时,它返回封闭的许多实体的次数太多了。 太多的次数取决于我插入的属性数量。
因此,如果我使用带有一个属性的childLvl2插入父级,则两种方法都会产生相同的结果。
但是让我说我插入一个带有两个属性的childLvl2,repository.findAll()
返回以下结果:
parent
└ childLvl1
└ childLvl2
├ attribute_1
└ attribute_1
而repository.getOne(Long)
返回以下结果:
parent (id=1)
├ childLvl1 (id=1)
│ ├ childLvl2 (id=1)
│ │ ├ attribute_1 (id=1)
│ │ └ attribute_2 (id=2)
│ └ childLvl2 (id=1)
│ ├ attribute_1 (id=1)
│ └ attribute_2 (id=2)
└ childLvl1 (id=1)
├ childLvl2 (id=1)
│ ├ attribute_1 (id=1)
│ └ attribute_2 (id=2)
└ childLvl2 (id=1)
├ attribute_1 (id=1)
└ attribute_2 (id=2)
(id = 1)这里表示数据库表中实体的内部ID。事实上,如果我直接在数据库中查看它包含我期望的数据,例如for ChildLvl1:
SELECT * FROM CHILD_LVL1;
ID | NAME | PARENT_ID
------|-------|-----------
1 | TEST | 1
这表明数据库内部的所有内容似乎都符合预期,但hibernate对repository.getOne(Long)
的映射似乎无法正常工作。
我也尝试使用@OneToMany(mappedBy="xxx")
代替@JoinColumn
,但我得到了相同的结果: - /,所以我猜错误是在其他地方。
查看hibernate执行的SQL,我发现这两种方法使用不同的查询,findAll()
执行多个查询然后构建实体,而getOne
只执行一个选择 join < / em>,但我真的希望得到同样的结果......
我的实体设置如下:
@Transactional
@Entity
@XmlRootElement
public class Parent {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
Long id;
String name;
@OneToMany(cascade= CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name="PARENT_ID", referencedColumnName="ID")
List<ChildLvl1>
...
}
@Transactional
@Entity
@XmlType
public class ChildLvl1 {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
Long id;
String name;
@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name="C_LVL1_ID", referencedColumnName="ID")
List<ChildLvl2> lvl2children;
...
}
@Transactional
@Entity
@XmlType
public class ChildLvl2 {
@Id
@Column(name="C_LVL2_ID")
@GeneratedValue(strategy=GenerationType.AUTO)
Long id;
String name;
@OneToMany(cascade= CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name="C_LVL2_ID")
List<Attribute> attributes;
...
}
@Transactional
@Entity
@XmlType
public class Attribute {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
Long id;
String key;
String value;
...
}
spring-data jpa repo:
public interface ParentRepository extends JpaRepository<Parent, Long>, QueryDslPredicateExecutor<Parent> {
public List<Parent> findByName(String name);
}
我的测试用例中的用法示例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestEntityCount {
@Autowired
ParentRepository repository;
@PostConstruct
public void postConstruct() {
if (!initialized) {
logger.debug("Setting up repository");
Parent parent = new Parent();
parent.setName("TEST_PARENT");
List<ChildLvl1> lvl1_children = new ArrayList<>();
ChildLvl1 lvl1_child = new ChildLvl1();
lvl1_child.setName("TEST_CHILD1");
List<ChildLvl2> lvl2_children = new ArrayList<>();
ChildLvl2 lvl2_child = new ChildLvl2();
lvl2_child.setName("TEST_CHILD2");
List<Attribute> attributes = new ArrayList<>();
attributes.add(new Attribute("KEY1", "VALUE1"));
attributes.add(new Attribute("KEY2", "VALUE2"));
lvl1_children.add(lvl1_child);
parent.setChildren1(lvl1_children);
lvl2_children.add(lvl2_child);
lvl1_child.setLvl2children(lvl2_children);
lvl2_child.setAttributes(attributes);
repository.save(parent);
initialized = true;
}
}
@Test
public void testFindAll() { // -> OK
Parent parent = repository.findAll().get(0);
logger.debug("Parent retrieved : {}", parent);
assertEquals(1, parent.getChildren1().size());
assertEquals(2, parent.getChildren1().get(0).getLvl2children().get(0).getAttributes().size());
}
@Test
@Transactional
public void testGetById() { // -> java.lang.AssertionError: expected:<1> but was:<2>
Parent parent = repository.getOne(1L);
logger.debug("Parent retrieved : {}", parent);
assertEquals(1, parent.getChildren1().size());
assertEquals(2, parent.getChildren1().get(0).getLvl2children().get(0).getAttributes().size());
}
...
我的项目使用
我已经设置了example project @Github,包括一个以可重现的方式演示问题的测试用例。