Обновление вызова Hibernate для дочерней сущности, тогда как вставка для родителя во время операции save () - PullRequest
1 голос
/ 11 апреля 2020

TL; DR

Для OneToMany сопоставления между Note и Tag с использованием третьей таблицы Note_tag, save() не может сохранить Note сущность.

Справочная информация:

Итак, я работал над этим NoteApp ( Место репозитория Github ), которое сохраняет примечание с заголовком, описанием, статусом и тегом (тег является строковым значением). В качестве обновления функции я подумал добавить несколько тегов для заметки. Для этого я создал таблицу тегов и третью таблицу ассоциаций тега и заметки с использованием аннотации faily straigt forward @JoinTable. Эта функция привела меня к вышеупомянутой проблеме при сохранении сущности Note.

Что я использую за экраном:

Java 1.8, Hiberanate, SpringBoot

Подробнее о техническом стеке на здесь

То, что у меня уже работает:

Save() Note без Tag с.

My Note. java:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "T_NOTE")
public class Note implements Serializable {

    private static final long serialVersionUID = -9196483832589749249L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="ID")
    private Integer id;

    @Column(name="TITLE")
    private String title;

    @Column(name="DESCRIPTION")
    private String description;

    @Column(name = "LAST_UPDATED_DATE")
    private Date lastUpdatedDate;

    @Column(name="STATUS")
    private String status;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "T_NOTE_TAG", joinColumns = { @JoinColumn(name="NOTE_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
            @JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID") })
    private List<Tag> tags = new ArrayList<Tag>();
    /** getters setters omitted for brevity**/
}

My Tag. java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "T_TAG")
public class Tag implements Serializable {

    private static final long serialVersionUID = -2685158076345675196L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="TAG_ID")
    private Integer tagId;

    @Column(name="TAG_NAME")
    private String tagName;
}

My NoteDto. java:

public class NoteDto {

    private int id;
    private String title;
    private String description;
    private List<TagDto> tags = new ArrayList<TagDto>();
    private Date lastUpdatedDate;
    private String status;
}

My TagDto. java:

public class TagDto {

    private int tagId;
    private String tagName;
}

Запросы на создание моей таблицы:

CREATE database if NOT EXISTS noteApp;

