spring-data-jpa 抛出异常和事务回退后更新实体字段值

owfi6suc  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(194)

我已经开发了一个测试项目来重现此问题。
这是一个项目结构:

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>value-updated-after-fail-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

</project>

Persone.java 档案:

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
@ToString
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;

    @Column(nullable = false)
    @NonNull
    String name;
}

PersonRepository.java 档案:

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {}

PersonService.java 档案:

@Component
public class PersonService {

    private final PersonRepository repository;

    public PersonService(PersonRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public Person create(String name) {
        return repository.save(new Person(name));
    }

    @Transactional
    public Person save(Person person) {
        if(StringUtils.isBlank(person.getName())) {
            throw new RuntimeException();
        }
        Person personFromDB = getById(person.getId());
        personFromDB.setName(person.getName());
        return repository.save(personFromDB);
    }

    @Transactional
    public Person getById(Long id) {
        return repository.findById(id)
                .orElseThrow(NullPointerException::new);
    }

    @Transactional
    public void deleteAll() {
        repository.deleteAll();
    }
}

application.properties 档案:

spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

TestApplicationConfiguration.java 档案

@SpringBootConfiguration
@EnableAutoConfiguration
@EnableJpaRepositories
@EntityScan("net.example.model")
@ComponentScan(basePackages = "net.example")
public class TestApplicationConfiguration {}

PersonServiceTest.java 档案:

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class PersonServiceTest {

    @Autowired
    private PersonService service;

    @AfterEach
    void tearDownEach() {
        service.deleteAll();
    }

    @Test
    void rename() {
        String expected = "name";
        Person person = service.create(expected);
        Person personFromDB = service.getById(person.getId());
        personFromDB.setName("");
        assertThrows(RuntimeException.class, () -> service.save(personFromDB));
        assertEquals(expected, service.getById(personFromDB.getId()).getName());
    }
}

**问题:**最后一个声明失败

org.opentest4j.AssertionFailedError:
Expected :name
Actual   :

"我已经试过补救了“
1.我尝试删除PersonService#getById方法的@Transactional注解,以避免该高速缓存中获取实体。-这没有解决问题
1.我尝试将spring.cache.type=none添加到application.properties文件以禁用该高速缓存。-这没有解决问题
"为什么我觉得该高速缓存“
调试时,我发现PersonService#getById()方法并不返回实际数据,但该方法返回了一个标题已更改的缓存对象。
也许我没有正确地开发测试,也许我应该改变保存更改数据的方法。
请分享最佳实践和文章,以便更好地了解如何更新数据以及如何正确配置和编写Sping Boot 应用程序的测试。

2izufjch

2izufjch1#

非常感谢Andrey B. Panfilov的评论。
我研究了@Transactional和Hibernate的一级缓存。实际上,用@DataJpaTest注解的类中的每个测试方法调用都会创建、运行和回滚一个事务。每个事务都会创建和关闭Hibernate会话。正如我们所知,一级缓存一直存在到会话关闭。这就是为什么它也被称为会话缓存的原因。
你可以在下面的截图中看到证据:

在第一个屏幕截图中,您可以看到SpringExtension(在@DataJpaTest注解中定义)在调用每个测试之前打开一个新会话。

在第二个屏幕截图中,您可以看到SpringExtension在调用每个测试后关闭会话。
我决定覆盖默认的事务传播:@Transactional(propagation = Propagation.NEVER)调用方法时不创建事务,如果在现有事务中调用方法,则抛出异常
帮助我的链接:
1.数据存取

  1. Transaction Propagation and Isolation in Spring @Transactional
  2. Transaction Propagation with illustrations
  3. Hibernate Caching - First Level Cach

相关问题