Hibernate的一对一关系,使用主键之间的关联,并具有延迟加载行为。

huangapple 未分类评论46阅读模式
英文:

Hibernate OneToOne between PK's with lazy behaviour

问题

我正在尝试使用 Hibernate 5.3.13.Final 和 Wildfly 18 注解来实现一个名为 MyEntity 的实体,以及另一个名为 MyEntityInfo 的实体。

想法是让 MyEntity 存储一些常被请求的字段,而 MyEntityInfo 则存储一些不经常被请求的字段。两者共享名为 SID(Long)的相同主键,且从 Info 的 SID 到 Entity 的 SID 存在一个外键关系。可以存在没有 info 的实体。

通常情况下,您不会需要额外的信息。例如,我不希望在像这样查询实体时获取信息实体:

MyEntityImpl entity = em.find(MyEntityImpl.class, 1L);

然而,当我运行这段代码时,我发现有第二个查询,除了主要查询外,还会获取 Info 实体,就像是 EAGER 行为一样。

我正在使用 @OneToOne 进行关系映射。我已经尝试了多种 FetchTypeoptional@LazyToOne 的组合,但迄今为止都没有成功。

以下是 MyEntityMyEntityInfo 类的代码(已省略其他的 getter 和 setter 方法):

MyEntity(ID 生成器是自定义序列生成器):

@Entity
@Table(name = MyEntityImpl.TABLE_NAME)
public class MyEntityImpl {

    public static final String TABLE_NAME = "TMP_MY_ENTITY";

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_" +
            TABLE_NAME)
    @GenericGenerator(name = "GEN_" +
            TABLE_NAME, strategy = CoreIdGenerator.ID_GENERATOR, parameters = {
                    @Parameter(name = "tableName", value = TABLE_NAME) })
    @Column(name = "sid", nullable = false, unique = true)
    private Long sid;

    @OneToOne(mappedBy = "myEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private MyEntityInfoImpl info;

    @Column
    private String field;
}

MyEntityInfo:

@Entity
@Table(name = MyEntityInfoImpl.TABLE_NAME)
public class MyEntityInfoImpl {
    public static final String TABLE_NAME = "TMP_MY_ENTITY_INFO";

    @Id
    @Column(name = "SID", nullable = false, unique = true)
    private Long sid;

    @OneToOne(fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
    private MyEntityImpl myEntity;

    @Column(name = "INFO_FIELD")
    private String infoField;
}

我尝试了这个解决方案,但正如我所说,对我来说并没有起作用:

https://stackoverflow.com/questions/17155452/hibernate-lazy-loading-for-reverse-one-to-one-workaround-how-does-this-work

我已经设法使用 @OneToMany 做了一些类似的事情,并且手动管理数据,但这不是我想做的。然而,关于是否可以使用 @OneToOne 实现这一点,或者适合实现这一点的正确设计模式的其他替代方案和信息也是受欢迎的。

PS:如果您想尝试创建 SQL Server 数据库表,以下是表的创建语句:

create table TMP_MY_ENTITY (SID NUMERIC(19,0) NOT NULL, FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY ADD CONSTRAINT PK_TMP_MY_ENTITY PRIMARY KEY CLUSTERED (SID);
go

create table TMP_MY_ENTITY_INFO (SID NUMERIC(19,0) NOT NULL, INFO_FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY_INFO ADD CONSTRAINT PK_TMP_MY_ENTITY_INFO PRIMARY KEY CLUSTERED (SID);
go

CREATE SEQUENCE SEQ_TMP_MY_ENTITY START WITH 1 INCREMENT BY 1 MINVALUE 1 CACHE 20;

alter table TMP_MY_ENTITY_INFO add constraint FK_TMP_MY_ENT_INFO_MY_ENT FOREIGN KEY (SID) references TMP_MY_ENTITY(SID);
go

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 1');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 1');

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 2');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 2');

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 3 no info');

-- DELETE ALL

drop table TMP_MY_ENTITY_INFO;
drop table TMP_MY_ENTITY;
drop sequence SEQ_TMP_MY_ENTITY;
英文:

I'm trying to achieve to have an entity called MyEntity along with another entity called MyEntityInfo using Hibernate 5.3.13.Final with annotations under Wildfly 18.

The idea is to have MyEntity store some commonly requested fields, and MyEntityInfo store some rarely requested fields. Both share the same primary key called SID (Long), and there is a FK from Info's SID to Entity's SID. There can be entities without info.

Normally you will not require the additional info. For example, I don't want the info entity to be fetched when I query my entity like this:

MyEntityImpl entity = em.find(MyEntityImpl.class, 1L);

However, when I run this code, I find that there's a second query, fetching the Info entity along the main one, as in an EAGER behaviour.

I'm mapping the relationship using @OneToOne. I've tried several combinations of FetchType, optional and @LazyToOne, but so far without success.

Here is the code for both MyEntity and MyEntityInfo classes (additional getters and setters removed):

MyEntity (ID generator is a custom sequence generator):

@Entity
@Table(name = MyEntityImpl.TABLE_NAME)
public class MyEntityImpl {

