代码之家  ›  专栏  ›  技术社区  ›  Aleksandr Semyannikov

休眠:生成的ID在sqlIntegrityConstraintVolationException之后保留在非持久实体中

  •  0
  • Aleksandr Semyannikov  · 技术社区  · 7 年前

    大家好!

    一段时间前,我遇到了一个问题:如果存储库的save方法失败,那么由hibernate注入bean的标识符将保留在bean中。这种行为可能会导致我们陷入这样的境地:我们会把不持久的bean想象成持久的bean。我很高兴知道避免这种情况的惯例是什么。

    示例测试(Spring引导+Hibernate+Oracle数据库):

    @Entity
    @SequenceGenerator(name = "TEST_ENTITY_GENERATOR", allocationSize = 1, sequenceName = "TEST_ENTITY_SEQ")
    public class TestEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_ENTITY_GENERATOR")
        private Long id;
    
        @Column(nullable = false)
        private String name;
    
        public Long getId() {
            return id;
        }
    }
    
    @Repository
    public interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
    
    }
    
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class RemainingIdTest {
    
        @Autowired
        private TestEntityRepository testEntityRepository;
    
        @Test
        public void test() {
            TestEntity entity = new TestEntity();
            try {
                Assertions.assertThat(entity.getId()).isNull();
                testEntityRepository.save(entity);
                Assertions.fail("Save must fail");
            } catch (DataIntegrityViolationException e) {
                Assertions.assertThat(entity.getId()).isNotNull();
            }
        }
    }
    
    1 回复  |  直到 7 年前
        1
  •  0
  •   Aleksandr Semyannikov    7 年前

    可能的解决方案是 org.hibernate.event.spi.PreInsertEventListener 在这里,我们可以将事务与处理器绑定,如果事务失败,处理器将清除您的实体。

    例子:

    @Component
    public class IdentifierCleaner implements PreInsertEventListener {
    
        @Autowired
        private EntityManagerFactory entityManagerFactory;
    
        @PostConstruct
        private void init() {
            SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
            EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
            registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(this);
        }
    
        @Override
        public boolean onPreInsert(PreInsertEvent event) {
            Object entity = event.getEntity();
            event.getSession().getActionQueue().registerProcess(((success, session) -> {
                if (!success) {
                    event.getPersister().resetIdentifier(
                            entity,
                            event.getId(),
                            event.getPersister().getVersion(entity),
                            event.getSession()
                    );
                }
            }));
            return false;
        }
    }