使用泛型的 HashMap 上的 JPA @ElementCollection 返回重复项

发布于 2025-01-11 08:59:23 字数 6070 浏览 0 评论 0原文

这让我很不舒服。

问题陈述:

从数据库获取 Marklar 时,返回时会出现重复的 Foo。具体来说,bars HashMap 中有多少个 Foo 就有多少个元素。例如,如果我在 FooCollection 中保存一个带有 one FooMarklar,并且如果 bars 是:

{0, "data1", 1, "data2", 2, "data3"}

当我读回它时,我将在 FooCollection 中得到三个 Foo。到底是怎么回事?

定义:

我有以下类结构:

   Foo<T>
     ^
     |
     | 1:M
FooContainer
     ^
     |
     | 1:1
<<Marklar>>

Marklar有一个FooCollection,其中有几个FooFoo 是一个泛型类,定义为:

@Getter
@Setter
@ToString
@Entity
public class Foo<T> {

  @Id @GeneratedValue private Long id;

  @ElementCollection(fetch = FetchType.EAGER)
  private final Map<Integer, String> bars = new HashMap<>();

  @Type(type = "java.lang.Class")
  private final Class<T> clazz;

  @Column(columnDefinition = "LONGTEXT")
  @Convert(converter = PatternConverter.class)
  private Pattern pattern;

  public void addBar(Integer key, String bar) {
    bars.put(key, bar);
  }

  public Foo(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Foo() {
    this.clazz = null;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Foo)) return false;
    Foo<?> foo = (Foo<?>) o;
    return Objects.equals(getId(), foo.getId())
        && Objects.equals(getBars(), foo.getBars())
        && Objects.equals(getClazz(), foo.getClazz())
        && Objects.equals(getPattern(), foo.getPattern());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getBars(), getClazz(), getPattern());
  }
}

问题陈述:

从数据库获取 Marklar 时,返回时会出现重复的 Foo 。具体来说,bars HashMap 中有多少个 Foo 就有多少个元素。例如,如果我在 FooCollection 中保存一个带有 one FooMarklar,并且如果 bars 是:

{0, "data1", 1, "data2", 2, "data3"}

当我读回它时,我会在 FooContainer 中得到三个 Foo。到底是怎么回事?

定义:

我有以下类结构:

   Foo<T>
     ^
     |
     | 1:M
FooCollection
     ^
     |
     | 1:1
<<Marklar>>

Marklar有一个FooContainer,其中有几个FooFoo 是一个泛型类,定义为:

@Getter
@Setter
@ToString
@Entity
public class Foo<T> {

  @Id @GeneratedValue private Long id;

  @ElementCollection(fetch = FetchType.EAGER)
  private final Map<Integer, String> bars = new HashMap<>();

  @Type(type = "java.lang.Class")
  private final Class<T> clazz;

  @Column(columnDefinition = "LONGTEXT")
  @Convert(converter = PatternConverter.class)
  private Pattern pattern;

  public void addBar(Integer key, String bar) {
    bars.put(key, bar);
  }

  public Foo(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Foo() {
    this.clazz = null;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Foo)) return false;
    Foo<?> foo = (Foo<?>) o;
    return Objects.equals(getId(), foo.getId())
        && Objects.equals(getBars(), foo.getBars())
        && Objects.equals(getClazz(), foo.getClazz())
        && Objects.equals(getPattern(), foo.getPattern());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getBars(), getClazz(), getPattern());
  }
}

FooContainer

@Getter
@Setter
@ToString
@Entity
public class FooContainer {

  @Id @GeneratedValue private Long id;

  @OneToMany(targetEntity = Foo.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
  private final List<Foo> foos = new ArrayList<>();

  public void addFoo(Foo foo) {
    foos.add(foo);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof FooContainer)) return false;
    FooContainer that = (FooContainer) o;
    return Objects.equals(getId(), that.getId()) && Objects.equals(getFoos(), that.getFoos());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getFoos());
  }
}

