OpenEntityManagerInViewFilter注释配置

时间:2015-10-10 17:34:31

标签: 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");
    }

}

每次我得到"没有bean命名' entityManagerFactory'已定义"或者"没有定义[javax.persistence.EntityManagerFactory]类型的限定bean"。我的实体管理器工厂在@Configuration类中定义。

如何在没有web.xml文件的情况下配置此过滤器?

2 个答案:

答案 0 :(得分:1)

因为启动过程可能还不清楚(可能导致错误的配置),所以我决定给出详细的答案,但是对于那些想立即跳转到答案的人来说,答案也是“太长;没读过”

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指向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的子类,而WebApplicationInitializerServletContainerInitializer的实现。 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()};
    }
}

其他说明:

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

为什么/何时使用OpenEntityManagerInViewFilter

OSIV pattern是JPA的Session(在视图中打开会话)的实现。 (EntityManagerLazyInitializationExceptions的本机Hibernate版本。因此,对于JPA,可以将其称为:“在视图中打开EntityManager”。)在此模式中,单个EntityManager在单个HTTP中共享/重用。请求,并在HTTP请求结束时关闭。

使用OSIV模式的好处是:

  • 与单个HTTP请求导致打开多个不同的事务和实体危险相比,数据库性能更高
  • 允许JPA使用第一级缓存
  • 更轻松的编程,您不必担心https://vladmihalcea.com/the-open-session-in-view-anti-pattern/,这可能是由于关闭entityManager并访问需要持久性上下文的实体上的方法
  • 当您使用使用JPA的自定义Spring安全过滤器时,可能会需要它。

但是,这也可能导致持久性上下文的打开时间超过需要的时间,这可能会损害性能。 有些人也将OSIV模式视为反模式:ServletContainerInitializer此网页还讨论了避免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和SpringServletContainerInitializer服务提供者实现(这还要求您在META-INF / services中定义一个文件) )。 ServletContainerInitializer实现允许以编程方式配置Servlet容器。

将Spring连接到Servlet容器

Spring Web默认包含一个名为WebApplicationInitializer的ServletContainerInitializer的实现,因此您不必自己创建ServletContainerInitializer的实现。这使框架设计者更容易配置其框架以与容器一起使用。 要使用此功能,您必须创建Springs ServletContext接口的实现。 Spring(SpringServletContainerInitializer)自动发现此实现。它为您提供了一个AbstractAnnotationConfigDispatcherServletInitializer实例,该实例允许您以编程方式配置容器(servlet和过滤器)。 一种更简单的方法是创建WebApplicationInitializer的子类(这是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 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的文档。

选项3-以编程方式通过AbstractAnnotationConfigDispatcherServletInitializer,OpenEntityManagerInViewFilter(推荐)

这是在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 {}

选项4:无过滤器,在Spring控制器方法上为@Transactional

如果您的应用程序不需要过滤器,并且模板层是在Spring控制器中呈现的,则可以使用@Transactional而非纯OSIV来划分控制器方法。

在这种情况下,请确保在Spring配置上设置了@EnableTransactionManagement,并用@org.springframework.transaction.annotation.Transactional注释了控制器方法

请注意,当您使用例如JSP或其他常用模板时,这将不起作用,因为那样一来,文本就不会在controller方法中生成。而是创建一个模型和视图对象,然后将其转发到模板以进行渲染,但是一旦控制器方法完成,则在渲染之前关闭entityManager,当您访问延迟加载的实体属性时,这可能导致LazyInitializationException。 / p>

选项5-OpenEntityManagerInViewInterceptor而不是过滤器(推荐)

过滤器是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的使用信息

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()};
        }
    }
相关问题