m0skit0
m0skit0

Reputation: 25873

CRUD repository does not respect UNIQUE constraint

I have the following JPA entity

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

And this is my test to check the UNIQUE constraint, inserting another user with the same email.

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

    @Autowired
    private lateinit var userCrudRepository: UserCrudRepository

    private val testUserEntity = UserEntity.fromParameters(
        UUID.randomUUID().toString(),
        "[email protected]",
        "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) }
        }
    }
}

The new entity always gets inserted with a duplicate email without any exception being thrown.

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

I can see in the log that the table is created correctly with the given constraint.

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)

Thanks in advance!

Upvotes: 2

Views: 4488

Answers (1)

Denis Zavedeev
Denis Zavedeev

Reputation: 8297

This happens because no insert statement issued.

Hibernate does not flush session unless it has a good reason to do so.

  1. @DataJpaTest is @Transactional. This means that the transaction a @Test method executed within is rolled back after the method returns.
  2. UserEntity mapping also encourages hibernate to delay the insert (try using @GeneratedValue(strategy = IDENTITY) on id property to force issuing inserts)

Not diving into too much details the following happens when you run test:

  1. Spring's test infrastructure begins transaction
  2. @Test method runs
  3. save(testUserEntity) - Hibernate realizes that there is no reason to hit the database and delays the insert
  4. shouldThrow<SQLException> { save(newUserEntity) } - same as previous
  5. @Test method returns
  6. Transaction rolls back. Hibernate does execute inserts because there is no reason to.

How to fix it?

The most simple way to do it is to use JpaRepository#flush:

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

Note that there is no flush method in CrudRepository

I guess that you have extended CrudRepository... You may want to extend JpaRepository instead.

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


Note on exception

You are expecting an SQLException to be thrown.

But note that DataIntegrityViolationException will be thrown instead.

See: Consistent Exception Hierarchy

Upvotes: 8

Related Questions