MarklarsMarklarRepository 中获取:

@Repository
public interface MarklarRepository extends JpaRepository<Marklar<?>, UUID> {}

A Marklar 是一个抽象类:

@Getter
@Setter
@Entity
@ToString
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Marklar<T> {

    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    private UUID id;

    @Convert(converter = UriConverter.class)
    private final URI baseUri;

    @Type(type = "java.lang.Class")
    private final Class<T> clazz;

    private boolean enabled = true;

    public Marklar() {
        this.baseUri = null;
        this.clazz = null;
    }

    public Marklar(@NotNull URI baseUri, @NotNull Class<T> clazz) {
        this.baseUri = baseUri;
        this.clazz = clazz;
    }    
}

作为 DefaultMarklar 实现:

@Getter
@Setter
@ToString(callSuper = true)
@Entity
@RequiredArgsConstructor
public final class DefaultMarklar<T> extends Marklar<T> {

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private final FooContainer fooContainer;

    public DefaultMarklar() {
        super();
        this.fooContainer = null;
    }

    public DefaultMarklar(
            @NotNull URI baseUri, @NotNull Class<T> clazz, @NotNull FooContainer fooContainer) {
        super(baseUri, clazz);
        this.fooContainer = fooContainer;
    }
}

This one is giving me fits.

Problem Statement:

When fetching a Marklar from the database, it is returned with duplicated Foos. Specifically, there are as many Foos as there are elements in the bars HashMap. For example, if I am saving a Marklar with one Foo in its FooCollection, and if bars is:

{0, "data1", 1, "data2", 2, "data3"}

I will get three Foos in the FooCollection when I read it back. What is going on?

Definitions:

I have the following class structure:

   Foo<T>
     ^
     |
     | 1:M
FooContainer
     ^
     |
     | 1:1
<<Marklar>>

A Marklar has a FooCollection, which has several Foos. A Foo is a generic class defined as:

@Getter
@Setter
@ToString
@Entity
public class Foo<T> {

  @Id @GeneratedValue private Long id;

  @ElementCollection(fetch = FetchType.EAGER)
  private final Map<Integer, String> bars = new HashMap<>();

  @Type(type = "java.lang.Class")
  private final Class<T> clazz;

  @Column(columnDefinition = "LONGTEXT")
  @Convert(converter = PatternConverter.class)
  private Pattern pattern;

  public void addBar(Integer key, String bar) {
    bars.put(key, bar);
  }

  public Foo(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Foo() {
    this.clazz = null;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Foo)) return false;
    Foo<?> foo = (Foo<?>) o;
    return Objects.equals(getId(), foo.getId())
        && Objects.equals(getBars(), foo.getBars())
        && Objects.equals(getClazz(), foo.getClazz())
        && Objects.equals(getPattern(), foo.getPattern());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getBars(), getClazz(), getPattern());
  }
}

Problem Statement:

When fetching a Marklar from the database, it is returned with duplicated Foos. Specifically, there are as many Foos as there are elements in the bars HashMap. For example, if I am saving a Marklar with one Foo in its FooCollection, and if bars is:

{0, "data1", 1, "data2", 2, "data3"}

I will get three Foos in the FooContainer when I read it back. What is going on?

Definitions:

I have the following class structure:

   Foo<T>
     ^
     |
     | 1:M
FooCollection
     ^
     |
     | 1:1
<<Marklar>>

A Marklar has a FooContainer, which has several Foos. A Foo is a generic class defined as:

@Getter
@Setter
@ToString
@Entity
public class Foo<T> {

  @Id @GeneratedValue private Long id;

  @ElementCollection(fetch = FetchType.EAGER)
  private final Map<Integer, String> bars = new HashMap<>();

  @Type(type = "java.lang.Class")
  private final Class<T> clazz;

