如何解决与@DataJpaTest保持多对多关系,并且h2数据库不起作用
我有两个实体。 User
:
@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);
}
Role
:
@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<>();
我正在使用Spring Data JPA,并为User
和Role
创建存储库:
public interface UserRepository extends JpaRepository<User,Long> {
public Optional<User> findByUsername(String username);
}
public interface RoleRepository extends JpaRepository<Role,Long> {
public Optional<Role> findByName(String name);
public void deleteByName(String name);
}
我编写了一个测试,该测试创建了一个新的User
并将Role
添加到User
,但是Role doesn't get added to the
User`,并且测试失败:
@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));
}
}
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,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);
}
}
一切正常! 发生了什么事?
解决方法
您陷入了JPA一级缓存的陷阱。
您的完整测试在单个事务中进行,因此在会话中进行。
JPA的核心原则之一是,在给定类和ID的会话中,EntityManager
将始终返回相同的实例。
因此,当您在测试中执行Role findedRole = roleRepository.findByName("User").get();
时,实际上并没有重新加载角色。您将获得在测试的设置部分创建的确切实例,该实例仍然没有用户。
解决此问题的方法取决于您实际想要实现的目标。
-
对于双向关系,应始终保持它们同步,即,对
user.addRole(..)
的调用应更新作为参数传递的Role
。有关详细信息,请参见https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/。虽然这会使您的测试变为绿色,但实际上并不会测试数据是否已写入数据库,是否可以按预期再次加载。为此,我喜欢使用以下方法之一。
-
flush
和clear
。获得注入测试的EntityManager
。保存实体后,调用flush
,以便将更改写入数据库,然后clear
清除一级缓存。这样可以确保后续的加载操作实际上可以访问数据库并加载新实例。 -
明确交易。从测试中删除
@Transactional
批注。使用TransactionManager.execute
将TransactionTemplate
注入到测试和运行设置,保存部分以及装入和声明部分中,分别进行事务处理。我认为这使测试的意图更加明显。但是现在您的事务实际上已经提交,如果您将同一个数据库重新用于多个测试,则可能会导致问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。