【问题标题】:CRUD repository does not respect UNIQUE constraintCRUD 存储库不遵守 UNIQUE 约束
【发布时间】:2019-09-29 05:01:11
【问题描述】:

我有以下 JPA 实体

@Entity
class UserEntity {

    companion object {
        fun fromParameters(uuid: String, email: String, password: String, firstName: String, lastName: String) =
            UserEntity().apply {
                this.uuid = uuid
                this.email = email
                this.password = password
                this.firstName = firstName
                this.lastName = lastName
            }
    }

    @Id
    lateinit var uuid: String

    @Column(nullable = false, unique = true)
    lateinit var email: String

    @Column(nullable = false)
    lateinit var password: String

    @Column(nullable = false)
    lateinit var firstName: String

    @Column(nullable = false)
    lateinit var lastName: String
}

这是我检查 UNIQUE 约束的测试,使用相同的电子邮件插入另一个用户。

@RunWith(SpringRunner::class)
@DataJpaTest
@AutoConfigureTestEntityManager
class TestUserCrudRepository {

    @Autowired
    private lateinit var userCrudRepository: UserCrudRepository

    private val testUserEntity = UserEntity.fromParameters(
        UUID.randomUUID().toString(),
        "test@test.com",
        "password".toHash(),
        "Caetano",
        "Veloso"
    )

    @Test
    fun `when email already exists it should throw error`() {
        with (userCrudRepository) {
            save(testUserEntity)
            val newUserEntity = with (testUserEntity) { UserEntity.fromParameters(UUID.randomUUID().toString(), email, password, firstName, lastName) }
            shouldThrow<SQLException> { save(newUserEntity) }
        }
    }
}

插入新实体时总是带有重复的电子邮件,不会引发任何异常。

Expected exception java.sql.SQLException but no exception was thrown

我可以在日志中看到使用给定约束正确创建了表。

Hibernate: drop table user_entity if exists
Hibernate: create table user_entity (uuid varchar(255) not null, email varchar(255) not null, first_name varchar(255) not null, last_name varchar(255) not null, password varchar(255) not null, primary key (uuid))
Hibernate: alter table user_entity add constraint UK_4xad1enskw4j1t2866f7sodrx unique (email)

提前致谢!

【问题讨论】:

    标签: hibernate spring-boot kotlin spring-data-jpa h2


    【解决方案1】:

    发生这种情况是因为没有发出 insert 声明。

    Hibernate 不会 flush 会话 除非它有充分的理由这样做

    1. @DataJpaTest@Transactional。这意味着在其中执行的@Test 方法的事务会在方法返回后回滚。
    2. UserEntity 映射还鼓励休眠延迟insert(尝试在id 属性上使用@GeneratedValue(strategy = IDENTITY) 来强制发出inserts)

    没有深入了解太多细节,运行测试时会发生以下情况:

    1. Spring 的测试基础架构begins 事务
    2. @Test 方法运行
    3. save(testUserEntity) - Hibernate 意识到没有理由访问数据库并延迟 insert
    4. shouldThrow&lt;SQLException&gt; { save(newUserEntity) } - 和之前一样
    5. @Test 方法返回
    6. 事务回滚。 Hibernate 确实会执行 inserts,因为没有理由这样做。

    如何解决?

    最简单的方法是使用JpaRepository#flush

    with (userCrudRepository) {
        save(testUserEntity)
        val newUserEntity = with (testUserEntity) { UserEntity.fromParameters(UUID.randomUUID().toString(), email, password, firstName, lastName) }
        save(newUserEntity)
        assertThrows<DataIntegrityViolationException> {
            flush()
        }
    }
    

    注意CrudRepository中没有flush方法

    我猜你已经扩展了CrudRepository...你可能想要扩展JpaRepository

    见:What is difference between CrudRepository and JpaRepository interfaces in Spring Data JPA?


    异常说明

    您期望抛出SQLException

    但请注意,DataIntegrityViolationException 将被抛出。

    见:Consistent Exception Hierarchy

    【讨论】:

    • 很好的答案,我 3 天前刚开始使用 Spring Boot :) 用 @Commit 注释测试并不能解决我的问题,因为我在测试方法结束之前检查异常。我按照您的建议扩展了JpaRepository,并在flush() 上捕获了异常,并在测试中使用了@Rollback,以避免它抛出UnexpectedRollbackException。也许你想用这些来更新你的答案。
    • 哦,我只是错过了一些时刻。 @Commit 将抛出 @Test 的异常 outside (我删除了这部分答案)。您不需要使用@Rollback。将flush 包装成assertThrows 就足够了。只有当@Commit 出现在@Test 上时,我才能获得UnexpectedRollbackException。你能详细说明一下吗?
    • 很高兴能帮到你:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-21
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多