Я пишу приложение весеннего ботинка (весенний ботинок 1.2.2, весна 4.1.5), что по существу является REST API. Я настроил весеннюю безопасность для автоматической загрузки пользователя, проверки авторизации и т. Д. При каждом HTTP-запросе. Пользователи хранятся в базе данных, я использую hibernate для доступа ко всем данным внутри этой базы данных.Избегайте разрушения сеанса гибернации между методами
Короче говоря, проблема заключается в следующем: Я загружаю объект user
из своей базы данных, используя спящий режим для каждого запроса HTTP во время PreFilter. Позже я получаю этот самый объект в параметрах моего RestController, но он больше не привязан к сеансу спящего режима. Из-за этого я не могу получить доступ к ленивым коллекциям внутри пользовательского объекта, не перечитывая его сначала из базы данных.
Вопрос: Есть ли способ начать сеанс гибернации во время PreFilter и сохранить его до тех пор, пока HTTP-запрос не будет завершен?
Длинная версия, с кодом: Прежде всего, я установка пружина безопасность поэтому она будет загружать мой пользовательский объект как часть авторизации: конфигурации
безопасности: Детали
@Configuration
@Component
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
...
}
пользователя служба, который загружает пользователей из базы данных:
@Service
@Transactional
public class DomainUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException("User '" + username + "' not found");
return user;
}
}
Я также хочу, чтобы ввести объект пользователя в методы контроллера, так что я делаю это так:
@PreAuthorize("hasAuthority('VIEW_ACCOUNT_INFO')")
@Transactional(readOnly = true)
@RequestMapping(value = "/user/api-keys/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public KeysResponse getApiKeys(
@AuthenticationPrincipal User user
) {
return new KeysResponse(user);
}
Это прекрасно работает. Однако, если я пытаюсь ленивой нагрузки коллекцию, связанную с пользователем, я получаю исключение:
@Table(name = "users")
public class User implements UserDetails {
...
@Column
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<ApiKey> keys;
...
}
user.getKeys(); <-- Exception here
failed to lazily initialize a collection of role: com.sebbia.pushwit.core.domain.User.keys, could not initialize proxy - no Session
Это происходит потому, что hiberante сессия была закрыта после того, как объект пользователя был загружен во время ПРЕДФИЛЬТРА, и теперь переменная user
отсоединяется. Для того, чтобы присоединить его снова, я должен повторно загрузить его из базы данных:
@PreAuthorize("hasAuthority('MANAGE_ACCOUNT_INFO')")
@Transactional(readOnly = false)
@RequestMapping(value = "/user/api-keys/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public KeysResponse addApiKey(
@RequestParam(required = true) String name,
@RequestParam(required = true) Role role,
@AuthenticationPrincipal User user
) throws ApiKeyAlreadyExistsException {
// I have to re-attach user object to new session
Session session = (Session) entityManager.getDelegate();
user = (User) session.merge(user);
// Now I can change user or load any of it's collections
...
}
Это работает, но это, кажется, подвержены ошибкам и ненужные. Я по существу загружаю user
из базы данных дважды. Это кажется пустой тратой, учитывая, что я только что загрузил ее в предыдущем методе, DomainUserDetailsService.loadUserByUsername
.
Есть ли способ начать сеанс гибернации во время PreFilter и сохранить его до тех пор, пока HTTP-запрос не будет завершен?
Почему вы не рассматриваете явную загрузку коллекции ключей api при первой загрузке пользователя? Или другой вариант - загрузить только коллекцию ключей api для данного пользователя, когда это необходимо. В любом случае вам не нужно перезагружать пользовательский объект. Если вам нужно больше объяснений, дайте мне знать. –
@ PavlaNováková Спасибо за ваше предложение, это достойное решение проблемы. Тем не менее, я по-прежнему предпочитаю решение, которое позволяет мне обращаться к переменным обычным способом, например user.getKeys(). – Alexey
С решением, которое я предложил, вы можете получить доступ к наборам ключей api для данного пользователя как user.getKeys() - в первом случае вы можете инициализировать вызов коллекции user.getKeys(). Size() в методе loadUserByUsername, например а затем получить доступ к коллекции как обычно - user.getKeys(), не получив LazyInitializationException, а во втором случае (явная загрузка набора ключей позже) логика метода будет: загрузить api-ключи с помощью данного идентификатора пользователя, а затем просто вызвать user.setKeys (загруженныеKeys) и получить к ним доступ user.getKeys(). Открытая сессия - это плохая практика, на мой взгляд. –