英文:
Why @Transactional and @Rollback don't work?
问题
IUserInfoRepository.java
interface IUserInfoRepository : JpaRepository<UserInfo, UUID> {
fun findByUserName(name: String): UserInfo?
@Transactional
fun deleteByUserName(name: String): Int
}
UserService.java
@Service
class UserService(@Autowired val userInfoRepository: IUserInfoRepository) {
private lateinit var user: User
fun initService(user: User) {
this.user = user;
}
fun createUser(userName: String, password: ByteArray, nickName: String, isAdmin: Boolean): Boolean {
if (userInfoRepository.findByUserName(userName) != null) {
return false
}
val hashCodeTable = HashCodeTable(RandomStringUtils.randomAscii(16).toByteArray())
val hashedPassword = genHashedPassword(password, hashCodeTable.salt1)
userInfoRepository.save(UserInfo(userName, nickName, hashedPassword, hashCodeTable, isAdmin))
return true
}
fun deleteUser(): Boolean {
return validateUserAndDo {
userInfoRepository.deleteByUserName(user.userName)
true
}
}
fun findUser(): UserInfo? {
return userInfoRepository.findByUserName(user.userName)
}
fun validateUser(): Boolean {
return (findUser()?.hashedPassword?.contentEquals(user.password)) ?: false
}
fun getLoginSalt(userName: String): ByteArray {
return userInfoRepository.findByUserName(userName)?.hashCodeTable?.salt1 ?: ByteArray(0)
}
fun changePassword(newPassword: ByteArray): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val hashedPassword = genHashedPassword(newPassword, oldUser.hashCodeTable.salt1)
val newUser = UserInfo(oldUser.userName, oldUser.nickName, hashedPassword, oldUser.hashCodeTable, oldUser.isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
private fun validateUserAndDo(something: () -> Boolean): Boolean {
if (validateUser()) {
return something.invoke()
}
return false
}
fun changeNickName(newNickName: String): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val newUser = UserInfo(oldUser.userName, newNickName, oldUser.hashedPassword, oldUser.hashCodeTable, oldUser.isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
fun changePermission(isAdmin: Boolean): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val newUser = UserInfo(oldUser.userName, oldUser.nickName, oldUser.hashedPassword, oldUser.hashCodeTable, isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
fun isPermission(): Boolean {
return userInfoRepository.findByUserName(user.userName)?.isAdmin ?: false
}
fun deleteAll(): Boolean {
userInfoRepository.deleteAll()
return true
}
}
UserServiceTest.java
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserServiceTest(@Autowired val userService: UserService) {
private lateinit var userName: String
private lateinit var password: ByteArray
@BeforeAll
fun beforeTest() {
userName = "test"
password = "123456".toByteArray()
userService.createUser(userName, password, "Test", false)
val user = User(userName, genHashedPassword(password, userService.getLoginSalt(userName)))
userService.initService(user)
}
@Test
@Rollback
fun `delete user`() {
userService.deleteUser()
Assertions.assertNull(userService.findUser())
}
@Test
@Rollback
fun `get login salt`() {
Assertions.assertEquals(userService.getLoginSalt(userName).contentEquals(ByteArray(0)), false)
}
@Test
@Rollback
fun `validate User`() {
Assertions.assertEquals(userService.validateUser(), true)
}
}
英文:
I'm using Spring boot with Kotlin and database which I use is PostgreSQL. When I wrote the test, I found that @Rollback
does not working.
application-test.properties
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/magnus
spring.datasource.username=postgres
spring.datasource.password=123456
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.database=POSTGRESQL
IUserInfoRepository.java
interface IUserInfoRepository : JpaRepository<UserInfo, UUID> {
fun findByUserName(name: String): UserInfo?
@Transactional
fun deleteByUserName(name: String): Int
}
UserService.java
@Service
class UserService(@Autowired val userInfoRepository: IUserInfoRepository) {
private lateinit var user: User
fun initService(user: User) {
this.user = user;
}
fun createUser(userName: String, password: ByteArray, nickName: String, isAdmin: Boolean): Boolean {
if (userInfoRepository.findByUserName(userName) != null) {
return false
}
val hashCodeTable = HashCodeTable(RandomStringUtils.randomAscii(16).toByteArray())
val hashedPassword = genHashedPassword(password, hashCodeTable.salt1)
userInfoRepository.save(UserInfo(userName, nickName, hashedPassword, hashCodeTable, isAdmin))
return true
}
fun deleteUser(): Boolean {
return validateUserAndDo {
userInfoRepository.deleteByUserName(user.userName)
true
}
}
fun findUser(): UserInfo? {
return userInfoRepository.findByUserName(user.userName)
}
fun validateUser(): Boolean {
return (findUser()?.hashedPassword?.contentEquals(user.password)) ?: false
}
fun getLoginSalt(userName: String): ByteArray {
return userInfoRepository.findByUserName(userName)?.hashCodeTable?.salt1 ?: ByteArray(0)
}
fun changePassword(newPassword: ByteArray): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val hashedPassword = genHashedPassword(newPassword, oldUser.hashCodeTable.salt1)
val newUser = UserInfo(oldUser.userName, oldUser.nickName, hashedPassword, oldUser.hashCodeTable, oldUser.isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
private fun validateUserAndDo(something: () -> Boolean): Boolean {
if (validateUser()) {
return something.invoke()
}
return false
}
fun changeNickName(newNickName: String): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val newUser = UserInfo(oldUser.userName, newNickName, oldUser.hashedPassword, oldUser.hashCodeTable, oldUser.isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
fun changePermission(isAdmin: Boolean): Boolean {
return validateUserAndDo() {
val oldUser = findUser()!!
val newUser = UserInfo(oldUser.userName, oldUser.nickName, oldUser.hashedPassword, oldUser.hashCodeTable, isAdmin, oldUser.subscriptionTable, oldUser.id, oldUser.categoryTables)
userInfoRepository.save(newUser)
true
}
}
fun isPermission(): Boolean {
return userInfoRepository.findByUserName(user.userName)?.isAdmin ?: false
}
fun deleteAll(): Boolean {
userInfoRepository.deleteAll()
return true
}
}
UserServiceTest.java
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserServiceTest(@Autowired val userService: UserService) {
private lateinit var userName: String
private lateinit var password: ByteArray
@BeforeAll
fun beforeTest() {
userName = "test"
password = "123456".toByteArray()
userService.createUser(userName, password, "Test", false)
val user = User(userName, genHashedPassword(password, userService.getLoginSalt(userName)))
userService.initService(user);
}
@Test
@Rollback
fun `delete user`() {
userService.deleteUser()
Assertions.assertNull(userService.findUser())
}
@Test
@Rollback
fun `get login salt`() {
Assertions.assertEquals(userService.getLoginSalt(userName).contentEquals(ByteArray(0)), false)
}
@Test
@Rollback
fun `validate User`() {
Assertions.assertEquals(userService.validateUser(), true)
}
}
答案1
得分: 1
关于 @Transactional,Spring 文档指出:
使用 @Transactional 对测试方法进行注解会导致测试在一个事务中运行,这个事务默认情况下会在测试完成后自动回滚。如果一个测试类被注解为 @Transactional,在该类层次结构内的每个测试方法都将在事务中运行。没有被注解为 @Transactional(无论是在类级别还是方法级别)的测试方法将不会在事务中运行。
关于 @Rollback 注解,Spring 文档指出:
指示是否在测试方法完成后回滚事务。如果为 true,则回滚事务;否则,提交事务(还可以参见 @Commit 注解)。在 Spring TestContext 框架中,集成测试的回滚语义默认为 true,即使没有显式声明 @Rollback。
当作为类级别的注解声明时,@Rollback 为测试类层次内的所有测试方法定义了默认的回滚语义。当作为方法级别的注解声明时,@Rollback 为特定的测试方法定义了回滚语义,可能会覆盖类级别的 @Rollback 或 @Commit 语义。
请参考:https://docs.spring.io/spring/docs/4.2.5.RELEASE/spring-framework-reference/html/integration-testing.html#testcontext-tx
英文:
With regards @Transactional, the Spring Documentation states :
> Annotating a test method with @Transactional causes the test to be run
> within a transaction that will, by default, be automatically rolled
> back after completion of the test. If a test class is annotated with
> @Transactional, each test method within that class hierarchy will be
> run within a transaction. Test methods that are not annotated with
> @Transactional (at the class or method level) will not be run within a
> transaction.
With regards the @Rollback Annotation, the Spring Documentation states:
> Indicates whether the transaction for a transactional test method
> should be rolled back after the test method has completed. If true,
> the transaction is rolled back; otherwise, the transaction is
> committed (see also @Commit). Rollback semantics for integration tests
> in the Spring TestContext Framework default to true even if @Rollback
> is not explicitly declared.
>
> When declared as a class-level annotation, @Rollback defines the
> default rollback semantics for all test methods within the test class
> hierarchy. When declared as a method-level annotation, @Rollback
> defines rollback semantics for the specific test method, potentially
> overriding class-level @Rollback or @Commit semantics.
Please Refer To : https://docs.spring.io/spring/docs/4.2.5.RELEASE/spring-framework-reference/html/integration-testing.html#testcontext-tx
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论