Ленивые инициализации игнорируются в Spring Data JPA при использовании @WebAppConfiguration вместо @SpringBootTest в ControllerTest

Я использую следующую модель:

@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 при отображении пользователей в ролях пользователя, чего стоит:

Ленивые инициализации игнорируются в Spring Data JPA при использовании @WebAppConfiguration вместо @SpringBootTest в ControllerTest

Ведение журнала 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 игнорирует ленивую загрузку?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
581
1

Ответы 1

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

Спасибо! Но, похоже, это не так, все еще не терпится получить свойство. Я не использую представления, по крайней мере, явно, просто выполняю контроллер через тест и отлаживаю пользователя при вызове репозитория.

Whimusical 20.05.2018 12:43

Я нашел время, чтобы разделить код пополам, пока не преобразовал пример в минимальное выражение и большую часть соглашения о загрузке. Последний блок резюмирует очень важный вывод, который сильно сужает объем проблемы.

Whimusical 25.05.2018 18:43

Другие вопросы по теме