为了克服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");
}
}
每次我得到"没有bean命名' entityManagerFactory'已定义"或者"没有定义[javax.persistence.EntityManagerFactory]类型的限定bean"。我的实体管理器工厂在@Configuration类中定义。
如何在没有web.xml文件的情况下配置此过滤器?
答案 0 :(得分:1)
因为启动过程可能还不清楚(可能导致错误的配置),所以我决定给出详细的答案,但是对于那些想立即跳转到答案的人来说,答案也是“太长;没读过”
最简单的方法是以下两个选项之一:
选项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指向config类,您需要这样的东西:
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的子类,而WebApplicationInitializer是ServletContainerInitializer的实现。 Servlet容器将自动检测Spring Web,因为默认情况下Spring Web包含SpringServletContainerInitializer的服务提供程序实现,该实现自动得到检测。此实现称为OpenEntityManagerInViewFilter。
选项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()};
}
}
其他说明:
OSIV pattern是JPA的Session(在视图中打开会话)的实现。 (EntityManager是LazyInitializationExceptions的本机Hibernate版本。因此,对于JPA,可以将其称为:“在视图中打开EntityManager”。)在此模式中,单个EntityManager在单个HTTP中共享/重用。请求,并在HTTP请求结束时关闭。
使用OSIV模式的好处是:
但是,这也可能导致持久性上下文的打开时间超过需要的时间,这可能会损害性能。 有些人也将OSIV模式视为反模式:ServletContainerInitializer此网页还讨论了避免LazyInitializationExceptions和OSIV模式的其他解决方案。
从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和SpringServletContainerInitializer服务提供者实现(这还要求您在META-INF / services中定义一个文件) )。 ServletContainerInitializer实现允许以编程方式配置Servlet容器。
Spring Web默认包含一个名为WebApplicationInitializer的ServletContainerInitializer的实现,因此您不必自己创建ServletContainerInitializer的实现。这使框架设计者更容易配置其框架以与容器一起使用。 要使用此功能,您必须创建Springs ServletContext接口的实现。 Spring(SpringServletContainerInitializer)自动发现此实现。它为您提供了一个AbstractAnnotationConfigDispatcherServletInitializer实例,该实例允许您以编程方式配置容器(servlet和过滤器)。 一种更简单的方法是创建WebApplicationInitializer的子类(这是WebApplicationInitializer的实现),这样您就不必直接使用ServletContext来配置所有内容。
实现OSIV的多种方法。最佳选择可能因项目而异。
在OSIV中,您希望EntityManager在HTTP请求的开始处打开/开始,并在相应的HTTP响应结束时关闭。划分是打开和关闭entityManager和事务的过程。根据您使用的Web应用程序或框架的类型,您可以在标界的确切位置上稍作改动。
一些分界选项:
您可以通过让ServletContainer直接检测OpenEntityManagerInViewFilter,或者对其进行子类化和注释,或者在web.xml中进行指定,来绕过Spring。
这类似于您的方法:
@WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
不推荐这种方法,因为它有点麻烦。
通过这种方法,您可以创建Springs ServletContext的实现以掌握WebApplicationInitializer。
然后,您可以手动创建和配置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。有关示例,请参见SpringServletContainerInitializer的文档。
这是在Spring应用程序中启用OpenEntityManagerInViewFilter的主要方法。
如前所述,servlet容器将自动检测Springs AbstractAnnotationConfigDispatcherServletInitializer,然后Spring将自动检测我们的MyServletInitializer(请参见下文),因为其超类(WebApplicationInitializer)是{{3}的实现}。
从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 {}
如果您的应用程序不需要过滤器,并且模板层是在Spring控制器中呈现的,则可以使用@Transactional而非纯OSIV来划分控制器方法。
在这种情况下,请确保在Spring配置上设置了@EnableTransactionManagement
,并用@org.springframework.transaction.annotation.Transactional
注释了控制器方法
请注意,当您使用例如JSP或其他常用模板时,这将不起作用,因为那样一来,文本就不会在controller方法中生成。而是创建一个模型和视图对象,然后将其转发到模板以进行渲染,但是一旦控制器方法完成,则在渲染之前关闭entityManager,当您访问延迟加载的实体属性时,这可能导致LazyInitializationException。 / p>
过滤器是servlet api的一部分,webRequestInterceptors是Spring Web的一部分,因此与Spring的距离更近了。 这些OpenEntityManagerInViewInterceptor是webRequestInterceptors之一,它是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的文档中所述,您必须确保以正确的名称存在一个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消息创建一个构造函数,因此请确认它仅创建一次。
答案 1 :(得分:-1)
WebApplicationInitializer is a fine general-purpose way of registering servlets, filters,and listeners in Java when deploying to a Servlet 3.0 container. But if you’re registering a filter and only need to map that filter to DispatcherServlet, then there’s a shortcut in AbstractAnnotationConfigDispatcherServletInitializer. To register one or more filters and map them to DispatcherServlet, all you need to do is override the getServletFilters() method of AbstractAnnotationConfigDispatcherServletInitializer. For example, the following getServletFilters() method overrides the one from AbstractAnnotationConfigDispatcherServletInitializer to register a filter:
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new MyFilter() };
}
Here is my snippet of WebAppInitializer code
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()};
}
}