Як виправити хибернативний "об'єкт посилається на незбережений перехідний екземпляр - збережіть перехідний екземпляр перед змивом" помилка


610

Коли я зберігаю об'єкт за допомогою Hibernate, я отримую таку помилку

object references an unsaved transient instance - save the transient instance before flushing

1
У контексті хочу ця помилка? це з перехідною змінною чи без неї?
vijay

Відповіді:


802

Ви повинні включити cascade="all"(якщо ви використовуєте xml) або cascade=CascadeType.ALL(якщо ви використовуєте анотації) у своєму зіставленні колекції.

Це відбувається тому, що у вас є колекція у вашій сутності, і ця колекція містить один або кілька елементів, яких немає в базі даних. Вказавши вищенаведені параметри, ви скажете сплячому режиму зберегти їх у базі даних при збереженні батьків.


7
Це не неявно? Чи не завжди ви хотіли, щоб Зимова сплячка врятувала їх?
Маркус Леон

29
@Marcus - ні, це не так. Ви можете обробити їх вручну.
Божо

5
Божо має рацію. У мене виникли обставини, коли у мене були колекції, якими я хотів керувати вручну через їх розмір або через ділові правила, які не дозволяють зберегти всі об'єкти в колекції одночасно.
Алекс Маршалл

26
Це трапляється не лише для колекцій, а й для простого відображення в одному
Себастьян Лорбер

12
Не було б краще почати з CascadeType.PERSIST і використовувати зберегти для збереження?
Сергій Шевчик

248

Я вважаю, що це може бути просто повторна відповідь, але просто для уточнення я отримав це як на @OneToOneкартографуванні, так і на @OneToMany. В обох випадках справа в тому, що Childоб’єкт, який я додав до, Parentще не був збережений у базі даних. Отже, коли я додав Childдо Parent, а потім збереженого Parent, Hibernate буде кидати "object references an unsaved transient instance - save the transient instance before flushing"повідомлення при збереженні батьків.

Додавання в cascade = {CascadeType.ALL}на Parent'sпосилання на Childвирішити цю проблему , в обох випадках. Це врятувало Childі Parent.

Вибачте за будь-які повторні відповіді, просто хотіли детальніше уточнити для людей.

@OneToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
    return performanceLog;
}

7
Що робити, якщо я не хочу каскадно економити на відносинах @OneToOne? Створюючи обидва об'єкти вперше, як я можу зберегти будь-яку базу даних, не викликаючи виняток?
xtian

4
Що робити, якщо я хочу врятувати дитину в деяких випадках, а не в деяких інших?
Омаручан

@xtian: Ну тоді вам слід подбати про правильний порядок збереження в базі даних, зберігаючи об’єкти за допомогою EntityManager. В основному ви просто говорите em.persist (object1); em.persist (object2); і т. д.
kaba713

Цю проблему я отримав спеціально, коли я використовував @Inheritance, у цьому випадку TABLE_PER_CLASS я посилався на підклас. CascadeType.ALL виправили це.
Jim ReesPotter

Або ви створили об'єкт сутності за допомогою new MyEntity(не синхронізувавши його до бази даних - промивання), замість того, щоб отримувати його синхронізований примірник із бази даних. Здійснення запитів у сплячому режимі за допомогою цього примірника повідомляє про те, що те, що ви очікуєте знаходитись у базі даних, відрізняється від того, що є у вашій пам'яті програми. У цьому випадку - просто синхронізуйте / отримайте інстанцію від вашої сутності з БД та використовуйте її. Тоді CascadeType.ALL не потрібен.
Зон

66

Це відбувається під час збереження об’єкта, коли Hibernate вважає, що потрібно зберегти об'єкт, пов’язаний із тим, який ви зберігаєте.

У мене була ця проблема, і я не хотів зберігати зміни на посилається об'єкт, тому я хотів, щоб тип каскаду був НІКОЛИ.

Трюк полягає в тому, щоб забезпечити, щоб ідентифікатор та VERSION у посилається об'єкт встановлені таким чином, щоб Hibernate не думав, що посилається об'єкт є новим об'єктом, який потребує збереження. Це працювало для мене.

