PropertyAccessException при сохранении сущности с помощью @Embeddable - PullRequest
0 голосов
/ 13 сентября 2018

Я слежу за этой сопоставлением ассоциации «многие ко многим» с учебником по дополнительным столбцам , но не вполне успешно.

Итак, у меня есть следующие сущности ...

@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 значение с помощью отражения: [class 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.jpg).hibernate.internal.ExceptionConverterImpl.convert и т. д. ... еще 63

Я сталкивался с этой аналогичной проблемой , но с решением, но не работающим.После попытки первого решения, которое создает объект new PeopleStatsId для @EmbeddedId, оно вызывает ту же ошибку.

Кто-нибудь может мне помочь?Спасибо.

Обновление 1: я загрузил POC на github .

Обновление 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);
    }

Itтеперь выдает ошибку обособленной сущности.

Вызывается: org.hibernate.PersistentObjectException: отсоединенная сущность передается в persist: com.sample.Stats в org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java: 124) at 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);
    }

Ответы [ 2 ]

0 голосов
/ 15 сентября 2018

Так что после помощи от @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, который должен был быть сделан в первую очередь, за исключением того, что это не так.Извлеченный урок.

Я также упомянул, что ранее сталкивался с вопросом об отделенной сущности.Это было потому, что я пытался установить в поле идентификатора Stats, когда это не требовалось.

В соответствии с 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)), поскольку она рассматривалась как отдельная сущность, поскольку существовал идентификатор.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.

Я также обновил свой документ в github .

Надеюсь, это поможет любому, кто будет следующим.

0 голосов
/ 14 сентября 2018

Вам нужно указать peopleStatsId в вашем классе PeopleStats.Поэтому измените строку:

@EmbeddedId
private PeopleStatsId peopleStatsId;

на эту:

@EmbeddedId
private PeopleStatsId peopleStatsId = new PeopleStatsId();

Hibernate пытается установить поля PeopleStatsId, но экземпляр равен нулю, поэтому выбрасывается исключение NullPointerException.

...