Home > Mobile >  Hibernate one-to-one unidirectional puts null in foreign key
Hibernate one-to-one unidirectional puts null in foreign key

Time:10-20

I have one-to-one mapping between two tables:

person -> id | name

person_status -> id | person_id | status

I need a unidirectional mapping, i.e. I want to get personStatus from person object, but not the other way round.

Here are the entities:

@Entity(name = "Person")
class PersonEntity(
    @Id
    @Column(name = "id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    val name: String,

    @OneToOne(mappedBy = "person", cascade = [CascadeType.ALL])
    var personStatus: PersonStatusEntity,
)
@Entity(name = "PersonStatus")
class PersonStatusEntity(
    @Id
    @Column(name = "id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    @OneToOne(cascade = [CascadeType.ALL])
    @JoinColumn(name = "person_id", referencedColumnName = "id")
    var person: PersonEntity? = null,

    @Enumerated(EnumType.STRING)
    val status: Status,

    @UpdateTimestamp
    val updateTime: OffsetDateTime? = null
)

Now when I run personRepository.findOneByName("John"), I get the PersonEntity object, which has the personStatus field populated. But inside that personStatus object, the person object is null.

personEntity = 
-- id = 1
-- name = "John"
-- personStatus = 
----- id = 1
----- person = null
----- status = APPROVED
----- updateTime = "2022-10-14T10:50:21.519228 11:00"

Ideally this person field inside personStatus should not be there, as I don't want to access a person from a personStatus object. But I can't figure out the correct annotations to tell hibernate what column to look for while joining. If there is no way to do that, then at least it should not be null. I looked at some existing questions, but they are doing bidirectional mapping, which I'm trying to avoid. How can I achieve that? Thanks.

CodePudding user response:

Why do you need a separate primary key for PersonStatus? Model it like this instead:

@Entity(name = "Person")
class PersonEntity(
    @Id
    @Column(name = "id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    val name: String,

    @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
    @JoinColumn(name = "id", referenceColumnName = "person_id")
    var personStatus: PersonStatusEntity,
)
@Entity(name = "PersonStatus")
class PersonStatusEntity(
    @Enumerated(EnumType.STRING)
    val status: Status,

    @UpdateTimestamp
    val updateTime: OffsetDateTime? = null
)

or even better, through a secondary table:

@Entity(name = "Person")
@SecondaryTable(name = "PersonStatus", pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
class PersonEntity(
    @Id
    @Column(name = "id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    val name: String,

    @Embedded
    var personStatus: PersonStatus,
)
@Embeddable
class PersonStatusEntity(
    @Column(table = "PersonStatus")
    @Enumerated(EnumType.STRING)
    val status: Status,

    @Column(table = "PersonStatus")
    @UpdateTimestamp
    val updateTime: OffsetDateTime? = null
)

Maybe you need to inline this though:

@Entity(name = "Person")
@SecondaryTable(name = "PersonStatus", pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
class PersonEntity(
    @Id
    @Column(name = "id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    val name: String,

    @Column(table = "PersonStatus")
    @Enumerated(EnumType.STRING)
    val status: Status,

    @Column(table = "PersonStatus")
    @UpdateTimestamp
    val updateTime: OffsetDateTime? = null
)
  • Related