java 带有泛型的HashMap上的JPA @ElementCollection返回重复项

4xrmg8kj  于 2023-09-29  发布在  Java
关注(0)|答案(2)|浏览(94)

这个让我很不舒服。

问题陈述:

从数据库中获取Marklar时,返回的是重复的Foo。具体来说,Foo s的数量与bars HashMap中的元素数量一样多。例如,如果我保存一个Marklar,在它的FooCollection中有oneFoo,如果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 s。具体来说,Foo s的数量与bars HashMap中的元素数量一样多。例如,如果我保存一个Marklar,在它的FooCollection中有oneFoo,如果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());
  }
}

Marklars是从MarklarRepository中获取的:

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

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;
    }
}
9fkzdhlc

9fkzdhlc1#

问题是bars被Hibernate认为是一个单独的包,同时急切地获取多个包可能会导致这样的基数问题。
我从BarsMap中删除了fetch = FetchType.EAGER,并在调用方法中添加了@Transactional注解,一切都很好。另外,我将对延迟加载字段的直接引用(比如在toString()方法中)替换为对它们各自getter的调用。

ih99xse1

ih99xse12#

按照@Tomas的建议,将获取类型从Lazy更改为Easy解决了重复问题,但它也会影响结果。
如果你想保持EagerType为EAGER,只需指定FechMode选择:

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

相关问题