Hibernate: Как исправить "идентификатор экземпляра, измененного с X на Y"?

org.hibernate.HibernateException: identifier of an instance 
of org.cometd.hibernate.User altered from 12 to 3

на самом деле мой user Таблица действительно должна динамически менять свое значение, мое Java-приложение является многопоточным. Есть идеи как это исправить?

23 ответа

Решение

Вы меняете значение первичного ключа объекта User где-нибудь? Ты не должен этого делать. Проверьте правильность сопоставления первичного ключа.

Как выглядит ваш картографический XML-файл или картографические аннотации?

Вы должны отсоединить свою сущность от сеанса до изменения ее полей идентификатора.

В моем случае поле PK в hbm.xml имело тип "целое число", но в коде компонента оно было long,

В моем случае имена получателей и сеттеров отличались от имени переменной.

private Long stockId;
    public Long getStockID() {
        return stockId;
    }
    public void setStockID(Long stockID) {
        this.stockId = stockID;
    }

где это должно быть

public Long getStockId() {
    return stockId;
}
public void setStockId(Long stockID) {
    this.stockId = stockID;
}

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

Общий ответ: убедитесь, что после загрузки объекта в спящий режим никакой код никоим образом не изменяет значение первичного ключа в этом объекте. Когда спящий режим сбрасывает все изменения обратно в базу данных, он генерирует это исключение, потому что изменился первичный ключ. Если вы не делаете этого явно, поищите места, где это может произойти непреднамеренно, например, на связанных объектах, для которых настроена только LAZY загрузка.

В моем случае я использую структуру сопоставления (MapStruct) для обновления объекта. В процессе обновлялись и другие объекты, на которые есть ссылки, поскольку каркасы обычно делают это по умолчанию. Позже я заменил исходный объект новым (в терминах БД изменил значение внешнего ключа для ссылки на другую строку в связанной таблице), первичный ключ ранее упомянутого объекта был уже обновлен, и спящий режим попытался сохранить это обновление при сбросе.

В моем случае я решил это, изменив тип поля @Id с long на Long.

В моем конкретном случае это было вызвано методом в моей реализации сервиса, который нуждался в пружине @Transactional(readOnly = true) аннотаций. Как только я добавил это, проблема была решена. Необычно, хотя, это было только отборное утверждение.

Убедитесь, что вы не пытаетесь использовать один и тот же объект User более одного раза при изменении идентификатора. Другими словами, если вы что-то делали в операции пакетного типа:

User user = new User();  // Using the same one over and over, won't work
List<Customer> customers = fetchCustomersFromSomeService();
for(Customer customer : customers) {
 // User user = new User(); <-- This would work, you get a new one each time
 user.setId(customer.getId());
 user.setName(customer.getName());
 saveUserToDB(user);
}

В моем случае шаблон имел опечатку, поэтому вместо проверки на эквивалентность (==) он использовал присвоение, равное (=).

Поэтому я изменил логику шаблона с:

if (user1.id = user2.id) ...

в

if (user1.id == user2.id) ...

и теперь все хорошо. Итак, проверьте свои взгляды!

Это проблема в вашем методе обновления. Просто сохраните изменения, прежде чем сохранить изменения, и все будет в порядке. Если вы используете отображение между DTO и классом Entity, сделайте это перед отображением.

У меня тоже была эта ошибка. У меня был пользовательский объект, пытающийся изменить его местоположение, местоположение было FK в пользовательской таблице. Я решил эту проблему с

@Transactional
public void update(User input) throws Exception {

    User userDB = userRepository.findById(input.getUserId()).orElse(null);
    userDB.setLocation(new Location());
    userMapper.updateEntityFromDto(input, userDB);

    User user= userRepository.save(userDB);
}  

Я тоже столкнулся с этой проблемой.

Целевая таблица - это таблица отношений, связывающая два идентификатора из разных таблиц. У меня есть УНИКАЛЬНОЕ ограничение на комбинацию значений, заменяющее PK. При обновлении одного из значений кортежа произошла эта ошибка.

Вот как выглядит таблица (MySQL):

CREATE TABLE my_relation_table (
  mrt_left_id BIGINT NOT NULL,
  mrt_right_id BIGINT NOT NULL,
  UNIQUE KEY uix_my_relation_table (mrt_left_id, mrt_right_id),
  FOREIGN KEY (mrt_left_id)
    REFERENCES left_table(lef_id),
  FOREIGN KEY (mrt_right_id)
    REFERENCES right_table(rig_id)
);

Класс Entity для RelationWithUnique сущность выглядит в основном так:

@Entity
@IdClass(RelationWithUnique.class)
@Table(name = "my_relation_table")
public class RelationWithUnique implements Serializable {

  ...

