spring-data-jpa 一对多/多对一关系在Kotlin中不保存,而在Java中保存

tcomlyy6  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(212)

我有一个Kotlin项目..
我添加了complete code to github,所以你可以下载它来玩。
有2个实体:

@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,

    @OneToMany(fetch = FetchType.EAGER,
                mappedBy = "band",
                cascade = [CascadeType.ALL])
    val links: Set<Link> = mutableSetOf()
)

链接

@Entity
data class Link(

    @Id
    @Setter(AccessLevel.NONE)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(hidden = true)
    val id: Int? = null,

    var url: String? = null,

    @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
    @JoinColumn(name = "band_id", nullable = true)
    var band: Band? = null

)

SQL如下所示:

create table band
(
    id serial PRIMARY KEY not null,
    name varchar(100) not null
);

create table link
(
    id serial PRIMARY KEY not null,
    band_id int,
    url varchar(100) not null,

    CONSTRAINT "fk_band_links" FOREIGN KEY ("band_id") REFERENCES "band" ("id")    
);

我为我的测试创建了一个这样的乐队(在BandUtils.kt中)

fun createBandWithLinks(): Band {
        val link1 = Link()
        link1.url = "https://fb.com/"

        val link2 = Link()
        link2.url = "https://twitter.com/"

        return Band(
            name = "Band with links",
            links = mutableSetOf(link1,link2),
        )
    }

保存数据时:

bandRepository!!.save(BandUtils.createBandWithLinks())

我希望,链接和乐队都被保存了!这很有效。
但我看不到链接表中的带。
我以前在Java中做过同样的事情,我也看到Baeldung的例子是这样工作的。
Kotlin和Java之间有什么区别吗?

0yg35tkg

0yg35tkg1#

问题1.持久化双向关系Band=Link

在双向关系的情况下,Hibernate(或JPA实现)只关心关联的拥有方。拥有方是不具有mappedBy属性的那一方!
因此,如果我们只调用band.links = LinkUtils.createListOfLinks(),则Band将不会链接到新的Link实体,因为这不是关系的拥有/跟踪方。
您需要显式地将Band设置为Link,调用link.band = band,因为这是关系的拥有方。
当使用mappedBy时,开发人员有责任知道什么是拥有方,并更新关系的正确方,以便触发新关系在数据库中的持久性。
Java和Kotlin没有什么区别,都是JPA规范。

解决方案1:从双方设置关系

更正实用程序以从两侧设置关系

object BandUtils {
    fun createBandWithLinks(): Band {
         val band = Band(
            name = "MEA with links",
            description = "merch em all"
            )
        band.links = LinkUtils.createListOfLinks(band)
        return band
    }
}

object LinkUtils {
    fun createListOfLinks(band: Band): MutableSet<Link> {
        val link1 = Link()
        link1.url = "https://fb.com/joergi"
        link1.band = band

        val link2 = Link()
        link2.url = "https://twitter.com/joergi"
        link2.band = band

        var linkSets = mutableSetOf<Link>()
        linkSets.add(link1)
        linkSets.add(link2)
        return linkSets
    }
}

解决方案2:更改关系的拥有方(不推荐)

不需要改变你的效用。
删除OneToManymappedBy,添加JoinColumn

@OneToMany(
        fetch = FetchType.EAGER,
       // mappedBy = "band",
        cascade = [CascadeType.ALL]
    )
    @JoinColumn(name = "band_id")
    var links: MutableSet<Link> = mutableSetOf()

JoinColumn(insertable = false, updatable = false)ManyToOne相加

@ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
    @JoinColumn(name = "band_id", insertable = false, updatable = false)
    var band: Band? = null

实体定义

@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 = "links")
    @OneToMany(
        fetch = FetchType.EAGER,
        cascade = [CascadeType.ALL]
    )
    @JoinColumn(name = "band_id")
    var links: MutableSet<Link> = mutableSetOf()
}

@Entity
data class Link(

    @Id
    @Setter(AccessLevel.NONE)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(hidden = true)
    val id: Int? = null,

    var url: String? = null

) {
    @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
    @JoinColumn(name = "band_id", insertable = false, updatable = false)
    var band: Band? = null
}

在你的Baeldung文章中,这个问题在第6项中描述。

问题2. toString和hashCode的双向关系数据类循环依赖

当您正确创建实体时,在持久化过程中会收到StackOverflow错误,因为您有双向关系和定义为data class的实体。Kotling将生成不正确的hashCode实现,该实现在执行双向实体时会进入无限循环。

解决方案1:Lombok岛

使用Lombok代替data class并忽略一个关系边
第一个

解决方案2:将导致循环依赖关系的属性移动到数据类主体

请参阅详细信息Kotlin - Data class entity throws StackOverflowError

@Entity
data class Link(

    @Id
    @Setter(AccessLevel.NONE)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(hidden = true)
    val id: Int? = null,

    var url: String? = null

) {
    @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
    @JoinColumn(name = "band_id", nullable = true)
    var band: Band? = null
}

更新
为什么更改拥有方不是一个好的解决方案?

它对生成的查询计数的影响以及因此对性能的影响。

3个查询在拥有方为ManyToOne的情况下生成:

Hibernate:
    insert 
    into
        band
        (id, description, name) 
    values
        (default, ?, ?)
Hibernate: 
    insert 
    into
        link
        (id, band_id, url) 
    values
        (default, ?, ?)
Hibernate: 
    insert 
    into
        link
        (id, band_id, url) 
    values
        (default, ?, ?)

5个查询在拥有方为OneToMany的情况下生成:

Hibernate:
  insert 
    into
        band
        (id, description, name) 
    values
        (default, ?, ?)
Hibernate: 
    insert 
    into
        link
        (id, url) 
    values
        (default, ?)
Hibernate: 
    insert 
    into
        link
        (id, url) 
    values
        (default, ?)
Hibernate: 
    update
        link 
    set
        band_id=? 
    where
        id=?
Hibernate: 
    update
        link 
    set
        band_id=? 
    where
        id=?

相关问题