我使用的是以下型号:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String username;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "USER_ROLE", joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
private List<Role> roles;
(...)
}
@Entity
public class Role {
private String name;
@ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
private List<User> users;
(...)
}
导致以下存储库:
public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}
然后是一个控制器,如:
@RestController
@RequestMapping("/test")
public class Controller {
@Autowired
private UserRepository userRepository;
@GetMapping
public void test(@RequestParam final String u) {
if(!this.userRepository.findByUsername(u).getRoles().get(0).getUsers().get(0).getRoles().get(0).getName().equals("Role1")) throw new RuntimeException();
}
}
应用程序是vanilla,不使用任何与事务相关的注释(但main使用@SpringBootApplication注释)。
@SpringBootApplication
public class Application {
public static void main(final String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
我只在application.properties中使用了一个属性:spring.jpa.open-in-view=false
测试结果如下:
@RunWith(SpringRunner.class)
@WebAppConfiguration @ContextConfiguration(classes = Application.class)
@AutoConfigureMockMvc
public class ControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void test() throws Exception {
this.mockMvc.perform(get("/test").param("u", "user1")).andExpect(status().isOk());
}
}
然后我的问题是,当调试看起来所有的实体图都被完全急切地获取,并且测试返回成功,因为控制器没有失败。
我知道JPA存储库方法使用事务,但据我所知,只要我们不将它们包装在更高级别的事务上,它就应该在方法结束后完成。在尝试访问roler或user时,我怎么可能没有得到LazyInitializatioExceptions - &gt;角色 - &gt;用户??这是预期的行为吗?
这是Eclipse调试器在用户角色中显示用户时所显示的内容,值得:
Hibernate日志记录显示了在找到用户时如何急切地获取关系:
12:47:24.890 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection: ..domain.User.roles
12:47:24.937 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [User.roles#1002]
12:47:24.953 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving associations for [...domain.Role#5]
12:47:24.953 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [...domain.Role#5]
12:47:24.953 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: ...domain.User.roles
12:47:24.968 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [...domain.User.roles#1002]
12:47:24.968 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: ....domain.User.roles
12:47:24.968 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
12:47:24.968 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
编辑:我发现如果我通过现代@WebAppConfiguration
更改ControllerTest
上的@SpringBootTest
行,则会获得LazyInitializationException
。为什么@WebAppConfiguration
忽略了懒惰的提取?
答案 0 :(得分:1)
Spring Boot默认注册OpenEntityManagerInViewInterceptor
以在View模式中应用Open EntityManager,以允许在Web视图中延迟加载。如果您不想要此行为,则应在spring.jpa.open-in-view
中将application.properties
设置为false。
编辑:(编辑问题后)
确实你应该有一个 LazyInitializationException 。我的猜测是,在你的测试中,Session
仍然是开放的?
如果您使用@SpringBootTest
进行测试,则会获得LazyInitializationException
@RunWith(SpringRunner.class)
@SpringBootTest
public class LazyLoadingExceptionTest {
@Autowired UserRepository userRepository;
@Test(expected=LazyInitializationException.class)
public void showRolesTest() {
User whimusical = userRepository.findByUsername("Whimusical");
System.err.println(whimusical.getRoles());
}
}
但如果您使用@DataJpaTest
进行测试,则不会出现例外
@RunWith(SpringRunner.class)
@DataJpaTest
public class NoLazyLoadingExceptionTest {
@Autowired UserRepository userRepository;
@Test
public void showRolesTest() {
User whimusical = userRepository.findByUsername("Whimusical");;
System.err.println(whimusical.getRoles());
}
}
另一种触发 LazyInitializationException
的方法覆盖toString()
User
@Override
public String toString() {
return username + " with roles: " + roles;
}
并使用此Component
@Component
public class DataSetup implements CommandLineRunner{
@Override
public void run(String... args) throws Exception {
List<Role> roles = new ArrayList<>();
Role role = new Role();
role.setName("user");
roles.add(roleRepository.save(role));
User whimusical = new User();
whimusical.setUsername("Whimusical");
whimusical = userRepository.save(whimusical);
whimusical.setRoles(roles);
whimusical = userRepository.save(whimusical);
userRepository.findAll().forEach(System.err::println);
}
}