General question on composition OneToOne

If I have en entity A with a oneToOne composition relation to the entity B,
jmix will create

@JmixEntity
@Table(name = "A", indexes = [
    Index(name = "IDX_A_B_OWNING_SIDE", columnList = "B_OWNING_SIDE_ID")
])
@Entity
open class A {

    @OnDeleteInverse(DeletePolicy.UNLINK)
    @JoinColumn(name = "B_OWNING_SIDE_ID")
    @Composition
    @OneToOne(fetch = FetchType.LAZY)
    var bOwningSide: B? = null

   …
}

and

@JmixEntity
@Table(name = "B")
@Entity
open class B {
    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    var id: UUID? = null

    @Composition
    @NotNull
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "bOwningSide", optional = false)
    var a: A? = null

    …
}

First question, since I can get the error “During synchronization a new object was found through a relationship that was not marked cascade PERSIST” if I create all instances but don’t save them in the good order,
why Jmix don’t set the cascade @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]), is-it safe to add it ?

Second question, since the owning side is the only one valid to edit the relation and get it saved in database, why the reverse side is normal code which can create bug if the dev don’t check the owning side in an affection…
Is-it possible to have a behavior with both side equivalent to read or write the relation ?

Maybe like that ?

@JmixEntity
@Table(name = "A", indexes = [
    Index(name = "IDX_A_B_OWNING_SIDE", columnList = "B_OWNING_SIDE_ID")
])
@Entity
open class A {

    @OnDeleteInverse(DeletePolicy.UNLINK)
    @JoinColumn(name = "B_OWNING_SIDE_ID")
    @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
    @Composition
    var bOwningSide: B? = null
        set(value) {
            field = value
            if (value?.a != this) {
                value?.a = this
            }
        }

   …
}

and

@JmixEntity
@Table(name = "B")
@Entity
open class B {
    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    var id: UUID? = null

    @OnDeleteInverse(DeletePolicy.CASCADE)
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "bOwningSide", optional = false, cascade = [CascadeType.ALL])
    var a: A? = null
        set(value) {
            field = value
            if (value?.bOwningSide != this) {
                value?.bOwningSide = this
            }
        }

    …
}

Yes you can add cascade = CascadeType.ALL to simplify the programmatic control of the relationship. Studio doesn’t do it because the cascade attribute is optional by the spec, and the framework sets the relationship correctly anyway when the entity_openComposition action is used.

In general, we tend to make decisions for the application developer only when they are needed for basic usage scenarios in declarative UI. When it comes to more advanced use cases with programmatic data manipulation, we prefer to stick to the specification.

Perhaps I answered to this question above. We usually make opinionated decisions only if they are needed for declarative data-centric UI.