Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/400.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 避免方法之间的hibernate会话破坏_Java_Spring_Hibernate_Spring Security_Spring Boot - Fatal编程技术网

Java 避免方法之间的hibernate会话破坏

Java 避免方法之间的hibernate会话破坏,java,spring,hibernate,spring-security,spring-boot,Java,Spring,Hibernate,Spring Security,Spring Boot,我正在编写一个springboot应用程序(springboot1.2.2、spring4.1.5),它本质上是一个restapi。我已经设置了SpringSecurity,以便在每次HTTP请求时自动加载用户、检查授权等。用户存储在数据库中,我使用hibernate访问数据库中的所有数据 简而言之,问题是:在预筛选期间,我使用hibernate在每个HTTP请求上从数据库加载一个用户对象。我后来在RestController的参数中接收到这个对象,但它不再连接到任何hibernate会话。因此

我正在编写一个springboot应用程序(springboot1.2.2、spring4.1.5),它本质上是一个restapi。我已经设置了SpringSecurity,以便在每次HTTP请求时自动加载用户、检查授权等。用户存储在数据库中,我使用hibernate访问数据库中的所有数据

简而言之,问题是:在预筛选期间,我使用hibernate在每个HTTP请求上从数据库加载一个
用户
对象。我后来在RestController的参数中接收到这个对象,但它不再连接到任何hibernate会话。因此,如果不首先从数据库中重新读取用户对象,我就无法访问该对象中的任何惰性集合

问题是:是否有办法在预筛选期间启动hibernate会话并将其保留到HTTP请求完成

长版本,代码:首先,我设置了spring security,以便它将加载我的用户对象作为授权的一部分:
安全配置:

@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('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
    ...
}
我还想将用户对象注入控制器方法,所以我这样做:

@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
这是可行的,但这似乎容易出错,而且没有必要。我实际上从数据库中加载了两次
user
。考虑到我刚刚在前面的方法中加载了它,
DomainUserDetailsService.loadUserByUsername
,这似乎是一种浪费


有没有办法在预筛选期间启动hibernate会话并将其保持到HTTP请求完成?

有“每个请求会话”模式。看一下描述。如果您使用的是spring,那么有一个实现


请注意,它的使用是连续的:

经过一些考虑,我选择了user1675642提供的答案:我决定对每个HTTP请求使用单个hibernate会话(可能有嵌套会话)。对于我的特殊情况,我看不出这种方法有任何问题,因为我总是为每个请求打开数据库连接,所以在堆栈跟踪中打开几个方法并不会产生任何影响(实际上它确实会产生影响,因为我不必打开它两次)

但是,我无法利用提供的框架。我得到了一个“java.lang.IllegalArgumentException:ServletContext不能为null”异常。我无法找出我的配置的哪一部分导致了这个问题,所以我继续写我自己的实现:

首先,您必须声明以下筛选器:

@Component
public class OpenSessionFilter extends OncePerRequestFilter {
    @Autowired
    OpenSessionService sessionService;

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
        sessionService.doFilterInTransaction(request, response, filterChain);
    }
}
然后,您必须声明以下服务:

@Service
public class OpenSessionService {
    @Transactional
    public void doFilterInTransaction(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(request, response);
    }
}

然而,这回答了我的问题,但并不能解决我的问题。问题是,Spring安全缓存身份验证主体。这意味着身份验证筛选器将偶尔从数据库加载用户一次。这意味着,来自SpringSecurity的用户对象仍然可以独立于hibernate上下文(因为它是在前面的一个请求中创建的)。这迫使我在用户对象周围创建一个包装器,当需要时,用户将手动重新将用户对象重新连接到一个新的Hibernate会话。

当用户第一次加载时,为什么不考虑显式加载API密钥集合?或者另一种选择是在需要时为给定用户加载api密钥集合。无论如何,您不必重新加载用户实体。如果你需要更多的解释,让我知道。@PavlaNováková谢谢你的建议,这是一个很好的解决问题的方法。但是,我仍然更喜欢一种允许我以常规方式访问变量的解决方案,如user.getKeys()。使用我建议的解决方案,您当然可以以user.getKeys()的身份访问给定用户的api键集合-在第一种情况下,您可以初始化调用user.getKeys()的集合例如,在loadUserByUsername方法中,然后像往常一样访问集合-user.getKeys(),而不获取LazyInitializationException;在第二种情况下(稍后加载显式密钥集合),方法逻辑是:根据给定的用户id加载api密钥,然后简单地调用user.setKeys(loadedKeys)并访问它们user.getKeys()。在我看来,视图中的开放会话是一种糟糕的做法。感谢您提供这些过滤器,它们帮助我们解决了需求,并提供了来自的RefreshingSerDetailsSecurityContentExtrepository。我们的应用程序非常以用户为中心,因此大多数控制器方法接收用户对象@AuthenticationPrincipal。在RefreshingSerDetailsSecurityContextExtrapository的帮助下,每次加载页面时都会刷新它,从而允许实时更改权限。与这些过滤器一起,实体被正确绑定到正确的Jpa会话,并且可以直接在控制器或其视图中处理。