null

gijlo24d  于 2021-06-29  发布在  Java
关注(0)|答案(1)|浏览(326)

我将quarkus与hibernate和resteasy一起使用。
当一个实体被分割时,我想自动创建一个引用第一个实体的第二个实体。
我试着用 @PostPersist 带注解的方法,但它因sql异常而失败。当手动调用该方法时,它会工作。
中全景:

CREATE TABLE "entity_a" (
    "id" IDENTITY
);

CREATE TABLE "entity_b" (
    "id" IDENTITY,
    "entity_a_id" BIGINT NOT NULL,
    FOREIGN KEY ("entity_a_id") REFERENCES "entity_a"("id"),
);

@Entity
public class EntityA {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    // getter, setter

    @PostPersist
    public void postPersist() {
        EntityB b = new EntityB();
        b.setEntityA(this);
        JpaOperations.getEntityManager().persist(b);
    }
}

@Entity
public class EntityB {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @OneToOne
    private EntityA entityA;

    // getters, setters
}

// in the rest resource class
    @POST
    @Transactional
    public void create() {
        EntityA a = new EntityA();
        JpaOperations.getEntityManager().persist(a);
    }

调用该操作时,出现以下错误:

org.h2.jdbc.JdbcSQLException: NULL not allowed for column "entity_a_id"
NULL not allowed for column "entity_a_id"; SQL statement:
insert into "entity_b" ("id", "entity_a_id") values (null, ?) [23502-197]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
    at org.h2.message.DbException.get(DbException.java:179)
    at org.h2.message.DbException.get(DbException.java:155)
    at org.h2.table.Column.validateConvertUpdateSequence(Column.java:374)
    at org.h2.table.Table.validateConvertUpdateSequence(Table.java:798)
    at org.h2.command.dml.Insert.insertRows(Insert.java:177)
    at org.h2.command.dml.Insert.update(Insert.java:134)
    at org.h2.command.CommandContainer.update(CommandContainer.java:102)
    at org.h2.command.Command.executeUpdate(Command.java:261)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:199)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:153)
    at io.agroal.pool.wrapper.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:86)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:45)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3200)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3806)
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:330)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:287)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:720)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:706)
    at io.quarkus.hibernate.orm.runtime.entitymanager.TransactionScopedEntityManager.persist(TransactionScopedEntityManager.java:116)
    at io.quarkus.hibernate.orm.runtime.entitymanager.ForwardingEntityManager.persist(ForwardingEntityManager.java:27)
    at example.EntityA.postPersist(EntityA.java:24)
    ...

当我移除 @PostPersist 注解并从rest资源调用方法(在 getEntityManager().persist )它按预期工作。
当我在里面记录id值时 postPersist ,已设置。为什么会失败?我如何修复它?

wlwcrazw

wlwcrazw1#

虽然这是奇怪但正确的行为。基本上 entityManager 不必立即生成身份 persist() 打电话。这取决于选定的identifiergenerator的策略。
然而 IDENTITY 指示持久性提供程序必须使用数据库标识列为实体分配主键,只有在调用事务提交或flush()时才保证分配主键。
有许多解决方案和解决办法来解决这个问题。
解决方案#1:变革生成策略
据我所知 SEQUENCE 策略也许不错,但老实说我还没查过。这只是一种强烈的感觉。
解决方案2:使用合并而不是持久化
智能解决方案使用 merge() 持久化实体。关键是什么时候 merge() 调用一个新的实体示例,然后创建一个新的托管实体示例,并将原始实体的状态复制到同一实体的托管示例。执行sqlselect语句可以很容易地从数据库中检索托管实体。
那就换个吧

JpaOperations.getEntityManager().persist(b);
// and
JpaOperations.getEntityManager().persist(a);

通过

JpaOperations.getEntityManager().merge(b);
// and
JpaOperations.getEntityManager().merge(a);

如您所见,hibernate检索以前保存(合并)的以下日志条目 EntityA 从数据库。

2020-12-28 23:44:09,822 TRACE [org.hib.eng.spi.IdentifierValue] (executor-thread-1) ID unsaved-value: 0
2020-12-28 23:44:09,822 TRACE [org.hib.eve.int.EntityState] (executor-thread-1) Transient instance of: io.github.zforgo.stackoverflow.quarkus.model.EntityB
2020-12-28 23:44:09,822 TRACE [org.hib.eve.int.DefaultMergeEventListener] (executor-thread-1) Merging transient instance
2020-12-28 23:44:09,822 TRACE [org.hib.eng.spi.IdentifierValue] (executor-thread-1) ID unsaved-value: 0
2020-12-28 23:44:09,822 TRACE [org.hib.eng.spi.IdentifierValue] (executor-thread-1) ID unsaved-value: 0
2020-12-28 23:44:09,822 TRACE [org.hib.eve.int.DefaultLoadEventListener] (executor-thread-1) Loading entity: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
2020-12-28 23:44:09,822 TRACE [org.hib.eve.int.DefaultLoadEventListener] (executor-thread-1) Attempting to resolve: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
2020-12-28 23:44:09,822 TRACE [org.hib.eve.int.DefaultLoadEventListener] (executor-thread-1) Object not resolved in any cache: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
2020-12-28 23:44:09,822 TRACE [org.hib.per.ent.AbstractEntityPersister] (executor-thread-1) Fetching entity: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
2020-12-28 23:44:09,822 DEBUG [org.hib.SQL] (executor-thread-1) select entitya0_.id as id1_0_0_ from EntityA entitya0_ where entitya0_.id=?
2020-12-28 23:44:09,823 TRACE [org.hib.res.jdb.int.ResourceRegistryStandardImpl] (executor-thread-1) Registering statement [wrapped[ prep7: select entitya0_.id as id1_0_0_ from EntityA entitya0_ where entitya0_.id=? ]]
2020-12-28 23:44:09,823 TRACE [org.hib.eng.jdb.int.JdbcCoordinatorImpl] (executor-thread-1) Registering last query statement [wrapped[ prep7: select entitya0_.id as id1_0_0_ from EntityA entitya0_ where entitya0_.id=? ]]
2020-12-28 23:44:09,823 TRACE [org.hib.typ.des.sql.BasicBinder] (executor-thread-1) binding parameter [1] as [BIGINT] - [4]
2020-12-28 23:44:09,823 TRACE [org.hib.loa.pla.exe.int.AbstractLoadPlanBasedLoader] (executor-thread-1) Bound [2] parameters total

使用时缺失的最相关部分 persist() 是:

Loading entity: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
Attempting to resolve: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
Object not resolved in any cache: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
Fetching entity: [io.github.zforgo.stackoverflow.quarkus.model.EntityA#4]
select entitya0_.id as id1_0_0_ from EntityA entitya0_ where entitya0_.id=?

解决方案#3使用分离的服务和不同的事务
虽然我更喜欢这个,但它需要付出最大的努力。原来的代码根本不适合坚实的原则。这个想法是
使用创建另一个具有新事务边界的bean @Transactional(Transactional.TxType.REQUIRES_NEW) 注解
因为JPA2.2EventListener支持cdi注入,所以将事件处理程序移到一个单独的事件监听器类中
在listener类中,注入服务bean并调用所需的方法。
但由于这个漏洞,我无法检查它。

相关问题