You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于JPA @Access注解的使用验证及测试实现求助

关于JPA @Access注解工作机制的测试验证问题

我对JPA的@Access注解工作机制存在疑惑。已知默认访问类型由@Id的位置决定:@Id标注在字段上时,默认访问类型是AccessType.FIELD;标注在Getter方法上时,默认是AccessType.PROPERTY。

我写了如下Student实体类:

@Entity
public class Student  {
    @Id
    @GeneratedValue
    private Long ID;

    private String firstName;

    @Access(value= AccessType.PROPERTY)
    private String lastName;

    public String getFirstName() {
        System.out.println("~~~~~~~~getFirstName~~~~~~~~~");
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = "FirstName: " + firstName;
        System.out.println("~~~~~~~~setFirstName~~~~~~~~~");
    }

    public String getlastName() {
        System.out.println("~~~~~~~~getlastName~~~~~~~~~");
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = "LastName: "+ lastName;
        System.out.println("~~~~~~~~setLastname~~~~~~~~~");
    }
}

我在Setter方法里加了前缀("FirstName: "和"LastName: ")来判断方法是否被调用。因为@Id标注在字段上,默认访问类型是AccessType.FIELD,但我给lastName字段指定了AccessType.PROPERTY。

我理解这种情况下,JPA保存到数据库时:

  • firstName字段不会带前缀(因为不会调用setFirstName)
  • lastName字段会带前缀(因为会调用setLastName)

但我不知道怎么写测试来验证这个逻辑,以下是未完成的测试代码:

class StudentTest {
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;

    @BeforeEach
    public void init() {
        entityManagerFactory = Persistence.createEntityManagerFactory( "in.memory.test");
        entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
    }
    @AfterEach
    public void destroy() {
        entityManager.getTransaction().commit();
        entityManager.close();
        entityManagerFactory.close();
    }

    @Test
    public void studentFirstName(){

    }

    @Test
    public void studentLastName(){

    }
}

请指导如何实现测试验证上述逻辑。


测试实现方案

核心思路

要验证访问类型的差异,需要:

  1. 保存实体后,清空EntityManager的缓存(避免直接从内存取对象),再重新查询实体
  2. 对比查询结果中字段的值是否符合预期(是否带前缀)
  3. 也可以结合日志输出判断Getter/Setter是否被调用,但数据库值的对比是更可靠的验证方式

完整测试代码实现

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import static org.junit.jupiter.api.Assertions.*;

class StudentTest {
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;

    @BeforeEach
    public void init() {
        entityManagerFactory = Persistence.createEntityManagerFactory("in.memory.test");
        entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
    }

    @AfterEach
    public void destroy() {
        if (entityManager.getTransaction().isActive()) {
            entityManager.getTransaction().commit();
        }
        entityManager.close();
        entityManagerFactory.close();
    }

    @Test
    public void testFirstNameUsesFieldAccess() {
        // 创建实体并设置字段值
        Student student = new Student();
        student.setFirstName("Alice");
        student.setLastName("Smith");

        // 保存实体
        entityManager.persist(student);
        Long studentId = student.getID();

        // 清空EntityManager缓存,强制从数据库查询
        entityManager.flush();
        entityManager.clear();

        // 重新查询实体
        Student retrievedStudent = entityManager.find(Student.class, studentId);

        // 验证:firstName没有前缀(因为FIELD访问直接操作字段,setFirstName的前缀不会被持久化)
        assertEquals("Alice", retrievedStudent.getFirstName());
    }

    @Test
    public void testLastNameUsesPropertyAccess() {
        Student student = new Student();
        student.setFirstName("Bob");
        student.setLastName("Johnson");

        entityManager.persist(student);
        Long studentId = student.getID();

        entityManager.flush();
        entityManager.clear();

        Student retrievedStudent = entityManager.find(Student.class, studentId);

        // 验证:lastName带有前缀(因为PROPERTY访问会调用setLastName,前缀被持久化到数据库)
        assertEquals("LastName: Johnson", retrievedStudent.getlastName());
    }
}

关键说明

  • flush() 和 clear() 的作用:调用flush()会把EntityManager中的持久化操作同步到数据库;clear()会清空EntityManager的一级缓存,这样后续的find()操作会从数据库重新加载数据,而不是直接返回内存中的对象,确保验证的是数据库实际存储的值。
  • 断言逻辑:
    • firstName用FIELD访问,JPA直接操作字段firstName,所以数据库中存储的是原始值("Alice"),不会带上setFirstName里的前缀。
    • lastName用PROPERTY访问,JPA会调用setLastName(),所以数据库中存储的是带前缀的值("LastName: Johnson")。
  • 日志辅助验证:运行测试时,你会看到~~~~~~~~setLastName~~~~~~~~~的输出,但不会看到~~~~~~~~setFirstName~~~~~~~~~的输出(除非主动调用getFirstName),这也能侧面验证访问类型的差异。

内容的提问来源于stack exchange,提问作者Romillion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:30:21