CREATE TABLE `T_NOTE` (
    `id` int(11) unsigned NOT NULL auto_increment,
    `description` varchar(20) NOT NULL DEFAULT '',
    `header` varchar(20) NOT NULL,
    `status` varchar(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `T_TAG` (
    `tag_id` int unsigned not null auto_increment,
    `tag_name` varchar(30) not null,
    PRIMARY KEY(TAG_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `T_NOTE_TAG` (
    `note_tag_id` int unsigned not null auto_increment,
    `note_id` int unsigned not null,
    `tag_id` int unsigned not null,
    CONSTRAINT note_tag_note foreign key (`note_id`) references T_NOTE(`id`),
    CONSTRAINT note_tag_tag foreign key (`tag_id`) references T_TAG(`tag_id`),
    CONSTRAINT note_tag_unique UNIQUE (`tag_id`, `note_id`),
    PRIMARY KEY (`note_tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

use noteApp;
ALTER TABLE T_NOTE
Change header title varchar(1000) NOT NULL;

ALTER TABLE T_NOTE
ADD COLUMN last_updated_date timestamp AFTER status;

Журналы ошибок:

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: update T_TAG set TAG_NAME=? where TAG_ID=?
2020-04-11 18:02:40.647 ERROR 3614 --- [nio-8080-exec-1] o.h.i.ExceptionMapperStandardImpl        : HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]]
org.springframework.orm.jpa.JpaOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]; nested exception is javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:396)
    at org.springframework.orm.jpa.DefaultJpaDialect.translateExceptionIfPossible(DefaultJpaDialect.java:127)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.

Исключение подсказывает мне этот тег обновляется неправильно, что вызывает вопрос: почему он вызывает обновление в первую очередь для Tag, тогда как вставка корректно вызывается для Note? Я пытался найти решения, прежде чем опубликовать этот вопрос, но не смог найти ни одного. Заранее спасибо!

Ответы [ 3 ]

1 голос
/ 11 апреля 2020

Обновление вызовов Hibernate для детей при вставке родительского элемента, поскольку вы использовали однонаправленный @OneToMany вместо @ManyToOne или двунаправленный. независимо от того, что вы делаете, Hibernate моделирует это как OneToMany. Итак, Hibernate вставляет ваш родительский и дочерний элементы, а затем вызывает update для дочерних элементов, чтобы добавить внешний родительский ключ в дочерние таблицы. Теперь о том, почему вы получили исключение, как я уже сказал, вызовы Hibernate обновляются на многих сторонах ваших отношений, и, поскольку поведение по умолчанию имеет значение null, установленное на true, это вызывает вашу ошибку. Помните, что вы делаете однонаправленное отображение, поэтому об этом знает только одна сторона отношений. Пожалуйста, избегайте однонаправленных отношений OneToMany. Вы можете найти много замечательных статей по этому вопросу на сайте Влада Михалчеа. Он мастер зимней спячки. Установите для nullable значение false, и это должно решить вашу проблему.

1 голос
/ 11 апреля 2020

Редактировать

Убедитесь, что вы обновили теги перед сохранением заметки. Также убедитесь, что сервисный метод saveNote в NoteServiceImpl также помечен @Transactional, так что откат будет инициирован в случае прерывания запроса по какой-либо причине. Мне удалось воспроизвести проблему, с которой вы столкнулись, и guardian прав насчет того, как Hibernate справляется с такими ситуациями. Вы не можете сохранить данные в нескольких таблицах с помощью одной команды SQL (это связано с атомарностью), но вы достигаете этого с помощью транзакций. Здесь является ответом фантазии c, связанным с этим.

В вашем коде вам нужно изменить метод saveNote в NoteDaoImpl, если вы все еще хотите сохранить теги при сохранении данных в T_NOTE.

    public void saveNote(Note note) {
        try {
            Session session = this.sessionFactory.getCurrentSession();
            for(Tag tag: note.getTags()){
                session.saveOrUpdate(tag);
            }
            session.save(note);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Единственное, что я хотел бы предложить, это изменить tags с List на Set, чтобы избежать повторения данных.

После внесения вышеуказанных изменений к коду, вы должны иметь возможность делать успешные вызовы POST и GET, как показано ниже:

enter image description here

0 голосов
/ 12 апреля 2020

Update вызывается из-за несоответствия типа данных идентификатора в моем DTO и объекте (примечание и тег). Поскольку это был метод save(), я использовал int в NoteDTO и упакованный тип Integer в Note Entity. Таким образом, когда я передавал полезную нагрузку без id, входной объект возвращался к значению по умолчанию 0. Это 0 при преобразовании в сущность Note передавало 0 вместо null. Таким образом, для дочерней записи Tag значение id было равно 0, и, следовательно, hibernate мог прибегнуть к update(), увидев ненулевое значение для этого дочернего элемента в ее стратегии выполнения.

Я подтвердил, закрыв NoteDto идентификатор Integer, и это создало правильные запросы. Ниже была моя полезная нагрузка:

{
"title": "A Java SpringBoot note",
"description": "Desc for the note",
"lastUpdatedDate": "1486696408000",
"status": "New",
"tags": [
    {
    "tagName": "SpringBoot"
    }
]
}

В TagDto:

public class TagDto {

    private Integer tagId; ----> changed from int
    private String tagName;
}

В NoteDto:

public class NoteDto {

    private Integer id; ----> changed from int
    private String title;
}

В NoteDaoImpl,

Session session = this.sessionFactory.getCurrentSession();
session.save(note);

Журналы после успеха:

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: insert into T_TAG (TAG_NAME, TAG_ID) values (?, ?)
Hibernate: insert into T_NOTE_TAG (NOTE_ID, TAG_ID) values (?, ?)

Я не пометил @kavitha-karunakaran's как ответ как сохранение Tag отдельно, это не то, что я намереваюсь сделать, и это не подойдет для чистого подхода.

Я не сделал Отметить @guardian's ответ как переход к лучшему подходу было не моей целью на данный момент, а исправить мой текущий подход однонаправленного картирования.

Спасибо вам обоим за предложения! Ура!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...