  @Column(columnDefinition = "LONGTEXT")
  @Convert(converter = PatternConverter.class)
  private Pattern pattern;

  public void addBar(Integer key, String bar) {
    bars.put(key, bar);
  }

  public Foo(Class<T> clazz) {
    this.clazz = clazz;
  }

  public Foo() {
    this.clazz = null;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Foo)) return false;
    Foo<?> foo = (Foo<?>) o;
    return Objects.equals(getId(), foo.getId())
        && Objects.equals(getBars(), foo.getBars())
        && Objects.equals(getClazz(), foo.getClazz())
        && Objects.equals(getPattern(), foo.getPattern());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getBars(), getClazz(), getPattern());
  }
}

FooContainer:

@Getter
@Setter
@ToString
@Entity
public class FooContainer {

  @Id @GeneratedValue private Long id;

  @OneToMany(targetEntity = Foo.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
  private final List<Foo> foos = new ArrayList<>();

  public void addFoo(Foo foo) {
    foos.add(foo);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof FooContainer)) return false;
    FooContainer that = (FooContainer) o;
    return Objects.equals(getId(), that.getId()) && Objects.equals(getFoos(), that.getFoos());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId(), getFoos());
  }
}

Marklars are fetched from the MarklarRepository:

@Repository
public interface MarklarRepository extends JpaRepository<Marklar<?>, UUID> {}

A Marklar is an abstract class:

@Getter
@Setter
@Entity
@ToString
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Marklar<T> {

    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    private UUID id;

    @Convert(converter = UriConverter.class)
    private final URI baseUri;

    @Type(type = "java.lang.Class")
    private final Class<T> clazz;

    private boolean enabled = true;

    public Marklar() {
        this.baseUri = null;
        this.clazz = null;
    }

    public Marklar(@NotNull URI baseUri, @NotNull Class<T> clazz) {
        this.baseUri = baseUri;
        this.clazz = clazz;
    }    
}

Implemented as a DefaultMarklar:

@Getter
@Setter
@ToString(callSuper = true)
@Entity
@RequiredArgsConstructor
public final class DefaultMarklar<T> extends Marklar<T> {

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private final FooContainer fooContainer;

    public DefaultMarklar() {
        super();
        this.fooContainer = null;
    }

    public DefaultMarklar(
            @NotNull URI baseUri, @NotNull Class<T> clazz, @NotNull FooContainer fooContainer) {
        super(baseUri, clazz);
        this.fooContainer = fooContainer;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

無心 2025-01-18 08:59:23

按照 @Tomas 的建议,将获取类型从 Lazy 更改为 Easy 可以解决重复问题,但也会影响结果。

如果您想将 EagerType 保留为 EAGER,只需指定 FechMode 为 Select:

@ElementCollection(fetch = FetchType.EAGER)    
@Fetch(FetchMode.SELECT)

Changing the Fetching type from Lazy to Easy as @Tomas recommended solves the duplication, however it also impact the results.

If you want to keep the EagerType to EAGER, just specify the FechMode to Select:

@ElementCollection(fetch = FetchType.EAGER)    
@Fetch(FetchMode.SELECT)
白龙吟 2025-01-18 08:59:23

问题在于 Hibernate 将 bars 视为一个单独的包,同时急切地获取多个包可能会导致诸如此类的基数问题。

我从 Bars 地图中删除了 fetch = FetchType.EAGER,并添加了
@Transactional 对调用方法进行注释,一切都很好。此外,我还将对延迟加载字段(例如在 toString() 方法中)的任何直接引用替换为对其各自 getter 的调用。

The problem was that bars is considered a separate bag by Hibernate, and eager fetching of multiple bags simultaneously can result in cardinality problems such as this.

I removed fetch = FetchType.EAGER from the Bars map, and added the
@Transactional annotation to the calling method, and all is well. Also, I replaced any direct references to lazily loaded fields (such as in toString() methods) with calls to their respective getters.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文