OpenEntityManagerInViewFilter 注释配置
OpenEntityManagerInViewFilter annotation config
为了克服 LazyInitializationException 我决定使用 OpenEntityManagerInViewFilter 但我无法使用注释样式。我试过用两种方式设置它:
首先在 WebApplicationInitializer 的 onStartup 方法中:
OpenEntityManagerInViewFilter entityManagerInViewFilter = new OpenEntityManagerInViewFilter();
entityManagerInViewFilter.setEntityManagerFactoryBeanName("entityManagerFactory");
entityManagerInViewFilter.setPersistenceUnitName("defaultPersistenceUnit");
FilterRegistration.Dynamic filter = sc.addFilter("openEntityManagerInViewFilter", entityManagerInViewFilter);
filter.addMappingForUrlPatterns(null, false, "/*");
其次,通过创建新的 class 来扩展 OpenEntityManagerInViewFilter 并具有注释 @WebFilter:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter {
public MainFilter() {
setEntityManagerFactoryBeanName("entityManagerFactory");
setPersistenceUnitName("defaultPersistanceUnit");
}
}
每次我得到 "No bean named 'entityManagerFactory' is defined" 或 "No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined"。我的实体管理器工厂在@Configuration class 中定义。
如何在没有 web.xml 文件的情况下配置此过滤器?
WebApplicationInitializer 是在 Java 部署到 Servlet 3.0 容器时在 Java 中注册 servlet、过滤器和侦听器的一种很好的通用方法。但是如果你正在注册一个过滤器并且只需要将该过滤器映射到 DispatcherServlet,那么有一个
AbstractAnnotationConfigDispatcherServletInitializer 中的快捷方式。
要注册一个或多个过滤器并将它们映射到 DispatcherServlet,您只需要
要做的是覆盖 AbstractAnnotationConfigDispatcherServletInitializer 的 getServletFilters() 方法。
例如下面的getServletFilters()
方法覆盖了 AbstractAnnotationConfigDispatcherServletInitializer 中的方法
注册过滤器:
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new MyFilter() };
}
这是我的 WebAppInitializer 代码片段
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
protected Class<?>[] getRootConfigClasses() {
System.out.println("*****加载MySqlConfig*******");
return new Class<?>[] { MySqlConfig.class };
}
protected Class<?>[] getServletConfigClasses() {
System.out.println("*****加载WebConfig*******");
return new Class<?>[] { WebConfig.class };
}
protected String[] getServletMappings() {
System.out.println("*****要拦截的请求*******");
return new String[] { "/" };
}
//Register your filter here
protected Filter[] getServletFilters() {
System.out.println("*********加载filter**********");
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
因为引导过程可能非常不清楚(这可能导致配置错误),我决定给出一个详细的答案,但也 "too long; didn't read" 对于那些想立即跳转到答案的人。
TL;DR
最简单的方法是以下两个选项之一:
选项 1:OpenEntityManagerInViewInterceptor
这比 OpenEntityManagerInViewFilter 更接近 Spring,因此当您还需要使用其他 bean 配置它时可能更容易配置。
为您的网络配置使用类似这样的东西:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig implements WebMvcConfigurer {
@Autowired
private EntityManagerFactory emf;
@Override
public void addInterceptors(InterceptorRegistry registry) {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(emf);
registry.addWebRequestInterceptor(interceptor);
}
}
addInterceptors() 的重要部分,在其中创建拦截器并将其添加到 Spring Web。
并且要将 Spring Web 指向配置 class,您需要这样的东西:
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
MyServletInitializer 将被 Spring 自动检测到,因为它是 AbstractAnnotationConfigDispatcherServletInitializer, which is an implementation of WebApplicationInitializer. And Spring Web will automatically be detected by the Servlet container, because Spring Web contains a service provider implementation of ServletContainerInitializer by default, which gets detected automatically. This implementation is called SpringServletContainerInitializer.
的子class
选项 2:OpenEntityManagerInViewFilter
如果你不想使用拦截器,你可以使用过滤器。
过滤器具有更广泛的范围,其中每个 HTTP 请求都可以共享一个 entityManager,但拦截器更接近 Spring,从而更容易用它连接 bean(以备不时之需)。
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
补充说明:
- 确保您的 entityManagerFactoryBean 名称正确。请参阅 OpenEntityManagerInViewFilter 的文档,或下面我的详细描述。
- 确保配置没有被多次导入,否则你很难调试问题。有关更多信息,请参阅下面我的详细说明。
Why/when 使用 OpenEntityManagerInViewFilter
OpenEntityManagerInViewFilter 是 JPA 的 OSIV 模式(在视图中打开会话)的实现。 (Session 是 EntityManager 的本机 Hibernate 版本。因此对于 JPA,它可以称为:Open EntityManager In View 模式。)在这个模式中,单个 EntityManager 在单个 HTTP 请求中 shared/reused,并在 HTTP 请求完成时关闭。
使用 OSIV 模式的好处是:
- 与单个 HTTP 请求导致多个不同事务和实体管理器打开相比,数据库性能更好
- 允许 JPA 使用一级缓存
- 更简单的编程,您不必担心 LazyInitializationExceptions,这可能是在 entityManager 关闭并且您访问需要持久上下文的实体上的方法时引起的
- 当您使用使用 JPA 的自定义 Spring 安全过滤器时可能需要它。
但这也可能导致持久性上下文打开的时间比需要的时间长,这可能会损害性能。
有些人还将 OSIV 模式视为反模式:https://vladmihalcea.com/the-open-session-in-view-anti-pattern/ 该网页还讨论了避免 LazyInitializationExceptions 和 OSIV 模式的其他解决方案。
Servlet 3.0 和配置选项
自 Servlet 3.0 起,servlet 规范支持可插拔性,它允许您从 web.xml 切换到编程配置和注解配置。虽然注释配置不支持 web.xml 和编程配置支持的所有功能,例如 servlet 和过滤器的排序。仅使用注释时,如果您必须扩展现有过滤器才能配置它,这可能会很麻烦,就像您在此处所做的那样:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
在 Spring 中配置 Web 应用程序的一种简单方法是使用编程配置。当 Servlet >= 3.0 容器启动时(例如 Tomcat >= 7),它将搜索 web.xml 和 ServletContainerInitializer 服务提供者实现(这也需要您定义一个文件在 META-INF/services 中)。 ServletContainerInitializer 实现允许以编程方式配置 servlet 容器。
连接 Spring 到 Servlet 容器
Spring Web 默认包含 ServletContainerInitializer 的实现,称为 SpringServletContainerInitializer,因此您不必自己创建 ServletContainerInitializer 的实现。这使得框架设计者更容易配置他们的框架以与容器一起工作。
要使用它,您必须创建 Springs WebApplicationInitializer interface. This implementation is automatically discoverd by Spring (SpringServletContainerInitializer). It provides you a ServletContext 实例的实现,它允许您以编程方式配置容器(servlet 和过滤器)。
更简单的方法是创建 AbstractAnnotationConfigDispatcherServletInitializer 的子 class(这是 WebApplicationInitializer 的实现),这样您就不必直接使用 ServletContext.
配置所有内容
如何实现OSIV
实现OSIV的多种方式。最佳选择可能因项目而异。
在 OSIV 中,您希望 EntityManager 在 HTTP 请求的开头 opened/started 并在相应的 HTTP 响应结束时关闭。分界就是entityManager和transaction打开和关闭的过程。根据您使用的 Web 应用程序或框架的类型,您可以在分界的确切位置移动一点。
一些分界选项:
选项 1 - 绕过 Spring,仅注释,OpenEntityManagerInViewFilter(不推荐)
您可以绕过 Spring,方法是让 OpenEntityManagerInViewFilter 直接被 servletContainer 检测到,或者通过子class 对其进行注释并对其进行注释,或者通过在 web.xml 中指定它。
这与您的方法相似:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
不推荐这种方法,因为它有点麻烦。
选项 2 - 通过 WebApplicationInitializer、OpenEntityManagerInViewFilter 以编程方式手动
在这种方法中,您创建了 Springs WebApplicationInitializer to get hold of the ServletContext.
的实现
然后您可以手动创建和配置 openEntityManagerInViewFilter:
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
FilterRegistration.Dynamic registration = registerServletFilter(servletContext, filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE), true, "/*");
请注意,您还必须自己创建 Spring ApplicationContext 和 DispatcherServlet。有关示例,请参阅 WebApplicationInitializer 的文档。
选项 3 - 以编程方式通过 AbstractAnnotationConfigDispatcherServletInitializer、OpenEntityManagerInViewFilter(推荐)
这是在 Spring 应用程序中启用 OpenEntityManagerInViewFilter 的主要方法。
如前所述,servlet容器会自动检测Springs SpringServletContainerInitializer,然后Spring会自动检测我们的MyServletInitializer(见下文),因为它超class (AbstractAnnotationConfigDispatcherServletInitializer) 是 WebApplicationInitializer.
的实现
从 MyServletInitializer,Spring 将加载您的核心应用程序配置,即 rootConfigClasses,您可以在其中定义 JPA bean。
示例:
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
Spring 主配置文件示例:
@EnableJpaRepositories
@Configuration
@EnableTransactionManagement
public class MainConfig {
private Properties hibernateProperties() {
Properties props = new Properties();
props.setProperty("hibernate.hbm2ddl.auto", "update");
props.setProperty("hibernate.dialect", "org.hibernate.dialect.MariaDB10Dialect");
return props;
}
@Bean
public DataSource dataSource() {
Properties properties = new Properties();
properties.put("url", "jdbc:mariadb://localhost:3306/myDatabase");
properties.put("user", "root");
properties.put("password", "myPassword");
HikariConfig config = new HikariConfig();
config.setDataSourceClassName("org.mariadb.jdbc.MariaDbDataSource");
config.setMaximumPoolSize(10);
config.setDataSourceProperties(properties);
return new HikariDataSource(config);
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTxManager = new JpaTransactionManager();
jpaTxManager.setEntityManagerFactory(emf);
return jpaTxManager;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
HibernateJpaDialect jpaDialect = new HibernateJpaDialect();
emf.setJpaDialect(jpaDialect);
emf.setJpaProperties(hibernateProperties());
emf.setPackagesToScan("mypackage");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
emf.setJpaVendorAdapter(vendorAdapter);
return emf;
}
}
Spring 网络配置示例:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig {}
选项 4:无过滤器,Spring 控制器方法上的@Transactional
如果您的应用程序不需要过滤器,并且您的模板层是从 Spring 控制器中呈现的,那么您可以使用 @Transactional 来划分控制器方法,而不是使用纯 OSIV。
在这种情况下,确保在 Spring 配置上设置了 @EnableTransactionManagement
并且控制器方法用 @org.springframework.transaction.annotation.Transactional
注释
请注意,当您使用 JSP 或其他常用模板时,这不起作用,因为这样文本就不会在控制器方法中生成。相反,创建了一个模型和视图对象,然后将其转发给模板进行渲染,但是一旦控制器方法完成,entityManager 在渲染之前关闭,当您访问延迟加载的实体属性时,这可能会导致 LazyInitializationException。
选项 5 - OpenEntityManagerInViewInterceptor 而不是过滤器(推荐)
而过滤器是 servlet api 的一部分,webRequestInterceptors 是 Spring Web 的一部分,因此更接近于 Spring。
其中一个 webRequestInterceptors 是 OpenEntityManagerInViewInterceptor,它是 OpenEntityManagerInViewFilter 的拦截器版本。
文档说:“与 OpenEntityManagerInViewFilter 相比,此拦截器设置在 Spring 应用程序上下文中,因此可以利用 bean 连接。”。 =44=]
因此,在 WebApplicationInitializer(例如 AbstractAnnotationConfigDispatcherServletInitializer)中初始化 OpenEntityManagerInViewFilter 的地方,OpenEntityManagerInViewInterceptor 可以在 Spring 配置中初始化 class。
示例:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig implements WebMvcConfigurer {
@Autowired
private EntityManagerFactory emf;
@Override
public void addInterceptors(InterceptorRegistry registry) {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(emf);
registry.addWebRequestInterceptor(interceptor);
}
}
注意拦截器比过滤器更深一层。如果您有需要使用 entityManager 的过滤器,则拦截器无法提供它,但反之亦然。
关于 OpenEntityManagerInViewFilter 的使用信息
如 OpenEntityManagerInViewFilter 的文档中所述,您必须确保 Spring bean 以正确的名称存在,否则 OpenEntityManagerInViewFilter 可能会给出您描述的错误:“没有定义名为 'entityManagerFactory' 的 bean”。
来自文档:
Looks up the EntityManagerFactory in Spring's root web application
context. Supports an "entityManagerFactoryBeanName" filter init-param
in web.xml; the default bean name is "entityManagerFactory". As an
alternative, the "persistenceUnitName" init-param allows for retrieval
by logical unit name (as specified in persistence.xml).
在您的问题中,您没有说明您是如何准确定义 EntityManagerFactory 的。
如何测试是否有效
即使您没有收到 LazyInitializationException,也不意味着共享了 entitymanager。
要验证它是否正常工作,您可以在日志框架中将 org.springframework 设置为 DEBUG 日志级别。
测试时,最好使用Wget或Curl之类的工具,而不是浏览器,因为浏览器可能会触发多个HTTP请求(例如favicon.ico),导致日志不清晰。
当一个 HTTP 请求触发多个存储库中的 entityManager 请求时,所有存储库都应该使用相同的 EntityManager,因此对于单个 HTTP 请求,您应该只看到一行类似这样的内容:
21:48:46.872 [http-nio-8080-exec-5] DEBUG o.s.o.j.s.OpenEntityManagerInViewInterceptor - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
还有一个:
21:48:46.878 [http-nio-8080-exec-5] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
并且当您在多个存储库中有多个 entityManager 请求时,您应该看到它们正在使用相同的线程绑定 entityManager。所以你应该看到多行:
21:48:46.876 [http-nio-8080-exec-5] DEBUG o.s.orm.jpa.JpaTransactionManager - Found thread-bound EntityManager [SessionImpl(1847515591<open>)] for JPA transaction
配置时出现问题
在尝试将 web.xml 禁用为 web.xml.old 之类的东西时,我遇到了一个难以调试的问题,因为 IntelliJ 以某种方式在 IntelliJ 项目配置中映射到重命名的 [=207= .old,导致它仍然使用 xml 配置。
另一个问题可能是当您的 Spring 配置以某种方式被多次导入时,例如,如果您在 webConfiguration 中使用 @import 导入 mainConfiguration 并且您在 AbstractAnnotationConfigDispatcherServletInitializer 中同时指定它们,那么事情可能是多次导入,造成难以调试的问题。
如果你的配置因为有很多而不清楚,你可能想创建一个带有简单 println 消息的构造函数,以验证它只创建了一次。
为了克服 LazyInitializationException 我决定使用 OpenEntityManagerInViewFilter 但我无法使用注释样式。我试过用两种方式设置它:
首先在 WebApplicationInitializer 的 onStartup 方法中:
OpenEntityManagerInViewFilter entityManagerInViewFilter = new OpenEntityManagerInViewFilter();
entityManagerInViewFilter.setEntityManagerFactoryBeanName("entityManagerFactory");
entityManagerInViewFilter.setPersistenceUnitName("defaultPersistenceUnit");
FilterRegistration.Dynamic filter = sc.addFilter("openEntityManagerInViewFilter", entityManagerInViewFilter);
filter.addMappingForUrlPatterns(null, false, "/*");
其次,通过创建新的 class 来扩展 OpenEntityManagerInViewFilter 并具有注释 @WebFilter:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter {
public MainFilter() {
setEntityManagerFactoryBeanName("entityManagerFactory");
setPersistenceUnitName("defaultPersistanceUnit");
}
}
每次我得到 "No bean named 'entityManagerFactory' is defined" 或 "No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined"。我的实体管理器工厂在@Configuration class 中定义。
如何在没有 web.xml 文件的情况下配置此过滤器?
WebApplicationInitializer 是在 Java 部署到 Servlet 3.0 容器时在 Java 中注册 servlet、过滤器和侦听器的一种很好的通用方法。但是如果你正在注册一个过滤器并且只需要将该过滤器映射到 DispatcherServlet,那么有一个 AbstractAnnotationConfigDispatcherServletInitializer 中的快捷方式。 要注册一个或多个过滤器并将它们映射到 DispatcherServlet,您只需要 要做的是覆盖 AbstractAnnotationConfigDispatcherServletInitializer 的 getServletFilters() 方法。 例如下面的getServletFilters() 方法覆盖了 AbstractAnnotationConfigDispatcherServletInitializer 中的方法 注册过滤器:
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new MyFilter() };
}
这是我的 WebAppInitializer 代码片段
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
protected Class<?>[] getRootConfigClasses() {
System.out.println("*****加载MySqlConfig*******");
return new Class<?>[] { MySqlConfig.class };
}
protected Class<?>[] getServletConfigClasses() {
System.out.println("*****加载WebConfig*******");
return new Class<?>[] { WebConfig.class };
}
protected String[] getServletMappings() {
System.out.println("*****要拦截的请求*******");
return new String[] { "/" };
}
//Register your filter here
protected Filter[] getServletFilters() {
System.out.println("*********加载filter**********");
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
因为引导过程可能非常不清楚(这可能导致配置错误),我决定给出一个详细的答案,但也 "too long; didn't read" 对于那些想立即跳转到答案的人。
TL;DR
最简单的方法是以下两个选项之一:
选项 1:OpenEntityManagerInViewInterceptor
这比 OpenEntityManagerInViewFilter 更接近 Spring,因此当您还需要使用其他 bean 配置它时可能更容易配置。
为您的网络配置使用类似这样的东西:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig implements WebMvcConfigurer {
@Autowired
private EntityManagerFactory emf;
@Override
public void addInterceptors(InterceptorRegistry registry) {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(emf);
registry.addWebRequestInterceptor(interceptor);
}
}
addInterceptors() 的重要部分,在其中创建拦截器并将其添加到 Spring Web。
并且要将 Spring Web 指向配置 class,您需要这样的东西:
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
MyServletInitializer 将被 Spring 自动检测到,因为它是 AbstractAnnotationConfigDispatcherServletInitializer, which is an implementation of WebApplicationInitializer. And Spring Web will automatically be detected by the Servlet container, because Spring Web contains a service provider implementation of ServletContainerInitializer by default, which gets detected automatically. This implementation is called SpringServletContainerInitializer.
的子class选项 2:OpenEntityManagerInViewFilter 如果你不想使用拦截器,你可以使用过滤器。 过滤器具有更广泛的范围,其中每个 HTTP 请求都可以共享一个 entityManager,但拦截器更接近 Spring,从而更容易用它连接 bean(以备不时之需)。
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
补充说明:
- 确保您的 entityManagerFactoryBean 名称正确。请参阅 OpenEntityManagerInViewFilter 的文档,或下面我的详细描述。
- 确保配置没有被多次导入,否则你很难调试问题。有关更多信息,请参阅下面我的详细说明。
Why/when 使用 OpenEntityManagerInViewFilter
OpenEntityManagerInViewFilter 是 JPA 的 OSIV 模式(在视图中打开会话)的实现。 (Session 是 EntityManager 的本机 Hibernate 版本。因此对于 JPA,它可以称为:Open EntityManager In View 模式。)在这个模式中,单个 EntityManager 在单个 HTTP 请求中 shared/reused,并在 HTTP 请求完成时关闭。
使用 OSIV 模式的好处是:
- 与单个 HTTP 请求导致多个不同事务和实体管理器打开相比,数据库性能更好
- 允许 JPA 使用一级缓存
- 更简单的编程,您不必担心 LazyInitializationExceptions,这可能是在 entityManager 关闭并且您访问需要持久上下文的实体上的方法时引起的
- 当您使用使用 JPA 的自定义 Spring 安全过滤器时可能需要它。
但这也可能导致持久性上下文打开的时间比需要的时间长,这可能会损害性能。 有些人还将 OSIV 模式视为反模式:https://vladmihalcea.com/the-open-session-in-view-anti-pattern/ 该网页还讨论了避免 LazyInitializationExceptions 和 OSIV 模式的其他解决方案。
Servlet 3.0 和配置选项
自 Servlet 3.0 起,servlet 规范支持可插拔性,它允许您从 web.xml 切换到编程配置和注解配置。虽然注释配置不支持 web.xml 和编程配置支持的所有功能,例如 servlet 和过滤器的排序。仅使用注释时,如果您必须扩展现有过滤器才能配置它,这可能会很麻烦,就像您在此处所做的那样:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
在 Spring 中配置 Web 应用程序的一种简单方法是使用编程配置。当 Servlet >= 3.0 容器启动时(例如 Tomcat >= 7),它将搜索 web.xml 和 ServletContainerInitializer 服务提供者实现(这也需要您定义一个文件在 META-INF/services 中)。 ServletContainerInitializer 实现允许以编程方式配置 servlet 容器。
连接 Spring 到 Servlet 容器
Spring Web 默认包含 ServletContainerInitializer 的实现,称为 SpringServletContainerInitializer,因此您不必自己创建 ServletContainerInitializer 的实现。这使得框架设计者更容易配置他们的框架以与容器一起工作。 要使用它,您必须创建 Springs WebApplicationInitializer interface. This implementation is automatically discoverd by Spring (SpringServletContainerInitializer). It provides you a ServletContext 实例的实现,它允许您以编程方式配置容器(servlet 和过滤器)。 更简单的方法是创建 AbstractAnnotationConfigDispatcherServletInitializer 的子 class(这是 WebApplicationInitializer 的实现),这样您就不必直接使用 ServletContext.
配置所有内容如何实现OSIV
实现OSIV的多种方式。最佳选择可能因项目而异。
在 OSIV 中,您希望 EntityManager 在 HTTP 请求的开头 opened/started 并在相应的 HTTP 响应结束时关闭。分界就是entityManager和transaction打开和关闭的过程。根据您使用的 Web 应用程序或框架的类型,您可以在分界的确切位置移动一点。
一些分界选项:
选项 1 - 绕过 Spring,仅注释,OpenEntityManagerInViewFilter(不推荐)
您可以绕过 Spring,方法是让 OpenEntityManagerInViewFilter 直接被 servletContainer 检测到,或者通过子class 对其进行注释并对其进行注释,或者通过在 web.xml 中指定它。
这与您的方法相似:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
不推荐这种方法,因为它有点麻烦。
选项 2 - 通过 WebApplicationInitializer、OpenEntityManagerInViewFilter 以编程方式手动
在这种方法中,您创建了 Springs WebApplicationInitializer to get hold of the ServletContext.
的实现然后您可以手动创建和配置 openEntityManagerInViewFilter:
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
FilterRegistration.Dynamic registration = registerServletFilter(servletContext, filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE), true, "/*");
请注意,您还必须自己创建 Spring ApplicationContext 和 DispatcherServlet。有关示例,请参阅 WebApplicationInitializer 的文档。
选项 3 - 以编程方式通过 AbstractAnnotationConfigDispatcherServletInitializer、OpenEntityManagerInViewFilter(推荐)
这是在 Spring 应用程序中启用 OpenEntityManagerInViewFilter 的主要方法。
如前所述,servlet容器会自动检测Springs SpringServletContainerInitializer,然后Spring会自动检测我们的MyServletInitializer(见下文),因为它超class (AbstractAnnotationConfigDispatcherServletInitializer) 是 WebApplicationInitializer.
的实现从 MyServletInitializer,Spring 将加载您的核心应用程序配置,即 rootConfigClasses,您可以在其中定义 JPA bean。
示例:
public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MainConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new OpenEntityManagerInViewFilter()};
}
}
Spring 主配置文件示例:
@EnableJpaRepositories
@Configuration
@EnableTransactionManagement
public class MainConfig {
private Properties hibernateProperties() {
Properties props = new Properties();
props.setProperty("hibernate.hbm2ddl.auto", "update");
props.setProperty("hibernate.dialect", "org.hibernate.dialect.MariaDB10Dialect");
return props;
}
@Bean
public DataSource dataSource() {
Properties properties = new Properties();
properties.put("url", "jdbc:mariadb://localhost:3306/myDatabase");
properties.put("user", "root");
properties.put("password", "myPassword");
HikariConfig config = new HikariConfig();
config.setDataSourceClassName("org.mariadb.jdbc.MariaDbDataSource");
config.setMaximumPoolSize(10);
config.setDataSourceProperties(properties);
return new HikariDataSource(config);
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTxManager = new JpaTransactionManager();
jpaTxManager.setEntityManagerFactory(emf);
return jpaTxManager;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
HibernateJpaDialect jpaDialect = new HibernateJpaDialect();
emf.setJpaDialect(jpaDialect);
emf.setJpaProperties(hibernateProperties());
emf.setPackagesToScan("mypackage");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
emf.setJpaVendorAdapter(vendorAdapter);
return emf;
}
}
Spring 网络配置示例:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig {}
选项 4:无过滤器,Spring 控制器方法上的@Transactional
如果您的应用程序不需要过滤器,并且您的模板层是从 Spring 控制器中呈现的,那么您可以使用 @Transactional 来划分控制器方法,而不是使用纯 OSIV。
在这种情况下,确保在 Spring 配置上设置了 @EnableTransactionManagement
并且控制器方法用 @org.springframework.transaction.annotation.Transactional
请注意,当您使用 JSP 或其他常用模板时,这不起作用,因为这样文本就不会在控制器方法中生成。相反,创建了一个模型和视图对象,然后将其转发给模板进行渲染,但是一旦控制器方法完成,entityManager 在渲染之前关闭,当您访问延迟加载的实体属性时,这可能会导致 LazyInitializationException。
选项 5 - OpenEntityManagerInViewInterceptor 而不是过滤器(推荐)
而过滤器是 servlet api 的一部分,webRequestInterceptors 是 Spring Web 的一部分,因此更接近于 Spring。 其中一个 webRequestInterceptors 是 OpenEntityManagerInViewInterceptor,它是 OpenEntityManagerInViewFilter 的拦截器版本。
文档说:“与 OpenEntityManagerInViewFilter 相比,此拦截器设置在 Spring 应用程序上下文中,因此可以利用 bean 连接。”。 =44=]
因此,在 WebApplicationInitializer(例如 AbstractAnnotationConfigDispatcherServletInitializer)中初始化 OpenEntityManagerInViewFilter 的地方,OpenEntityManagerInViewInterceptor 可以在 Spring 配置中初始化 class。
示例:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig implements WebMvcConfigurer {
@Autowired
private EntityManagerFactory emf;
@Override
public void addInterceptors(InterceptorRegistry registry) {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(emf);
registry.addWebRequestInterceptor(interceptor);
}
}
注意拦截器比过滤器更深一层。如果您有需要使用 entityManager 的过滤器,则拦截器无法提供它,但反之亦然。
关于 OpenEntityManagerInViewFilter 的使用信息
如 OpenEntityManagerInViewFilter 的文档中所述,您必须确保 Spring bean 以正确的名称存在,否则 OpenEntityManagerInViewFilter 可能会给出您描述的错误:“没有定义名为 'entityManagerFactory' 的 bean”。 来自文档:
Looks up the EntityManagerFactory in Spring's root web application context. Supports an "entityManagerFactoryBeanName" filter init-param in web.xml; the default bean name is "entityManagerFactory". As an alternative, the "persistenceUnitName" init-param allows for retrieval by logical unit name (as specified in persistence.xml).
在您的问题中,您没有说明您是如何准确定义 EntityManagerFactory 的。
如何测试是否有效
即使您没有收到 LazyInitializationException,也不意味着共享了 entitymanager。
要验证它是否正常工作,您可以在日志框架中将 org.springframework 设置为 DEBUG 日志级别。
测试时,最好使用Wget或Curl之类的工具,而不是浏览器,因为浏览器可能会触发多个HTTP请求(例如favicon.ico),导致日志不清晰。
当一个 HTTP 请求触发多个存储库中的 entityManager 请求时,所有存储库都应该使用相同的 EntityManager,因此对于单个 HTTP 请求,您应该只看到一行类似这样的内容:
21:48:46.872 [http-nio-8080-exec-5] DEBUG o.s.o.j.s.OpenEntityManagerInViewInterceptor - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
还有一个:
21:48:46.878 [http-nio-8080-exec-5] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
并且当您在多个存储库中有多个 entityManager 请求时,您应该看到它们正在使用相同的线程绑定 entityManager。所以你应该看到多行:
21:48:46.876 [http-nio-8080-exec-5] DEBUG o.s.orm.jpa.JpaTransactionManager - Found thread-bound EntityManager [SessionImpl(1847515591<open>)] for JPA transaction
配置时出现问题
在尝试将 web.xml 禁用为 web.xml.old 之类的东西时,我遇到了一个难以调试的问题,因为 IntelliJ 以某种方式在 IntelliJ 项目配置中映射到重命名的 [=207= .old,导致它仍然使用 xml 配置。
另一个问题可能是当您的 Spring 配置以某种方式被多次导入时,例如,如果您在 webConfiguration 中使用 @import 导入 mainConfiguration 并且您在 AbstractAnnotationConfigDispatcherServletInitializer 中同时指定它们,那么事情可能是多次导入,造成难以调试的问题。 如果你的配置因为有很多而不清楚,你可能想创建一个带有简单 println 消息的构造函数,以验证它只创建了一次。