Я использую следующую модель:
@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();
}
}
Приложение является ванильным, не использует никаких аннотаций, связанных с транзакциями (но основное аннотируется с помощью @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 используют транзакцию, но, насколько мне известно, до тех пор, пока мы не переносим их в транзакцию более высокого уровня, она должна завершиться после завершения метода. Как это возможно, что я не получаю LazyInitializatioExceptions при попытке доступа к roler или user -> role -> users ?? Это ожидаемое поведение?
Это то, что показывает отладчик 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 игнорирует ленивую загрузку?




Spring Boot по умолчанию регистрирует OpenEntityManagerInViewInterceptor для применения шаблона Open EntityManager in View к разрешить отложенную загрузку в веб-представлениях. Если вы не хотите такого поведения, вам следует установить для spring.jpa.open-in-view значение false в вашем application.properties.
Обновлено: (после редактирования вопроса)
действительно, у вас должен быть 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);
}
}
Я нашел время, чтобы разделить код пополам, пока не преобразовал пример в минимальное выражение и большую часть соглашения о загрузке. Последний блок резюмирует очень важный вывод, который сильно сужает объем проблемы.
Спасибо! Но, похоже, это не так, все еще не терпится получить свойство. Я не использую представления, по крайней мере, явно, просто выполняю контроллер через тест и отлаживаю пользователя при вызове репозитория.