Перегляньте всі відносини класу, який ви зберігаєте, щоб опрацювати пов'язані об'єкти (та пов'язані з ними об’єкти асоційованих об’єктів) та переконайтесь, що ідентифікатор та VERSION встановлені у всіх об'єктах дерева об'єктів.


4
Цей коментар поставив мене на правильний шлях. Я призначив новий примірник батька у власність його дитини. Тож NH вважав, що це різні випадки.
Елвін

2
Так. Це відбувається, якщо, наприклад, ідентифікатор асоційованого об'єкта не включений (наприклад, його ігнорував @JsonIgnore). Hibernate не має можливості ідентифікувати асоційовану сутність, тому хоче зберегти її.
Рорі Штумпф

36

Вступ

Як я пояснив у цій статті, використовуючи JPA та Hibernate, суб'єкт може перебувати в одному з наступних 4 штатів:

  • Новий - Новостворений об’єкт, який ніколи не асоціювався з сплячим сеансом (він же "Контекст стійкості") і не відображається в жодному рядку таблиці баз даних, вважається в новому або перехідному стані.

    Щоб зберегтись, нам потрібно або явно викликати persistметод, або скористатися механізмом транзитивної стійкості.

  • Постійний - Стійкий об'єкт асоціюється з рядком таблиці баз даних і ним керує поточний контекст постійності.

    Будь-які зміни, внесені до такої сутності, будуть виявлені та розповсюджені до бази даних (під час часу сесії).

  • Від'єднано - Після закриття поточного контексту постійності всі раніше керовані об'єкти відключаються. Послідовні зміни більше не відстежуються, і автоматична синхронізація бази даних не відбуватиметься.

  • Видалено - Хоча JPA вимагає, щоб лише керовані об'єкти було дозволено видаляти, Hibernate також може видаляти відокремлені об'єкти (але лише за допомогою removeвиклику методу).

Переходи сутності держави

Щоб перемістити об'єкт з одного стану в інший, ви можете використовувати persist, removeабо mergeметоди.

Суб'єкти державних установ JPA

Виправлення проблеми

Питання, яке ви описуєте у своєму запитанні:

object references an unsaved transient instance - save the transient instance before flushing

викликано асоціацією суб'єкта в штаті New з об'єктом, який знаходиться в штаті керованого .

Це може статися, коли ви асоціюєте дочірню сутність з колекцією, яка складається з кількох осіб у материнській сутності, і колекція не cascadeпереходить до стану сутності.

Отже, як я пояснив у цій статті , ви можете це виправити, додавши каскад до асоціації об'єктів, яка спровокувала цю помилку, таким чином:

@OneToOneасоціація

@OneToOne(
    mappedBy = "post",
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private PostDetails details;

Помітьте CascadeType.ALLзначення, яке ми додали для cascadeатрибута.

@OneToManyасоціація

@OneToMany(
    mappedBy = "post", 
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();

Знову ж таки, CascadeType.ALLпідходить для двонаправлених@OneToMany асоціацій.

Тепер, щоб каскад працював належним чином у двонаправленому напрямку, вам також потрібно переконатися в синхронізації батьківських та дочірніх асоціацій.

Перегляньте цю статтю, щоб отримати докладнішу інформацію про те, який найкращий спосіб досягти цієї мети.

@ManyToManyасоціація

@ManyToMany(
    mappedBy = "authors",
    cascade = {
        CascadeType.PERSIST, 
        CascadeType.MERGE
    }
)
private List<Book> books = new ArrayList<>();

В @ManyToManyасоціації ви не можете використовувати CascadeType.ALLабо orphanRemovalоскільки це поширюватиме перехід стану видалення сутності від одного батьківського до іншого материнського об'єкта.

Тому для @ManyToManyасоціацій ви зазвичай каскадуєте операції CascadeType.PERSISTабо CascadeType.MERGE. Можна також розширити це до DETACHабо REFRESH.

Щоб отримати докладніші відомості про найкращий спосіб відображення @ManyToManyасоціації, ознайомтесь і з цією статтею .


Повне та послідовне пояснення! Гарна робота!
Холодний

Ви можете знайти сотні таких докладних пояснень у моєму підручнику зі сну .
Влад Михальча

30

Або, якщо ви хочете використовувати мінімальні "повноваження" (наприклад, якщо ви не хочете каскадного видалення), щоб досягти того, що вам потрібно,

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

...

@Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;

11
Це має бути прийнятою відповіддю. CascadeType.ALL ist too
wide

5
Як і в режимі Hibernate 5.2.8, схоже, немає жодного способу досягти такого ж ефекту з анотаціями JPA. Наприклад, @ManyToMany(cascade={PERSIST, MERGE, REFRESH, DETACH})(всі, крім REMOVE) не каскадує оновлення, як це CascadeType.SAVE_UPDATEробить Hibernate .
jcsahnwaldt повідомляє GoFundMonica

25

У моєму випадку це було спричинене відсутністю CascadeTypeна @ManyToOneстороні двосторонніх відносин. Якщо бути точнішим, я був CascadeType.ALLна @OneToManyбоці і не мав його @ManyToOne. Додано CascadeType.ALLдо @ManyToOneвирішення проблеми. Сторона " один на багато ":

@OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;

Багато в одну сторону (спричинило проблему)

@ManyToOne
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

Багато в одному (виправлено додаванням CascadeType.PERSIST)

@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

Якщо я це роблю так і зберігаю материнську сутність, то, що відбувається, є два вставки в моїй батьківській таблиці, це два ряди. Я думаю, що це тому, що у нас є каскад як для батьків, так і для дітей?
theprogrammer

18

Це сталося для мене, коли зберігалася сутність, у якій наявна запис у базі даних мала значення NULL для поля, позначеного за допомогою @Version (для оптимістичного блокування). Оновлення значення NULL до 0 у базі даних виправило це.


Це має бути новим питанням, і його слід додати як помилку, принаймні хибне виняток. Це виявилося причиною мого питання.
BML

Це вирішує мою проблему
Яд Чахін

11

Це не єдина причина помилки. Я зіткнувся з цим щойно за помилку помилки в моєму кодуванні, яка, на мій погляд, встановила значення сутності, яка вже була збережена.

X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);

Я виявив помилку, виявивши, яка саме змінна спричинила помилку (в даному випадку String xid). Я використав catchнавколо цілого блоку коду, який врятував об'єкт і надрукував сліди.

{
   code block that performed the operation
} catch (Exception e) {
   e.printStackTrace(); // put a break-point here and inspect the 'e'
   return ERROR;
}

1
Схожа проблема з моєю. Afterall, коли я перезавантажував об'єкт на локальному рівні, встановлював властивість, а потім зберігав, він працював чудово.
CsBalazsHungary

8

Не використовуйте, Cascade.Allпоки вам справді не доведеться. Roleі Permissionмають двостороннє manyToManyвідношення. Тоді наступний код буде добре працювати

    Permission p = new Permission();
    p.setName("help");
    Permission p2 = new Permission();
    p2.setName("self_info");
    p = (Permission)crudRepository.save(p); // returned p has id filled in.
    p2 = (Permission)crudRepository.save(p2); // so does p2.
    Role role = new Role();
    role.setAvailable(true);
    role.setDescription("a test role");
    role.setRole("admin");
    List<Permission> pList = new ArrayList<Permission>();
    pList.add(p);
    pList.add(p2);
    role.setPermissions(pList);
    crudRepository.save(role);

тоді як якщо об’єкт просто "новий", то він би видав ту саму помилку.


1
це на 100% вірно, Cascade.All - це ліниве рішення, і застосовувати його потрібно лише за потреби. по-перше, якщо сутність вже існує, перевірте, чи вона завантажена в поточному менеджері сутності, чи не завантажується.
Ренато Мендес

7

Якщо ваша колекція є нульовою, просто спробуйте: object.SetYouColection(null);


Це було абсолютно моїм питанням. Я ніколи б не здогадувався, що треба вручну встановити його на нуль.
Дедрон

Це також було моїм питанням. Я не використовував колекцію, тому спершу не спробував цього, але встановив об’єкт на нуль і тепер він працює.
Корм

5

Щоб додати свої 2 центи, я отримав цю саму проблему, коли випадково надсилаю nullідентифікатор. Нижче в коді зображений мій сценарій (а в ОП не було вказано жодного конкретного сценарію) .

Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);

Тут я встановлюю існуючий ідентифікатор відділу для нового екземпляра співробітника, фактично не отримуючи спочатку об'єкт відділу, оскільки я не хочу, щоб інший вибір запиту був запущений.

У деяких сценаріях deptIdPKID стає такимnull від методу виклику, і я отримую ту ж помилку.

Отже, слідкуйте за nullзначеннями для ідентифікатора ПК


що робити, якщо її нульова?
vipin cp

У мене є аналогічна проблема. Я отримую виняток, коли мій deptID дорівнює 0. Будь-яке інше значення, що перевищує 0, працює. Смішним є те, що у мене є загін з id = 0.
Густаво

5

Ця проблема трапилася зі мною, коли я створив нову сутність та асоційовану сутність методом, позначеним як @Transactional, а потім виконав запит перед збереженням. Вих

@Transactional
public someService() {
    Entity someEntity = new Entity();
    AssocaiatedEntity associatedEntity = new AssocaitedEntity();
    someEntity.setAssociatedEntity(associatedEntity);
    associatedEntity.setEntity(someEntity);

    // Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
    someDao.getSomething();

    entityDao.create(someEntity);
}

Для виправлення я виконував запит перед створенням нового об'єкта.


4

окрім усіх інших хороших відповідей, це може статися, якщо ви використовуєте mergeдля збереження об'єкта та випадково забудете використовувати об'єднану посилання на об'єкт у батьківському класі. розглянемо наступний приклад

merge(A);
B.setA(A);
persist(B);

У цьому випадку ви зливаєтесь, Aале забуваєте використовувати об'єднаний об’єкт A. щоб вирішити проблему, потрібно переписати такий код.

A=merge(A);//difference is here
B.setA(A);
persist(B);

3

я отримую цю помилку, коли я використовую

getSession().save(object)

але це працює без проблем, коли я використовую

getSession().saveOrUpdate(object) 

3

Я також зіткнувся з тією ж ситуацією. Встановивши після анотації над властивістю, це дозволило вирішити запропонований виняток.

Виняток, з яким я стикався.

Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany

Щоб подолати, я використав анотацію.

    @OneToMany(cascade = {CascadeType.ALL})
    @Column(name = "ListOfCarsDrivenByDriver")
    private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();

Що змусило сплячку викинути виняток:

Цей виняток кидається на вашу консоль, оскільки дочірній об’єкт, який я прикріплюю до батьківського об'єкта, на даний момент відсутній у базі даних.

Надаючи @OneToMany(cascade = {CascadeType.ALL}), він повідомляє Hibernate зберігати їх у базі даних, зберігаючи батьківський об'єкт.


2

Заради повноти: A

org.hibernate.TransientPropertyValueException 

з повідомленням

object references an unsaved transient instance - save the transient instance before flushing

також буде мати місце при спробі зберегти / об'єднати об'єкт із посиланням на іншу сутність, яка, як правило, від'єднана .


1

Ще одна можлива причина: у моєму випадку я намагався врятувати дитину, перш ніж врятувати батьків, на абсолютно новій сутності.

Код був приблизно таким у моделі User.java:

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();

Метод setNewPassword () створює запис PasswordHistory і додає його до колекції історії в User. Оскільки оператор create () ще не був виконаний для батьків, він намагався зберегти колекцію об'єкта, яка ще не створена. Все, що мені потрібно було зробити, щоб виправити це, - це перемістити виклик setNewPassword () після виклику create ().

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);

