使用@Embeddable保存实体时的PropertyAccessException

时间:2018-09-13 19:02:14

标签: java spring hibernate junit spring-data-jpa

我正在关注此map a many-to-many association with extra columns tutorial,但并不太成功。

所以我有以下实体...

@Data
@Entity
@Table(name = "PEOPLE")
public class People implements Serializable {

    @Id
    @SequenceGenerator(name = "people", sequenceName = "people_id_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "people")
    private long peopleId;

    private String peopleName;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(
            mappedBy = "people", 
            cascade = CascadeType.ALL, 
            orphanRemoval = true
    )
    private List<PeopleStats> peopleStats;

    public void addStats(Stats stats) {

        if (this.peopleStats == null) {
            this.peopleStats = new ArrayList<>();
        }

        PoepleStats pStats = PoepleStats.builder().people(this).stats(stats).build();

        this.peopleStats.add(pStats);
    }
}

@Data
@Entity
@Table(name = "STATS")
public class Stats implements Serializable {

    @Id
    @SequenceGenerator(name = "stats", sequenceName = "stats_id_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "stats")
    private long statsId;

    private String statsName;
    private String statsDescription;

}

@Data
@Entity
@Table(name = "PEOPLE_STATS")
public class PeopleStats implements Serializable {

    @EmbeddedId
    private PeopleStatsId peopleStatsId;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("peopleId")
    private People people;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("statsId")
    private Stats stats;

    private long value;

}

@Data
@Embeddable
@EqualsAndHashCode
public class PeopleStatsId implements Serializable {

    // Putting @Column(name = "people_id") or not doesn't seem to have any effect
    private long peopleId;
    // Same goes for this
    private long statsId;

}

然后进行以下单元测试。

@RunWith(SpringRunner.class)
@DataJpaTest
public class PeopleRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;


    @Test
    public void testSavePeople() {

        // People object created
        people.addStats(Stats.builder().statsId(new Long(1)).statsName("a").statsDescription("b").build());

        this.entityManager.persistAndFlush(people);
    }
}

hibernate生成的表是这样的:

Hibernate: create table people_stats (value bigint not null, people_people_id bigint not null, stats_stats_id bigint not null, primary key (people_people_id, stats_stats_id))

这是堆栈跟踪。.

  

javax.persistence.PersistenceException:   org.hibernate.PropertyAccessException:无法设置字段值1   反映价值:[类   com.sample.shared.entity.PeopleStatsId.peopleId]的设置   com.sample.shared.entity.PeopleStatsId.peopleId位于   org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:149)     在   org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)     在   org.hibernate.internal.ExceptionConverterImpl.convert   等等... 63更多

我遇到了这个similar issue,找到了解决方法,但是没有用。尝试了第一种解决方案后,即为new PeopleStatsId创建一个@EmbeddedId对象,它引发了同样的错误。

任何人都可以引导我前进吗?谢谢。

更新1:我已经在github上上传了POC。

更新2

public void addStats(Stats stats) {

        if (this.peopleStats == null) {
            this.peopleStats = new ArrayList<>();
        }

        PeopleStats pStats = PeopleStats.builder().peopleStatsId(new PeopleStatsId()).people(this).stats(stats).build();

        this.peopleStats.add(pStats);
    }

现在抛出分离的实体错误。

  

由于:org.hibernate.PersistentObjectException:分离的实体   传递给坚持:com.sample.Stats在   org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)     在   org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:807)     ... 68更多

更新3

我将CascadeType.ALL更改为MERGE,这似乎可以解决问题,但是我不确定为什么会这样。我什至删除了更新2中有关.peopleStatsId(new PeopleStatsId())的部分,它也可以正常工作。现在我更加困惑了。

在人班:

    @OneToMany(
            mappedBy = "people", 
            cascade = CascadeType.MERGE, 
            orphanRemoval = true
    )
    private List<PeopleStats> peopleStats;

    public void addStats(Stats stats) {

        if (this.peopleStats == null) {
            this.peopleStats = new ArrayList<>();
        }

        PeopleStats pStats = PeopleStats.builder().people(this).stats(stats).build();

        this.peopleStats.add(pStats);
    }

1 个答案:

答案 0 :(得分:0)

因此,在@K.Nicholas和研究的帮助下,我认为我已经设法解决了问题并从中学到了新东西。

@K.Nicholas在设置“人员”和“统计”字段方面是正确的,但是起初我不太清楚。无论如何,后来我想到了。

基本上,除People类外,所有其他类几乎保持相同。

@Data
@Entity
@Builder
@Table(name = "PEOPLE")
public class People implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name = "people", sequenceName = "people_id_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "people")
    private long peopleId;

    private String peopleName;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(
            mappedBy = "people", 
            cascade = CascadeType.ALL, 
            orphanRemoval = true
    )
    private List<PeopleStats> peopleStats;

    // Maintain the state of objects association
    public void addStats(Stats stats) {

        if (this.peopleStats == null) {
            this.peopleStats = new ArrayList<>();
        }

        // Important to ensure that a new instance of PeopleStatsId is being passed into the field
        // otherwise, PropertyAccessException will be encountered
        PeopleStats pStats = PeopleStats.builder()
                                .people(this)
                                .stats(stats)
                                .peopleStatsId(new PeopleStatsId(this.getPeopleId(), stats.getStatsId()))
                                .build();

        this.peopleStats.add(pStats);
    }

}

记下addStats方法中的注释,在此方法中,我需要传递PeopleStatsId对象的新实例以初始化PeopleStatsId对象,而该对象首先应该这样做除了不是。经验教训。

我还提到我以前曾遇到过独立实体的问题。这是因为我试图在不需要时在“统计信息ID”字段中进行设置。

根据Hibernate Guide

  

已分离

     

该实体具有关联的标识符,但不再与持久性上下文关联(通常是因为持久性   上下文已关闭或实例已从上下文中撤出)

在我的帖子中,我试图将“统计数据”设置为“人”,然后将其保留。

@Test
    public void testSavePeople() {

        // People object created
        people.addStats(Stats.builder().statsId(new Long(1)).statsName("a").statsDescription("b").build());

        this.entityManager.persistAndFlush(people);
    }

.statsId(new Long(1))就是问题所在,因为由于存在ID,它被视为独立实体。 CascadeType.MERGE在这种情况下可行是因为我认为由于saveOrUpdate功能?无论如何,如果不设置statsId,CascadeType.ALL就可以正常工作。

单元测试(工作中)的示例:

@Test
    public void testSavePeopleWithOneStats() {

        // Creates People entity
        People people = this.generatePeopleWithoutId();
        // Retrieve existing stats from StatsRepository
        Stats stats = this.statsRepository.findById(new Long(1)).get();
        // Add Stats to People
        people.addStats(stats);
        // Persist and retrieve
        People p = this.entityManager.persistFlushFind(people);

        assertThat(p.getPeopleStats().size()).isEqualTo(1);
    }

我有一个data-h2.sql脚本,它在单元测试开始时就加载了Stats数据,这就是为什么我可以从statsRepository中检索它的原因。

我还更新了poc in github

希望这对接下来的人有帮助。