java - OpenEntityManagerInViewFilter批注配置

标签 java spring-mvc jpa filter annotations

为了克服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()};
    }
}

附加说明:
  • 确保您具有正确的entityManagerFactoryBean名称。请参阅OpenEntityManagerInViewFilter的文档,或下面的详细说明。
  • 确保未多次导入配置,否则您将很难调试问题。有关更多信息,请参见下面的详细描述。


  • 为什么/何时使用OpenEntityManagerInViewFilter

    OpenEntityManagerInViewFilter是JPA的OSIV pattern(视图中的开放会话)的实现。 (一个SessionEntityManager的本机Hibernate版本。因此对于JPA,它可以称为:在视图模式下打开EntityManager。)在此模式下,单个EntityManager在单个HTTP请求中共享/重用,并在HTTP请求时关闭。完成。

    使用OSIV模式的好处是:

    与单个HTTP请求导致打开多个不同事务和实体危险相比,
  • 具有更好的数据库性能
  • 允许JPA使用1级缓存
  • 易于编程,您不必担心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接口的实现。 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/

    相关文章:

    java - 从 DATE 到 java.time.LocalDate 的 JPA 转换器不适用于 Glassfish 4.1

    java - 一个好的 Java 2d 引擎?

    java - 如何正确管理 Android 应用程序的上下文(非 Activity 类)

    java - 3个线程可以访问java中的1个对象

    java - Mockito 抛出 WrongTypeOfReturnValue

    asp.net-mvc - Java Spring MVC 中的 ASP.NET MVC 部分 View

    jquery - 使用 Spring 和 Ajax 从 Post 方法接收 Rest Controller 中的参数

    java - 使用 Hibernate JPA 启动 Spring Boot 应用程序时出现 NoSuchMethodError

    java - 如何按参数值拆分Spring MVC请求映射

    java - Hibernate 映射通过引用未知目标实体