1

Є ще одна можливість, яка може спричинити цю помилку в сплячому режимі. Ви можете встановити незбережену посилання вашого об'єкта Aна додану сутність Bі хочете зберегти об'єкт C. Навіть у цьому випадку ви отримаєте вищезгадану помилку.


1

Якщо ви використовуєте Spring Data JPA, то @Transactionalпроблема вирішення проблеми вирішить проблему додавання приміток до вашої реалізації послуги.


1

Я думаю, це тому, що ви намагаєтеся зберегти об'єкт, який має посилання на інший об'єкт, який ще не зберігається, і тому спробуйте в "стороні БД" поставити посилання на рядок, який не існує


0

Простий спосіб вирішення цього питання - врятувати обидва об'єкти. спочатку збережіть дочірню сутність, а потім збережіть материнську сутність. Оскільки материнське утворення залежить від дочірнього утворення за значенням іноземного ключа.

Нижче простий іспит відносин один на один

insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)

Session session=sf.openSession();
        session.beginTransaction();
        session.save(dep);
        session.save(emp);

1
Це навпаки - дочірнє сутність має значення FK і залежить від батьків, тому вам потрібно спочатку зберегти батьків! У блоці коду ви маєте це право.
Sõber

0

Однією з можливих причин помилки є відсутність встановлення значення материнської сутності; наприклад, для взаємовідносин відділу та співробітників вам потрібно написати це, щоб виправити помилку:

Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with @RequestParam String department
employee.setDepartment(dept);

