我有两个实体。
在band
和product
之间有一种 * 多对多的关系 *,因为一个产品可以有许多乐队(比如一个采样器,上面有许多乐队),而一个乐队可以有许多产品(比如衬衫,cd等)。
当我运行我的测试(见下文)时,它保存了2个实体,但它没有保存band_has_product
表中2个实体之间的关系。
据我所知,我不需要额外的存储库/实体来将关系保存到band_has_product
表中,因为所有的注解和休眠,它应该自动完成。
是这样吗?
我很确定,我错过了一些东西,我看了很多教程,但我找不到我的问题。期待一些帮助。谢啦,谢啦
下面是代码示例:product
实体是这样创建的:
@Entity
data class Product(
@Id
@Setter(AccessLevel.NONE)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(hidden = true)
val id: Int? = null,
var name: @NotEmpty(message = "name must not be empty") String? = null,
) {
@JsonBackReference(value ="bands")
@ManyToMany( fetch = FetchType.EAGER,
mappedBy = "products",
)
var bands: MutableSet<Band> = mutableSetOf()
}
字符串
而带实体看起来像这样:
@Entity
data class Band(
@Id
@Setter(AccessLevel.NONE)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(hidden = true)
val id: Int? = null,
var name: @NotEmpty(message = "name must not be empty") String? = null,
var description: String? = null,
){
@JsonBackReference(value ="products")
@ManyToMany(
fetch = FetchType.EAGER,
cascade = [CascadeType.ALL])
@JoinTable(
name = "band_has_product",
joinColumns = [JoinColumn(name = "band_id")],
inverseJoinColumns = [JoinColumn(name = "product_id")]
)
var products: MutableSet<Product> = mutableSetOf()
}
型
乐队是这样创建的:
create table band
(
id serial PRIMARY KEY not null,
name varchar(100) not null,
description text
);
型
产品是这样创建的:
create table product
(
id serial PRIMARY KEY not null,
name varchar(100) not null
);
型
我写了一个测试来测试这个。波段和产品已保存,但自动生成的band_has_product
表为空
@Test
fun `creating Product should be saved in the DB`() {
var bandSaved1 = bandRepository!!.save(BandUtils.createBandWithoutLinks("band1"))
productRepository!!.save(ProductUtils.createProductWithBand(
bandSaved1,"product1"))
val bands = (bandRepository!!.findAll() as Collection<Band>)
val products = (productRepository!!.findAll() as Collection<Product>)
bands.size shouldBe 1 // TRUE
products.size shouldBe 1 // TRUE
// not true, as the band_has_product table is empty
val product = products.first().bands shouldContain bandSaved1
型
Test helper函数如下所示:
fun createBandWithoutLinks(name: String = BAND_NAME_WITHOUT_LINK): Band {
val band = Band()
band.name = name
band.description = "merch em all"
return band
}
fun createProductWithBand(band: Band,
name: String = PRODUCT_NAME): Product {
var product = Product(
name = name,
)
product.bands.add(band)
return product
}
型
仓库是普通的CrudRepos:
@Repository
interface BandRepository : CrudRepository<Band?, Int?> {}
@Repository
interface ProductRepository : CrudRepository<Product?, Int?> {}
型
我使用标准的保存功能。MainApp
看起来像这样:
@EnableJpaRepositories
@SpringBootApplication
class MainApp
fun main(args: Array<String>) {
runApplication<MainApp>(*args)
}
型
1条答案
按热度按时间xwbd5t1u1#
从Steven Haines中读取"Java persistence with JPA and Hibernate, Part 2: Many-to-many relationships",您可以看到,在使用JPA和Hibernate的多对多关系中,您需要正确地管理关系的双方。
例如,在测试助手函数
createProductWithBand
中:字符串
在此函数中,将创建一个
Product
,并将一个Band
添加到其bands
集合中。然而,相反的情况并没有发生;Product
没有被添加到Band
的products
集合中。你的测试函数:
型
在这里,保存了一个
Band
,然后用该Band
创建了一个Product
并保存。但是,代码中没有任何地方将Product
添加到Band
的products
集合中。这意味着只设置了关系的一侧(即
Product
知道Band
,但Band
不知道Product
)。在像这样的双向关系中,必须更新关系的两端。JPA提供者(在本例中为Hibernate)需要了解双方的关系,以便正确地更新连接表(
band_has_product
)。型
通过这样做,当你保存
Product
时,Hibernate将知道它与Band
相关联,同样,当你保存Band
时,Hibernate将知道它与Product
相关联。这将确保正确填充连接表band_has_product
。从OP的answer中,我可以添加在Kotlin中,
plus
函数返回一个新列表,其中包括正在添加的元素,但它不会修改原始列表。因此,当您再次调用band.products.size
时,它仍然是0,因为原始列表没有被修改。要正确更新
Band
实体的products
集合,可以将plus
的结果赋回Band
的products
属性。这将使用添加的新产品更新products
集合。根据你的方法,这将是:
型
修改后的helper函数:
型
通过将
plus
的结果赋值回products
和bands
集合,您正在使用新实体更新集合,并且更改将正确地持久化。由于代码现在直接修改集合,因此在这个特定场景中不再需要显式地使用
@Transactional
。但是,请注意,在更复杂的场景或真实世界的应用程序中,可能仍然需要使用事务来确保数据一致性和操作的原子性。
Nathan在评论中添加:
对于写操作,将乘积添加到
bank
是唯一重要的操作。带有mappedBy=""
的@ManyToMany
注解在加载时使用,但在持久化或更新时不使用。在测试中,如果您希望正确加载
mappedBy=""
,您可能需要手动刷新、逐出或刷新,以便正确加载mappedBy=""
(如果您只设置了一侧)。至少,这就是hibernate 3.x、4x和5.x的工作方式。没有太多的经验与6。但我认为这在6年里没有改变。
确实,在这里,
Band
实体是关系的拥有方(因为它不使用mappedBy
属性),而Product
实体是相反方(因为它使用mappedBy
属性)。在测试时,如果您在关系的反向端(在本例中为
Product
)进行了更改,并且希望它立即反映在会话缓存中,则可能需要手动将会话刷新到数据库并刷新实体。这是因为Hibernate可能在事务提交之前不会将更改同步到数据库。型
警告:
entityManager
应该自动连接到测试类中,这样才能正常工作。这种做法通常在测试中更常见。
在生产代码中,通常不需要在同一事务中强制刷新或刷新,因为更改将在事务结束时自动同步。
您还需要确保这些操作是在事务中执行的。这是因为,在JPA中,持久化上下文通常在事务结束时刷新到数据库。
另见“JPA/Hibernate Persistence Context”from Baeldung
型
这应该确保当您保存
Product
时,相应的波段也会被保存,反之亦然,并且关系存储在band_has_product
表中。它使用事务范围的持久化上下文,这是JPA中使用Java EE或Spring环境时的默认持久化上下文类型。
@Transactional
注解确保整个方法在单个事务中运行。这意味着在方法开始时创建一个持久性上下文,并且在此持久性上下文中跟踪对实体所做的任何更改。当方法完成时,事务被提交或回滚,并且持久性上下文被关闭。此时,对事务中的托管实体所做的任何更改都将刷新到数据库。
这种类型的持久性上下文适合于所提供的场景,在这种场景中,您希望在单个原子操作中保存实体及其关系,以确保数据一致性。