CRUD 存储库不尊重 UNIQUE 约束

m0s*_*it0 2 hibernate h2 spring-data-jpa kotlin spring-boot

我有以下 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
}
Run Code Online (Sandbox Code Playgroud)

这是我检查 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) }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

新实体始终会插入重复的电子邮件,而不会引发任何异常。

Expected exception java.sql.SQLException but no exception was thrown
Run Code Online (Sandbox Code Playgroud)

我可以在日志中看到该表是根据给定的约束正确创建的。

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)
Run Code Online (Sandbox Code Playgroud)

提前致谢!

cac*_*co3 8

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

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

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

不深入讨论太多细节,运行测试时会发生以下情况:

  1. Spring的测试基础设施begin的事务
  2. @Test方法运行
  3. save(testUserEntity)- Hibernate 意识到没有理由访问数据库并延迟insert
  4. shouldThrow<SQLException> { 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,其中没有flush方法CrudRepository

我猜你已经延长了CrudRepository……你可能想延长JpaRepository

请参阅:Spring Data JPA 中的 CrudRepository 和 JpaRepository 接口有什么区别?


例外情况注意事项

你期待着 aSQLException被抛出。

但请注意,它将DataIntegrityViolationException被抛出。

请参阅:一致的异常层次结构