  @Id
  @ManyToOne
  @JoinColumn(name = "mrt_left_id", referencedColumnName = "left_table.lef_id")
  private LeftTableEntity leftId;

  @Id
  @ManyToOne
  @JoinColumn(name = "mrt_right_id", referencedColumnName = "right_table.rig_id")
  private RightTableEntity rightId;

  ...

Я исправил это

// usually, we need to detach the object as we are updating the PK
// (rightId being part of the UNIQUE constraint) => PK
// but this would produce a duplicate entry, 
// therefore, we simply delete the old tuple and add the new one
final RelationWithUnique newRelation = new RelationWithUnique();
newRelation.setLeftId(oldRelation.getLeftId());
newRelation.setRightId(rightId);  // here, the value is updated actually
entityManager.remove(oldRelation);
entityManager.persist(newRelation);

Большое спасибо за подсказку ПК, я просто пропустил это.

Проблема может быть также в разных типах объектов объекта ("Пользователь" в вашем случае) и типа, который вы просите hibernate, чтобы получить session.get(type, id);,

В моем случае ошибка была identifier of an instance of <skipped> was altered from 16 to 32, Тип ПК объекта был IntegerСпящий был спрошен Long тип.

Это старый вопрос, но я собираюсь добавить исправление для моей конкретной проблемы (Spring Boot, JPA с использованием Hibernate, SQL Server 2014), поскольку оно не совсем совпадает с другими ответами, включенными здесь:

У меня был внешний ключ, например, my_id = '12345', но значение в указанном столбце было my_id = '12345 '. В конце у него было дополнительное пространство, которое не понравилось. Я удалил пробел, исправил часть моего кода, которая позволяла это дополнительное пространство, и все работает отлично.

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

У меня та же проблема

      "message": "identifier of an instance of com.intern.assignment.model.Event was altered from 0 to 2"

затем я проверил свой EventController.java и в этом контроллере по ошибке попытался обновить eventId (атрибут первичного ключа), как и другие атрибуты. Затем я прокомментировал это и попробовал еще раз. Вау, проблема исчезла.

      //update event
@PutMapping("/eventsUpdate/{id}")
public ResponseEntity<Event> updateEvent(@PathVariable(value = "id") Long eventId, @Validated @RequestBody Event eventDetails) throws ResourceNotFoundException{
    
    Event event = eventRepository.findById(eventId).orElseThrow(() -> new ResourceNotFoundException("Event not found for this id: " + eventId));
    
    event.setAddrNbr(eventDetails.getAddrNbr());
    event.setClientId(eventDetails.getClientId());
    event.setEventCnt(eventDetails.getEventCnt());
    //event.setEventId(eventDetails.getEventId());
    event.setLocationCd(eventDetails.getLocationCd());
    event.setLocationId1(eventDetails.getLocationId1());
    event.setLocationId2(eventDetails.getLocationId2());
    event.setRcNum(eventDetails.getRcNum());
    event.setTransId(eventDetails.getTransId());
    event.setTransTms(eventDetails.getTransTms());
    return ResponseEntity.ok(this.eventRepository.save(event));
    
}

Похоже, вы изменили идентификатор экземпляра объекта org.cometd.hibernate.User, управляемого контекстом сущности JPA. В этом случае создайте новый объект сущности User с соответствующим идентификатором. И установите его вместо исходного объекта User.

Использовали ли вы несколько менеджеров транзакций из одного и того же класса обслуживания. Например, если в вашем проекте две или более конфигураций транзакций. Если верно, то сначала разделите их.

В моем случае у меня был первичный ключ в базе данных с акцентом, но в другой таблице его внешний ключ не имел. По какой-то причине MySQL позволял это.

Если вы используете Spring MVC или Spring Boot, старайтесь избегать:@ModelAttribute ("user") в одном контроллере и в другом контроллереmodel.addAttribute("user", userRepository.findOne(someId);

Эта ситуация может привести к такой ошибке.

Столкнулся с той же проблемой. У меня была ассоциация между 2 бобами. В bean A I определен тип переменной как Integer, а в bean B I определена та же переменная, что и в Long. Я изменил их обоих на Integer. Это решило мою проблему.

В моем случае это было из-за того, что свойство было длинным для объекта, но int в сопоставлении xml, это исключение должно быть более понятным

Я решаю эту проблему, создав новый экземпляр зависимого Object. Для примера

instanceA.setInstanceB(new InstanceB());
instanceA.setInstanceB(YOUR NEW VALUE);

У меня возникла проблема, когда я попытался получить существующий объект БД, изменил несколько полей и выполнил

      session.save(entity)

вместо

      session.merge(entity)

Поскольку он существует в БД, когда мы должны объединить() вместо сохранения()

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