Hibernate错误(EntityExistsException)在多次持久化带有子实体的实体时发生。

huangapple go评论59阅读模式
英文:

Hibernate error (EntityExistsException) when persisting entity with children multiple times

问题

I am currently writing a fairly "dumb" ETL process. I call it dumb because the process runs nightly, digest all of the data (even if it hasn't change), and persist it. My hope is to keep it simple.

我目前正在编写一个相当“愚蠢”的ETL过程。我之所以称之为愚蠢,是因为这个过程每天晚上都会运行,即使数据没有发生变化,也会进行处理和持久化。我希望保持它简单。

I've been using Hibernate v6.1.7 to define my entities and persist them. Since the entities may already exist in my database, I'm using merge to persist the entity.

我一直在使用Hibernate v6.1.7来定义我的实体并进行持久化。由于实体可能已经存在于我的数据库中,所以我使用merge来进行持久化。

This works fine for simple entities that don't have any complex relationship and for entities that have a ManyToOne relationship.

这对于没有复杂关系的简单实体以及具有ManyToOne关系的实体运行良好。

I'm running into problems when persisting an entity that has a OneToOne relationship where the child owns the key. Here are some trivial examples of entities that exhibit this problem.

当持久化具有OneToOne关系且子对象拥有键的实体时,我遇到了问题。以下是一些展示此问题的简单示例实体。

If I run the main code from above, it will succeed on the first run. But once the baseball_weather exists, running merge produces the following error.

如果我运行上面的主要代码,它将在第一次运行时成功。但是一旦baseball_weather存在,运行merge会产生以下错误。

Exception in thread "main" jakarta.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [x.y.z.BaseballWeather#12345]

线程中的异常:“main” jakarta.persistence.EntityExistsException:与会话关联的具有相同标识值的不同对象已存在:[x.y.z.BaseballWeather#12345]

The error message is correct, I'm manually creating a version of the BaseballWeather class that contains the same ID as an object already stored in the session. But I'm not seeing this behavior with the parent class or with other entities that don't have this OneToOne relationship. In those cases objects I create manually will still be merged without error.

错误消息是正确的,我手动创建了一个具有与会话中已存储的对象相同ID的BaseballWeather类的版本。但是我没有在父类或没有这个OneToOne关系的其他实体中看到这种行为。在这些情况下,我手动创建的对象仍然可以合并而没有错误。

I was under the impression that specifying the cascade type to "Merge" would, well, cascade the merge operation. I've tried substituting in the other cascade types (Persist, All, etc..) and none have been able to persist the object if it doesn't exist or update if it does.

我原以为将级联类型指定为“Merge”会级联合并操作。我尝试过使用其他级联类型(Persist、All等),但如果对象不存在,它们都无法进行持久化,如果对象存在,也无法进行更新。

Is anyone aware of how I can update my entities to support both persisting and updating via the merge method without running into errors?

有人知道如何更新我的实体以支持通过merge方法进行持久化和更新而不会遇到错误吗?

英文:

I am currently writing a fairly "dumb" ETL process. I call it dumb because the process runs nightly, digest all of the data (even if it hasn't change), and persist it. My hope is to keep it simple.

I've been using Hibernate v6.1.7 to define my entities and persist them. Since the entities may already exist in my database, I'm using merge to persist the entity.

public static void main(String[] args){
    BaseballGame game = new BaseballGame();
    BaseballWeather weather = new BaseballWeather();

    game.setGameId(12345L);
    game.setWeather(weather);

    weather.setTemperature(70.0);
    weather.setGame(game);

    Session session = Hibernate.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    session.merge(game);
    transaction.commit();
    session.close();
}

This works fine for simple entities that don't have any complex relationship and for entities that have a ManyToOne relationship.

I'm running into problems when persisting an entity that has a OneToOne relationship where the child owns the key. Here are some trivial examples of entities that exhibit this problem.

@Entity
@Table(name = "baseball_game")
public class BaseballGame {

	@Id
	private Long gameId;

	@OneToOne(mappedBy = "game", cascade = CascadeType.MERGE)
	private BaseballWeather weather;

	public void setGameId(Long gameId) {
		this.gameId = gameId;
	}

	public void setWeather(BaseballWeather weather) {
		this.weather = weather;
	}
}

@Entity
@Table(name = "baseball_weather")
public class BaseballWeather {

	@Id
	private Long id;

	private Double temperature;

	@OneToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "game_id")
	@MapsId
	private BaseballGame game;

	public void setTemperature(Double temperature) {
		this.temperature = temperature;
	}

	public void setGame(BaseballGame game) {
		this.game = game;
	}
}

If I run the main code from above, it will succeed on the first run. But once the baseball_weather exists, running merge produces the following error.

> Exception in thread "main" jakarta.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [x.y.z.BaseballWeather#12345]

The error message is correct, I'm manually creating a version of the BaseballWeather class that contains the same ID as an object already stored in the session. But I'm not seeing this behavior with the parent class or with other entities that don't have this OneToOne relationship. In those cases objects I create manually will still be merged without error.

I was under the impression that specifying the cascade type to "Merge" would, well, cascade the merge operation. I've tried substituting in the other cascade types (Persist, All, etc..) and none have been able to persist the object if it doesn't exist or update if it does.

Is anyone aware of how I can update my entities to support both persisting and updating via the merge method without running into errors?

答案1

得分: 2

I see. 这里出现的问题是由我添加的注释行引起的。Hibernate 推断出,由于@Id属性为空,分离的BaseballWeather是一个新的/临时对象(不存在于数据库中)。

这实际上是我没有意识到的情况。它只会在你使用@MapsId的这种一对一关联时发生。

无论如何,我将进一步调查此问题,以查看是否可以报告更好的错误,但明确一点,这实际上是你的错误,因为忘记设置id字段。

英文:

OK, I see. So here is the full code required to reproduce the error:

@SessionFactory
@DomainModel(annotatedClasses = {SOTest.BaseballGame.class, SOTest.BaseballWeather.class})
public class SOTest {

	@Test void test(SessionFactoryScope scope) {
		BaseballGame game = new BaseballGame();
		BaseballWeather weather = new BaseballWeather();

		game.setGameId(12345L);
		game.setWeather(weather);

 		// Note that the id field was never set!
		//weather.id = 12345L;

		weather.setTemperature(70.0);
		weather.setGame(game);

		scope.inSession(session-> {
			Transaction transaction = session.beginTransaction();
			session.merge(game);
			transaction.commit();
		});
		scope.inSession(session-> {
			Transaction transaction = session.beginTransaction();
			session.merge(game);
			transaction.commit();
		});
	}

	@Entity
	@Table(name = "baseball_game")
	static public class BaseballGame {

		@Id
		private Long gameId;

		@OneToOne(mappedBy = "game", cascade = CascadeType.MERGE)
		private BaseballWeather weather;

		public void setGameId(Long gameId) {
			this.gameId = gameId;
		}

		public void setWeather(BaseballWeather weather) {
			this.weather = weather;
		}
	}

	@Entity
	@Table(name = "baseball_weather")
	static public class BaseballWeather {

		@Id
		private Long id;

		private Double temperature;

		@OneToOne(fetch = FetchType.LAZY)
		@JoinColumn(name = "game_id")
		@MapsId
		private BaseballGame game;

		public void setTemperature(Double temperature) {
			this.temperature = temperature;
		}

		public void setGame(BaseballGame game) {
			this.game = game;
		}
	}

}

So the issue here resulted from the commented line added by me. Hibernate inferred that the detached BaseballWeather was a new/transient object (not existing in the database) because it had a null @Id property.

This is a situation that I actually didn't realize can happen. It only happens when you have these one-to-one associations with @MapsId.

Anyway I will look into this further to see if we can report a better error here, but to be clear, this was actually your fault for forgetting to set the id field.

huangapple
  • 本文由 发表于 2023年5月11日 10:17:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76223714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定