Propertyaccessexception при сохранении объекта с помощью @embeddable

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

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

@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: 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: Could not set field value 1 value by reflection : [class com.sample.shared.entity.PeopleStatsId.peopleId] setter of com.sample.shared.entity.PeopleStatsId.peopleId at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:149) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157) at org.hibernate.internal.ExceptionConverterImpl.convert and so on ... 63 more

Я столкнулся с этим аналогичная проблема, с решением, но не работающим. После попытки первого решения, которое создает объект 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);
    }

Теперь он выдает ошибку отсоединенного объекта.

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.sample.Stats at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:807) ... 68 more

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

Вам нужно установить людей и статистику в peoplestats.

K.Nicholas 13.09.2018 23:55

Пожалуйста, дополните. Я не совсем понимаю, что вы имеете в виду. Спасибо.

user1778855 14.09.2018 21:22

Вам необходимо установить поля People и Stats в сущности PeopleStats.

K.Nicholas 14.09.2018 21:44

Я все еще не понимаю. Вы имеете в виду метод установки или? Не могли бы вы показать пример кода? Я загрузил POC в github.

user1778855 14.09.2018 21:49

В SO много такого. stackoverflow.com/questions/50451894/…

K.Nicholas 14.09.2018 22:11

Я добавил новый PeopleStatsId () в свой addStats и теперь выдает мне ошибку обособленного объекта. Когда я добавил новый PeopleStatsId () в класс PeopleStats, похоже, он не делает того же самого. Следовательно, я не знаю об этом. Обновили исходный пост.

user1778855 15.09.2018 09:53
stats(stats) не имеет смысла. Обособленная сущность означает, что вы не участвуете в транзакции.
K.Nicholas 15.09.2018 14:48
0
7
408
2

Ответы 2

Вам необходимо установить peopleStatsId в свой класс PeopleStats. Так что измените строку:

@EmbeddedId
private PeopleStatsId peopleStatsId;

к этому:

@EmbeddedId
private PeopleStatsId peopleStatsId = new PeopleStatsId();

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

Я пробовал это, дает ту же ошибку. Я упоминал об этом в последней части моего исходного сообщения. Любое другое предложение?

user1778855 14.09.2018 21:22

Итак, после помощи @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 id, когда это не требовалось.

Согласно Руководство по гибернации,

detached

the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context)

В своем посте я пытался установить в Stats to People, а затем сохранить это.

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

Я тоже обновил свой poc в github.

Надеюсь, это поможет с тем, кто придет дальше.

Другие вопросы по теме