Java 与@DataJpaTest和h2数据库保持多对多关系不工作

Java 与@DataJpaTest和h2数据库保持多对多关系不工作,java,jpa,spring-data-jpa,many-to-many,Java,Jpa,Spring Data Jpa,Many To Many,我有两个实体用户: @Entity @Table(name = "user") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "username", unique = true) private String u

我有两个实体<代码>用户:

@Entity
@Table(name = "user")
@Data
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "username", unique = true)
private String username;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();

public void addRole(Role role) {
    this.roles.add(role);
}

public void addUser(User user) {
    this.users.add(user);
}
我正在使用SpringDataJPA并为
用户
角色
创建存储库:

@Entity
@Table(name = "role")
@Data
public class Role {

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

@Column(name = "name", unique = true)
private String name;

@ManyToMany(mappedBy = "roles", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<User> users = new HashSet<>();
public interface UserRepository extends JpaRepository<User, Long> {

public Optional<User> findByUsername(String username);

}
assertTrue(findedRole.getUsers().contains(findeUser))
失败。 如果在应用程序中使用相同的方法(不将用户添加到角色),则一切正常,但在测试中它不起作用

我的申请:

@Component
public class InitializeData implements CommandLineRunner {

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Transactional
@Override
public void run(String... args) throws Exception {

    Role role1 = new Role();
    role1.setName("Admin");

    Role role2 = new Role();
    role2.setName("Teacher");

    Role role3 = new Role();
    role3.setName("User");

    roleRepository.saveAll(Arrays.asList(role1, role2, role3));

    String username = "User1";
    String password = "password";
    String firstName = "name";
    String lastName = "last name";

    User user = new User();
    user.setUsername(username);
    user.setPassword(password);
    user.setFirstName(firstName);
    user.setLastName(lastName);

    Role findedUserRole = roleRepository.findByName("User").get();
    Role findedAdminRole = roleRepository.findByName("Admin").get();

    user.addRole(findedUserRole);
    user.addRole(findedAdminRole);

    userRepository.save(user);

}
}

一切正常!
发生了什么事?

您落入了JPAs一级缓存的陷阱

您的完整测试发生在单个事务中,因此是会话

JPA的核心原则之一是,在给定类和id的会话中,
EntityManager
将始终返回相同的实例

因此,当您执行
Role findedRole=roleRepository.findByName(“用户”).get()时在测试中,您实际上并没有重新加载角色。您得到的正是您在测试的设置部分创建的实例,该实例仍然没有用户

解决这一问题的方法取决于您实际想要实现的目标

  • 对于双向关系,您应该始终保持它们的同步,即对
    user.addRole(..)
    的调用应该更新作为参数传递的
    Role
    。有关详细信息,请参阅

    虽然这会使您的测试变为绿色,但实际上它不会测试数据是否写入数据库,是否可以按预期再次加载。为此,我喜欢使用以下方法之一

  • 刷新
    清除
    。在测试中注入
    EntityManager
    。保存实体后,调用它,以便将更改写入数据库,然后清除一级缓存。这将确保后续加载操作实际命中数据库并加载新实例

  • 明确说明事务。从测试中删除
    @Transactional
    注释。使用将
    TransactionTemplate
    注入到测试和运行设置、保存部分以及加载和断言部分的单独事务中。我认为这使得测试的意图更加明显。但是现在您的事务实际上是提交的,如果您在多个测试中重用同一个数据库,这可能会导致问题


  • 您落入了JPAs一级缓存的陷阱

    您的完整测试发生在单个事务中,因此是会话

    JPA的核心原则之一是,在给定类和id的会话中,
    EntityManager
    将始终返回相同的实例

    因此,当您执行
    Role findedRole=roleRepository.findByName(“用户”).get()时在测试中,您实际上并没有重新加载角色。您得到的正是您在测试的设置部分创建的实例,该实例仍然没有用户

    解决这一问题的方法取决于您实际想要实现的目标

  • 对于双向关系,您应该始终保持它们的同步,即对
    user.addRole(..)
    的调用应该更新作为参数传递的
    Role
    。有关详细信息,请参阅

    虽然这会使您的测试变为绿色,但实际上它不会测试数据是否写入数据库,是否可以按预期再次加载。为此,我喜欢使用以下方法之一

  • 刷新
    清除
    。在测试中注入
    EntityManager
    。保存实体后,调用它,以便将更改写入数据库,然后清除一级缓存。这将确保后续加载操作实际命中数据库并加载新实例

  • 明确说明事务。从测试中删除
    @Transactional
    注释。使用将
    TransactionTemplate
    注入到测试和运行设置、保存部分以及加载和断言部分的单独事务中。我认为这使得测试的意图更加明显。但是现在您的事务实际上是提交的,如果您在多个测试中重用同一个数据库,这可能会导致问题

  • @ExtendWith(SpringExtension.class)
    @DataJpaTest
    class UserRepositoryTest {
    
    @Autowired
    UserRepository userRepository;
    @Autowired
    RoleRepository roleRepository;
    
    @BeforeEach
    void setUp() {
    
        Role role1 = new Role();
        role1.setName("Admin");
    
        Role role2 = new Role();
        role2.setName("Teacher");
    
        Role role3 = new Role();
        role3.setName("User");
    
        roleRepository.saveAll(Arrays.asList(role1, role2, role3));
    
    }
    
    @Test
    @Transactional
    public void createNewUserTest() {
    
        String username = "User1";
        String password = "password";
        String firstName = "name";
        String lastName = "last name";
    
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setFirstName(firstName);
        user.setLastName(lastName);
    
        Role findedUserRole = roleRepository.findByName("User").get();
        Role findedAdminRole = roleRepository.findByName("Admin").get();
    
        user.addRole(findedUserRole);
        user.addRole(findedAdminRole);
    
        userRepository.save(user);
    
        User findedUser = userRepository.findByUsername(username).get();
        Role findedRole = roleRepository.findByName("User").get();
    
    
        assertEquals(firstName,findedUser.getFirstName());
        assertEquals(2, findedUser.getRoles().size());
        assertTrue(findedRole.getUsers().contains(findedUser));
    }
    }
    
    @Component
    public class InitializeData implements CommandLineRunner {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RoleRepository roleRepository;
    
    @Transactional
    @Override
    public void run(String... args) throws Exception {
    
        Role role1 = new Role();
        role1.setName("Admin");
    
        Role role2 = new Role();
        role2.setName("Teacher");
    
        Role role3 = new Role();
        role3.setName("User");
    
        roleRepository.saveAll(Arrays.asList(role1, role2, role3));
    
        String username = "User1";
        String password = "password";
        String firstName = "name";
        String lastName = "last name";
    
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setFirstName(firstName);
        user.setLastName(lastName);
    
        Role findedUserRole = roleRepository.findByName("User").get();
        Role findedAdminRole = roleRepository.findByName("Admin").get();
    
        user.addRole(findedUserRole);
        user.addRole(findedAdminRole);
    
        userRepository.save(user);
    
    }