    public static final String TABLE_NAME = "TMP_MY_ENTITY";

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_" +
            TABLE_NAME)
    @GenericGenerator(name = "GEN_" +
            TABLE_NAME, strategy = CoreIdGenerator.ID_GENERATOR, parameters = {
                    @Parameter(name = "tableName", value = TABLE_NAME) })
    @Column(name = "sid", nullable = false, unique = true)
    private Long               sid;

    @OneToOne(mappedBy = "myEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private MyEntityInfoImpl   info;

    @Column
    private String             field;

MyEntityInfo:

@Entity
@Table(name = MyEntityInfoImpl.TABLE_NAME)
public class MyEntityInfoImpl {
    public static final String TABLE_NAME = "TMP_MY_ENTITY_INFO";

    @Id
    @Column(name = "SID", nullable = false, unique = true)
    private Long               sid;

    @OneToOne(fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
    private MyEntityImpl       myEntity;

    @Column(name = "INFO_FIELD")
    private String             infoField;

I've tried this solution, but as I said, it didn't work for me:

https://stackoverflow.com/questions/17155452/hibernate-lazy-loading-for-reverse-one-to-one-workaround-how-does-this-work

I've managed to do something somewhat similar using @OneToMany and managing data manually, but that's not what I'd like to do. However, another alternatives and information on whether this can be achieved or not using @OneToOne, or the right design pattern to do this are also welcome.

PS: Database tables creation for SQL Server, in case you want to try it:

create table TMP_MY_ENTITY (SID NUMERIC(19,0) NOT NULL, FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY ADD CONSTRAINT PK_TMP_MY_ENTITY PRIMARY KEY CLUSTERED (SID);
go

create table TMP_MY_ENTITY_INFO (SID NUMERIC(19,0) NOT NULL, INFO_FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY_INFO ADD CONSTRAINT PK_TMP_MY_ENTITY_INFO PRIMARY KEY CLUSTERED (SID);
go

CREATE SEQUENCE SEQ_TMP_MY_ENTITY START WITH 1 INCREMENT BY 1 MINVALUE 1 CACHE 20;

alter table TMP_MY_ENTITY_INFO add constraint FK_TMP_MY_ENT_INFO_MY_ENT FOREIGN KEY (SID) references TMP_MY_ENTITY(SID);
go

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 1');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 1');

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 2');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 2');

insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 3 no info');

-- DELETE ALL

drop table TMP_MY_ENTITY_INFO;
drop table TMP_MY_ENTITY;
drop sequence SEQ_TMP_MY_ENTITY;

答案1

得分: 0

以下是翻译好的内容:

在跟随 @SternK 的链接并升级至 Wildfly 19 和 Hibernate 5.4.14 之后,通过使用 @MapsId 最终解决了该问题。

正确的映射如下:

MyEntity:

public class MyEntityImpl {

    @OneToOne(mappedBy = "myEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "SID")
    private MyEntityInfoImpl info;

MyEntityInfo:

public class MyEntityInfoImpl {

    @OneToOne(fetch = FetchType.EAGER, optional = false)
    @MapsId
    @JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
    private MyEntityImpl myEntity;
英文:

After following @SternK link, and upgrading to Wildfly 19 and Hibernate 5.4.14, it finally worked by using @MapsId.

The right mapping to use is this:

MyEntity:

public class MyEntityImpl {

    @OneToOne(mappedBy = "myEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "SID")
    private MyEntityInfoImpl   info;

MyEntityInfo:

public class MyEntityInfoImpl {

    @OneToOne(fetch = FetchType.EAGER, optional = false)
    @MapsId
    @JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
    private MyEntityImpl       myEntity;

huangapple
  • 本文由 发表于 2020年4月10日 21:05:28
  • 转载请务必保留本文链接:https://java.coder-hub.com/61140887.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定