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);
}