java 多次持久化具有子级的实体时出现Hibernate错误(EntityExistsException)

olhwl3o2  于 2023-05-12  发布在  Java
关注(0)|答案(1)|浏览(146)

我目前正在编写一个相当“愚蠢”的ETL过程。我称之为dumb,是因为这个进程每晚运行,消化所有数据(即使数据没有变化),并将其持久化。我的希望是保持简单。
我一直在使用Hibernate v6.1.7来定义我的实体并持久化它们。由于实体可能已经存在于我的数据库中,所以我使用合并来持久化实体。

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();
}

这对于没有任何复杂关系的简单实体和具有ManyToOne关系的实体都很有效。
我在持久化一个具有一对一关系的实体时遇到了问题,其中子实体拥有密钥。下面是一些展示此问题的实体的简单示例。

@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;
    }
}

如果我从上面运行主代码,它将在第一次运行时成功。但是一旦baseball_weather存在,运行merge会产生以下错误。
线程“main”中出现异常jakarta.persistence.EntityExistsException:具有相同标识符值的其他对象已与会话关联:[x.y.z.BaseballWeather#12345]
错误消息是正确的,我正在手动创建一个版本的BaseballWeather类,它包含与会话中已存储的对象相同的ID。但是我没有看到父类或其他没有这种一对一关系的实体的这种行为。在这些情况下,我手动创建的对象仍然会被合并而不会出错。
我的印象是,将级联类型指定为“Merge”将级联合并操作。我试过替换其他级联类型(PersistAll等),如果对象不存在,没有一个能够持久化对象,如果对象不存在,也没有一个能够更新对象。
有谁知道我如何更新我的实体来支持持久化和通过merge方法更新而不会出错吗?

uujelgoq

uujelgoq1#

好的,我明白了。下面是重现错误所需的完整代码:

@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;
        }
    }

}

所以这里的问题是由我添加的注解行引起的。Hibernate推断分离的BaseballWeather是一个新的/临时的对象(在数据库中不存在),因为它有一个空的@Id属性。
这是一个我实际上没有意识到会发生的情况。只有当您与@MapsId有这些一对一的关联时才会发生这种情况。
无论如何,我将进一步研究这个问题,看看我们是否可以在这里报告一个更好的错误,但要明确的是,这实际上是你忘记设置id字段的错误。

相关问题