为了克服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, "/*");
其次,通过创建扩展OpenEntityManagerInViewFilter并具有批注@WebFilter的新类:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter {
public MainFilter() {
setEntityManagerFactoryBeanName("entityManagerFactory");
setPersistenceUnitName("defaultPersistanceUnit");
}
}
每次收到“未定义名为'entityManagerFactory'的bean”或“未定义类型为[javax.persistence.EntityManagerFactory]的合格bean”时。我的实体管理器工厂在@Configuration类中定义。
没有web.xml文件的情况下如何配置此过滤器?
最佳答案
因为启动过程可能还不清楚(可能导致错误的配置),所以我决定给出详细的答案,但是对于想要立即跳转到答案的用户来说,答案也是“太长;没有阅读”。
TL; DR
最简单的方法是以下两个选项之一:
选项1:OpenEntityManagerInViewInterceptor
它比OpenEntityManagerInViewFilter更接近Spring,因此当您还需要与其他bean一起配置它时,可能更易于配置。
将以下内容用于您的Web配置:
@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指向配置类,您需要这样的东西:
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[]{"/"};
}
}
Spring将自动检测MyServletInitializer,因为它是AbstractAnnotationConfigDispatcherServletInitializer的子类,它是WebApplicationInitializer的实现。 Servlet容器将自动检测Spring Web,因为Spring Web默认包含ServletContainerInitializer的服务提供程序实现,该服务提供程序实现会自动检测。此实现称为SpringServletContainerInitializer。
选项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()};
}
}
附加说明:
为什么/何时使用OpenEntityManagerInViewFilter
OpenEntityManagerInViewFilter是JPA的OSIV pattern(视图中的开放会话)的实现。 (一个Session是EntityManager的本机Hibernate版本。因此对于JPA,它可以称为:在视图模式下打开EntityManager。)在此模式下,单个EntityManager在单个HTTP请求中共享/重用,并在HTTP请求时关闭。完成。
使用OSIV模式的好处是:
与单个HTTP请求导致打开多个不同事务和实体危险相比,
但这也可能导致持久性上下文的打开时间超过需要的时间,这可能会损害性能。
有些人也将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接口的实现。 Spring(SpringServletContainerInitializer)自动发现此实现。它为您提供了一个ServletContext实例,该实例允许您以编程方式配置容器(servlet和过滤器)。
一种更简单的方法是创建AbstractAnnotationConfigDispatcherServletInitializer的子类(这是WebApplicationInitializer的实现),因此您不必直接使用ServletContext进行所有配置。
如何实现OSIV
实现OSIV的多种方法。最佳选择可能因项目而异。
在OSIV中,您希望EntityManager在HTTP请求的开始处打开/开始,并在相应的HTTP响应结束时关闭。划分是打开和关闭entityManager和事务的过程。根据您使用的Web应用程序或框架的类型,您可以在标界的确切位置上稍作改动。
一些分界选项:
选项1-跳过Spring,仅注释,OpenEntityManagerInViewFilter(不建议)
您可以通过让ServletContainer直接检测OpenEntityManagerInViewFilter或对其进行子类化并对其进行注释,或者在web.xml中进行指定来绕过Spring。
这类似于您的方法:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
不建议使用此方法,因为它有点麻烦。
选项2-通过WebApplicationInitializer,OpenEntityManagerInViewFilter手动编程
通过这种方法,您将创建Springs WebApplicationInitializer的实现以获取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(请参见下文),因为其超类(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 Web配置示例:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "mypackages")
public class WebConfig {}
选项4:无过滤器,Spring控制器方法上的@Transactional
如果您的应用程序不需要过滤器,并且模板层是在Spring控制器中呈现的,则可以使用@Transactional而不是使用纯OSIV来划分控制器方法。
在这种情况下,请确保在Spring配置上设置了
@EnableTransactionManagement
,并且用@org.springframework.transaction.annotation.Transactional
注释了控制器方法请注意,当您使用JSP或其他常用模板时,这将不起作用,因为那样一来,文本就不会在controller方法中生成。而是创建一个模型和视图对象,然后将其转发到模板以进行渲染,但是一旦控制器方法完成,则在渲染之前将entityManager关闭,这会在您访问延迟加载的实体属性时导致LazyInitializationException。
选项5-OpenEntityManagerInViewInterceptor而不是过滤器(推荐)
过滤器是servlet api的一部分,而webRequestInterceptors是Spring Web的一部分,因此离Spring更近一些。
这些webRequestInterceptor之一是OpenEntityManagerInViewInterceptor,它是OpenEntityManagerInViewFilter的拦截器版本。
该文档说:“与OpenEntityManagerInViewFilter相比,此拦截器是在Spring应用程序上下文中设置的,因此可以利用Bean接线。”
因此,在WebApplicationInitializer(例如AbstractAnnotationConfigDispatcherServletInitializer)中初始化OpenEntityManagerInViewFilter的地方,可以在Spring配置类中初始化OpenEntityManagerInViewInterceptor。
例:
@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”。
从文档中:
在Spring的根Web应用程序中查找EntityManagerFactory
上下文。支持“entityManagerFactoryBeanName”过滤器init-param
在web.xml;缺省的Bean名称是“entityManagerFactory”。作为一个
或者,“persistenceUnitName” init-param允许检索
通过逻辑单元名称(在persistence.xml中指定)。
在您的问题中,您没有显示如何精确定义EntityManagerFactory。
如何测试它是否正常工作
即使您没有收到LazyInitializationException,也并不意味着entitymanager是共享的。
要验证它是否正常工作,可以将org.springframework放在日志记录框架中的DEBUG日志记录级别。
测试时,最好使用Wget或Curl之类的工具代替Web浏览器,因为Web浏览器可能会触发多个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项目配置中映射到重命名的web.xml.old,导致它仍然使用xml。组态。
另一个问题可能是当您的Spring配置以某种方式多次导入时,例如,如果您在webConfiguration中使用@import导入mainConfiguration并在AbstractAnnotationConfigDispatcherServletInitializer中都指定了它们,则可能会多次导入内容,从而导致难以调试的问题。
如果由于配置太多而导致配置不清楚,则可能需要创建带有简单println消息的构造函数,因此请验证它仅创建一次。
关于java - OpenEntityManagerInViewFilter批注配置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33056952/