0

Існує так багато можливостей цієї помилки, деякі інші можливості також є на сторінці додавання або редагуванні. У моєму випадку я намагався зберегти об'єкт AdvanceSalary. Проблема полягає в тому, що в редагуванні AdvanceSalary співробітника.employee_id є недійсним, оскільки під час редагування я не був встановлений співробітник.employee_id. Я зробив приховане поле і встановив його. мій код працює абсолютно чудово.

    @Entity(name = "ic_advance_salary")
    @Table(name = "ic_advance_salary")
    public class AdvanceSalary extends BaseDO{

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

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id", nullable = false)
        private Employee employee;

        @Column(name = "employee_id", insertable=false, updatable=false)
        @NotNull(message="Please enter employee Id")
        private Long employee_id;

        @Column(name = "advance_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        @NotNull(message="Please enter advance date")
        private Date advance_date;

        @Column(name = "amount")
        @NotNull(message="Please enter Paid Amount")
        private Double amount;

        @Column(name = "cheque_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        private Date cheque_date;

        @Column(name = "cheque_no")
        private String cheque_no;

        @Column(name = "remarks")
        private String remarks;

        public AdvanceSalary() {
        }

        public AdvanceSalary(Integer advance_salary_id) {
            this.id = advance_salary_id;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Employee getEmployee() {
            return employee;
        }

        public void setEmployee(Employee employee) {
            this.employee = employee;
        }


        public Long getEmployee_id() {
            return employee_id;
        }

        public void setEmployee_id(Long employee_id) {
            this.employee_id = employee_id;
        }

    }

0

Я зіткнувся з цим винятком, коли я не зберігав батьківський об'єкт, але врятував дитину. Щоб вирішити проблему, з цього ж сеансу я зберігав дочірні та батьківські об'єкти та використовував CascadeType.ALL для батьків.


0

Випадок 1: Я отримував цей виняток, коли я намагався створити батьківський і зберегти посилання на його батьківське дочірнє, а потім інший запит DELETE / UPDATE (JPQL). Тож я просто очищую () новостворене об'єкт після створення батьківського і після створення дочірнього використання, використовуючи ту саму батьківську довідку. Це працювало для мене.

Випадок 2:

Батьківський клас

public class Reference implements Serializable {

    @Id
    @Column(precision=20, scale=0)
    private BigInteger id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modifiedOn;

    @OneToOne(mappedBy="reference")
    private ReferenceAdditionalDetails refAddDetails;
    . 
    .
    .
}

Дитячий клас:

public class ReferenceAdditionalDetails implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @OneToOne
    @JoinColumn(name="reference",referencedColumnName="id")
    private Reference reference;

    private String preferedSector1;
    private String preferedSector2;
    .
    .

}

У вищенаведеному випадку, коли батьків (довідник) та дочірня (ReferenceAdditionalDetails), які мають відношення OneToOne, і коли ви намагаєтесь створити довідковий об'єкт, а потім його дочірню (ReferenceAdditionalDetails), це дасть вам те саме виняток. Таким чином, щоб уникнути винятку, вам потрібно встановити null для дочірнього класу, а потім створити батьківський (Sample Code)

.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.

0

Моя проблема була пов'язана з @BeforeEach з JUnit. І навіть якщо я врятував пов'язані сутності (в моєму випадку@ManyToOne ), я отримав ту саму помилку.

Проблема так чи інакше пов'язана з послідовністю, яку я маю у свого батька. Якщо я призначу цьому атрибуту значення, проблема вирішується.

Вих. Якщо у мене є питання про сутність, яке може містити деякі категорії (одну або більше), а питання питання сутності має послідовність:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
@Id
private Long id;

Я повинен призначити значення question.setId(1L);


0

Просто зробіть конструктор вашого відображення у базовому класі. Начебто, якщо ви хочете відношення «Один до одного» в Entity A, Entity B. Якщо ви приймаєте A як базовий клас, тоді A повинен мати Конструктор, який має B як аргумент.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.