In order to overcome LazyInitializationException I've decided to use OpenEntityManagerInViewFilter but I couldn't do it annotation style. I've tried set it up two ways:
First in onStartup method from WebApplicationInitializer:
OpenEntityManagerInViewFilter entityManagerInViewFilter = new OpenEntityManagerInViewFilter();
entityManagerInViewFilter.setEntityManagerFactoryBeanName("entityManagerFactory");
entityManagerInViewFilter.setPersistenceUnitName("defaultPersistenceUnit");
FilterRegistration.Dynamic filter = sc.addFilter("openEntityManagerInViewFilter", entityManagerInViewFilter);
filter.addMappingForUrlPatterns(null, false, "/*");
Second by creating new class that extends OpenEntityManagerInViewFilter and has annotation #WebFilter:
#WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter {
public MainFilter() {
setEntityManagerFactoryBeanName("entityManagerFactory");
setPersistenceUnitName("defaultPersistanceUnit");
}
}
Each time I got "No bean named 'entityManagerFactory' is defined" or "No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined". My entity manager factory is defined in #Configuration class.
How can I configure this filter without web.xml file?
Because the boot process can be pretty unclear (which can lead to wrong configurations), I decided to give a detailed answer, but also a "too long; didn't read" for those who want to jump to the answer immediately.
TL;DR
The easiest way is one of these two options:
Option 1: OpenEntityManagerInViewInterceptor
This is closer to Spring than OpenEntityManagerInViewFilter and thus may be easier to configure when you also need to configure it with other beans.
Use something like this for your web configuration:
#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);
}
}
The important part the addInterceptors(), in which the interceptor is created and added to Spring Web.
And to point Spring Web to config class, you need something like this:
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 will be automatically detected by Spring, because it's a subclass of 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.
Option 2: OpenEntityManagerInViewFilter
If you don't want to use the interceptor, you can use the filter.
The filter has a wider range scope in which an entityManager can be shared per HTTP request, but interceptors are closer to Spring, making it easier to wire beans with it (in case needed).
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()};
}
}
Additional notes:
Make sure that you have correct names of entityManagerFactoryBean. See documentation of OpenEntityManagerInViewFilter, or my detailed description below.
Make sure that configurations aren't imported multiple times, otherwise you can get hard to debug problems. For more info, see my detailed description below.
Why/when to use OpenEntityManagerInViewFilter
OpenEntityManagerInViewFilter is an implementation of the OSIV pattern (Open Session In View) for JPA. (A Session is the native Hibernate version of an EntityManager. So for JPA it can be called: Open EntityManager In View pattern.) In this pattern, a single EntityManager is shared/reused within a single HTTP request, and closed when the HTTP request finishes.
The benefits of using the OSIV pattern are:
better database performance compared to when a single HTTP request causes multiple different transactions and entitymangers to be opened
allows JPA to use 1st level caching
easier programming, you don't have to worry about LazyInitializationExceptions, which can be caused when the entityManager is closed and you access methods on entities that require a persistent context
it may be required when you're using custom Spring security filters that use JPA.
But it may also cause the persistent context to be opened longer than needed, which may harm performance.
Some also consider the OSIV pattern as an anti-pattern: https://vladmihalcea.com/the-open-session-in-view-anti-pattern/ This web page also discusses other solutions to avoid both LazyInitializationExceptions and the OSIV pattern.
Servlet 3.0 and configuration options
Since Servlet 3.0, the servlet specification supports pluggability, it allows you to switch from web.xml to programmatic configurations and annotation configurations. Although annotation configurations don't support all features that web.xml and programmatic configurations do, such as ordering of servlets and filters. When using annotations only, it can be cumbersome if you have to extend and existing filters just to be able to configure it, as you do here:
#WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
An easy way to configure the web app in Spring is to use a programmatic configuration. When a Servlet >= 3.0 container starts (such as Tomcat >= 7), it will search for web.xml and for a ServletContainerInitializer service provider implementations (which also requires you to define a file in META-INF/services). The ServletContainerInitializer implementation allows the servlet container to be configured programmatically.
Connecting Spring to the Servlet container
Spring Web by default contains an implemenation of ServletContainerInitializer, called SpringServletContainerInitializer, so that you don't have to create an implementation of ServletContainerInitializer yourself. This makes it easier for framework designer to configure their framework to work with the container.
To make use of this, you have to create an implementation of Springs WebApplicationInitializer interface. This implementation is automatically discoverd by Spring (SpringServletContainerInitializer). It provides you a ServletContext instance, which allows you to configure the container (servlets and filters) programmatically.
An easier way is to create a subclass of AbstractAnnotationConfigDispatcherServletInitializer (which is an implementation of WebApplicationInitializer), so that you don't have to configure everything directly using the ServletContext.
How to achieve OSIV
Multiple ways to achieve OSIV. The best option may differ per project.
In OSIV, you want the EntityManager to the opened/started at the beginning of the HTTP request and to be closed when the corresponding HTTP response ends. Demarcation is the proccess of opening and closing the entityManager and transaction. Depending on what type of web application or framework you are using, you can shift a bit in the exact location of demarcation.
Some demarcation options:
Option 1 - bypass Spring, annotations-only, OpenEntityManagerInViewFilter (not recommended)
You can bypass Spring by letting the OpenEntityManagerInViewFilter be directly detected by the servletContainer, or by subclassing it and annotating it, or by specifying it in web.xml.
This is similar to your approach:
#WebFilter(urlPatterns = {"/*"})
public class MainFilter extends OpenEntityManagerInViewFilter
This approach is not recommended because it's a bit cumbersome.
Option 2 - manually programmatically via WebApplicationInitializer, OpenEntityManagerInViewFilter
In this approach you create an implemenatation of Springs WebApplicationInitializer to get hold of the ServletContext.
Then you can create and configure the openEntityManagerInViewFilter manually:
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
FilterRegistration.Dynamic registration = registerServletFilter(servletContext, filter);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE), true, "/*");
Note you'll also have to create the Spring ApplicationContext and DispatcherServlet yourself. See the documentation of WebApplicationInitializer for an example.
Option 3 - programmatically via AbstractAnnotationConfigDispatcherServletInitializer, OpenEntityManagerInViewFilter (recommended)
This is the main approach to enable OpenEntityManagerInViewFilter in a Spring application.
As described earlier, the servlet container will automatically detect Springs SpringServletContainerInitializer, and then Spring will automatically detect our MyServletInitializer (see below) because its superclass (AbstractAnnotationConfigDispatcherServletInitializer) is an implementation of WebApplicationInitializer.
From MyServletInitializer, Spring will load your core application config, the rootConfigClasses, in which you can define your JPA beans.
Example:
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 main config file example:
#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 config example:
#Configuration
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan(basePackages = "mypackages")
public class WebConfig {}
Option 4: no filter, #Transactional on Spring controller methods
If your application doesn't require filters, and your template layer is rendered from within a Spring controller, than you can demarcate the controller method with #Transactional instead of using pure OSIV.
In this case, make sure #EnableTransactionManagement is set on the Spring configuration and the controller method is annotated with #org.springframework.transaction.annotation.Transactional
Note that this doesn't work when you are for example using JSP or other common templates, because then the text isn't generated in the controller method. Instead, a model and view object is created, which is then forwarded to the template for rendering, but once the controller method is finished, the entityManager is closed before the rendering, which can cause the LazyInitializationException when you access lazy loaded entity properties.
Option 5 - OpenEntityManagerInViewInterceptor instead of a filter (recommended)
Whereas filters are part of the servlet api, webRequestInterceptors are part of Spring Web and thus a bit closer to Spring.
One of these webRequestInterceptors is OpenEntityManagerInViewInterceptor, which is the interceptor version of OpenEntityManagerInViewFilter.
The documentation says: "In contrast to OpenEntityManagerInViewFilter, this interceptor is set up in a Spring application context and can thus take advantage of bean wiring."
So where the OpenEntityManagerInViewFilter is initialized in a WebApplicationInitializer (such as AbstractAnnotationConfigDispatcherServletInitializer), OpenEntityManagerInViewInterceptor can be initialized in a Spring configuration class.
Example:
#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);
}
}
Note that the interceptor is in a deeper layer than the filter. If you have filters that need to use an entityManager, than the interceptors cannot provide it, but the other way around should work.
Usage info about OpenEntityManagerInViewFilter
As described in the documentation of OpenEntityManagerInViewFilter, you have to make sure that a Spring bean exist under the right name, otherwise OpenEntityManagerInViewFilter may give the error that you describe: "No bean named 'entityManagerFactory' is defined".
From the documentation:
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).
In your question, you didn't show how you did define the EntityManagerFactory exactly.
How to test whether it is working
Even when you don't get a LazyInitializationException, it doesn't mean that entitymanager is shared.
To verify whether it is working correctly, you can put org.springframework at DEBUG logging level in your logging framework.
When testing, preferably use a tool like Wget or Curl instead of a webbrowser, because a webbrowser may trigger multiple HTTP requests (such as favicon.ico), which makes the logs less clear.
When a HTTP requests triggers entityManager requests in multiple repositories, all should use the same EntityManager, so for a single HTTP request, you should only see a single line with something like:
21:48:46.872 [http-nio-8080-exec-5] DEBUG o.s.o.j.s.OpenEntityManagerInViewInterceptor - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
and a single:
21:48:46.878 [http-nio-8080-exec-5] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
And when you have multiple entityManager requests in multiple repositories, you should see that they are using the same thread-bound entityManager. So you should see multiple lines with:
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
Problems while configuring
I had a difficult to debug problem when trying to disable web.xml to something like web.xml.old, because IntelliJ somehow made a mapping in the IntelliJ project configuration to the renamed web.xml.old, causing it to still use the xml configuration.
Another problem can be when your Spring configurations are somehow imported multiple times, for example if you use #import in the webConfiguration to import the mainConfiguration and you specify them both in your AbstractAnnotationConfigDispatcherServletInitializer, then things may be imported multiple times, causing hard to debug problems.
If your configuration is unclear because you have many, you may want to create a constructor with a simple println message so verify that it's only created once.
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()};
}
}
Related
I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.
I have a mature java web application that uses spring security (4.2.3, java8, tomcat8), and includes a view that allows a user to query the session registry to see who else is logged in. This depends on HttpSessionEventPublisher to notify the session registry when a user logs out.
The original implementation used web.xml config, but I have mostly reimplemented that using java config now. The last thing I refactored was the HttpSessionEventPublisher.
When I moved it from a web.xml declaration into my WebSecurityConfigurerAdapter it stopped working. Specifically, logged-out sessions are no longer being removed from the registry.
I know the bean is being instantiated (the factory method is being called).
This is the java config suggested in the reference docs:
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
Is there some trick needed to get this to work in java config?
HttpSessionEventPublisher should be registered as a standard listener not as spring bean. check the documentation.
So, I think you need to register it using WebApplicationInitializer as follows:
public final class MyWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(final ServletContext ctx) {
ctx.addListener(new HttpSessionEventPublisher());
}
}
I am using Spring Boot 1.5.7 and Apache Camel 2.19.3, using Spring Boot AutoConfiguration provided by spring-boot-starter-camel
It is pretty basic Spring Boot and Camel initialized as in their tutorial, so we have a RouteBuilder component that does exactly that.
#Component
public class CIFRoutes extends RouteBuilder {
#Override
public void configure() throws Exception {
// build routes
}
}
We have a Configuration that defines some beans we need in our application
#Configuration
public class MyConfiguration {
#Bean
public void Map<String, Object> map() {
return new HashMap<>()
}
}
Finally, we have a custom InflightRepository implementation that should be scanned by auto-configuration and added to the CamelContext which basically works, but for some reason, the component doesn't get initialized properly. Means, its dependencies are not initialized but the bean is instantiated and injected in my Application.
#Component
public class MyCustomInflightRepository extends DefaultInflightRepository {
#Autowired
private Map<String, Object> map;
#Override
public void add(Exchange exchange) {
super.addExchange(exchange);
// ....
}
}
The problem now is that map remains (null), I also tried adding a #PostConstruct initializer method but it doesn't get called.
As far as I was able to reconstruct, it seems to be connected to premature in CamelAutoConfiguration where the CamelContext bean gets instantiated (done in private method afterPropertiesSet.
InflightRepository inflightRepository = getSingleBeanOfType(applicationContext, InflightRepository.class);
if (inflightRepository != null) {
LOG.info("Using custom InflightRepository: {}", inflightRepository);
camelContext.setInflightRepository(inflightRepository);
}
If MyCustomInflightRepository doesn't implement InflightRepository, the bean is initialized correctly, but indeed not recognized by Camel. When disabling auto-configuration, the bean's dependencies are injected.
So, either I'm doing the impossible by Spring standards or there's something fishy with the Camel component for Spring.
I'm a bit quick on resolving this (I wanted to post this two days ago already^^), but a colleague figured out what could be the problem.
When using CamelAutoConfiguration the InflightRepository bean (or practicially everything for which Camel tries to get a matching bean here), the context is accessed before property resolvers are fully initialized which leads to the bean being initialized (and cached in context) before any auto-wired properties can be resolved.
I'm not a Spring expert but this behavior is a bit problematic in my opinion because uninitialized beans are pulled into the CamelContext when you rely on Spring DI for your custom components.
To be sure, I'll raise this with the maintainers...
By the way, my simple solution was to manually setting the in-flight repository in context configuration (as suggested)
#Bean
public CamelContextConfiguration camelConfig() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext context) {
context.setInflightRepository(new MyCustomInflightRepository(/* Dependencies go here */ ));
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
}
};
}
Also it seems to be an issue when use camel-http-starter in your project which isn't recommended, they claim it is deprecated.
So, either don't do DI (regardless if via property or constructor injection) for your camel-managed beans or skip that starter.
The problem is that a Map<String,Object> is too vague for Spring to be able to understand what you want; I think the default behavior is that it'll give you all beans keyed by name.
Instead, be more specific, or possibly provide the necessary parameters as constructor arguments and configure them explicitly in an #Bean method (it's a good idea to always use constructor injection anyway).
I writing application using spring-boot-starter-jdbc (v1.3.0).
The problem that I met: Instance of BeanPropertyRowMapper fails as it cannot convert from java.sql.Timestamp to java.time.LocalDateTime.
In order to copy this problem, I implemented
org.springframework.core.convert.converter.Converter for these types.
public class TimeStampToLocalDateTimeConverter implements Converter<Timestamp, LocalDateTime> {
#Override
public LocalDateTime convert(Timestamp s) {
return s.toLocalDateTime();
}
}
My question is: How do I make available TimeStampToLocalDateTimeConverter for BeanPropertyRowMapper.
More general question, how do I register my converters, in order to make them available system wide?
The following code bring us to NullPointerException on initialization stage:
private Set<Converter> getConverters() {
Set<Converter> converters = new HashSet<Converter>();
converters.add(new TimeStampToLocalDateTimeConverter());
converters.add(new LocalDateTimeToTimestampConverter());
return converters;
}
#Bean(name="conversionService")
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
return bean.getObject();
}
Thank you.
All custom conversion service has to be registered with the FormatterRegistry. Try creating a new configuration and register the conversion service by implementing the WebMvcConfigurer
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new TimeStampToLocalDateTimeConverter());
}
}
Hope this works.
I'll copy my answer from https://stackoverflow.com/a/72781591/140707 since I think the two questions are similar (so the answer applies to both).
Existing answers didn't work for me:
Customizing via WebMvcConfigurerAdapter.addFormatters (or simply annotating the converter with #Component) only works in the WebMvc context and I want my custom converter to be available everywhere, including #Value injections on any bean.
Defining a ConversionService bean (via ConversionServiceFactoryBean #Bean or #Component) causes Spring Boot to replace the default ApplicationConversionService on the SpringApplication bean factory with the custom bean you've defined, which will probably be based on DefaultConversionService (in AbstractApplicationContext.finishBeanFactoryInitialization). The problem is that Spring Boot adds some handy converters such as StringToDurationConverter to the standard set in DefaultConversionService, so by replacing it you lose those conversions. This may not be an issue for you if you don't use them, but it means that solution won't work for everyone.
I created the following #Configuration class which did the trick for me. It basically adds custom converters to the ConversionService instance used by Environment (which is then passed on to BeanFactory). This maintains as much backwards compatibility as possible while still adding your custom converter into the conversion services in use.
#Configuration
public class ConversionServiceConfiguration {
#Autowired
private ConfigurableEnvironment environment;
#PostConstruct
public void addCustomConverters() {
ConfigurableConversionService conversionService = environment.getConversionService();
conversionService.addConverter(new MyCustomConverter());
}
}
Obviously you can autowire a list of custom converters into this configuration class and loop over them to add them to the conversion service instead of the hard-coded way of doing it above, if you want the process to be more automatic.
To make sure this configuration class gets run before any beans are instantiated that might require the converter to have been added to the ConversionService, add it as a primary source in your spring application's run() call:
#SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(new Class<?>[] { MySpringBootApplication.class, ConversionServiceConfiguration.class }, args);
}
}
If you don't do this, it might work, or not, depending on the order in which your classes end up in the Spring Boot JAR, which determines the order in which they are scanned. (I found this out the hard way: it worked when compiling locally with an Oracle JDK, but not on our CI server which was using a Azul Zulu JDK.)
Note that for this to work in #WebMvcTests, I had to also combine this configuration class along with my Spring Boot application class into a #ContextConfiguration:
#WebMvcTest(controllers = MyController.class)
#ContextConfiguration(classes = { MySpringBootApplication.class, ConversionServiceConfiguration.class })
#TestPropertySource(properties = { /* ... properties to inject into beans, possibly using your custom converter ... */ })
class MyControllerTest {
// ...
}
I suggest to use #Autowired and the related dependency injection mechanism of spring to use a single ConversionService instance throughout your application. The ConversionService will be instantiated within the configuration.
All Converters to be available application wide receive an annotation (e.g. #AutoRegistered). On application start a #Component FormatterRegistrar (Type name itself is a bit misleading, yes it is "...Registrar" as it does the registering. And #Component as it is fully spring managed and requires dependency injection) will receive #AutoRegistered List of all annotated Converters.
See this thread for concrete implementation details. We use this mechanism within our project and it works out like a charm.
org.springframework.web.servlet.config.annotation.WebMvcConfigurer or any on its implementation is one stop place for any kind of customization in spring boot project. It prvoides various methods, for your Converter requirement.
Just create a new Converter by extending org.springframework.core.convert.converter.Converter<S, T>. Then register it with Spring by your class overriding method org.springframework.web.servlet.config.annotation.WebMvcConfigurer.addFormatters(FormatterRegistry)
Note there are Other types of Converter also which basically starts from ConditionalConverter.
Trying adding
#Converter(autoApply = true)
Its needs to be placed over the convertor class. This works for me in case of Convertor needed for Localdate for interacting to DB.
#Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return (locDate == null ? null : Date.valueOf(locDate));
}
#Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return (sqlDate == null ? null : sqlDate.toLocalDate());
}
}
This is now applied automatically while interacting with DB.
I'm building an application framework which (as part of the implementation) registers filters using Spring FilterRegistrationBean beans. The registration method (annotated with #Bean) returns either a FilterRegistrationBean wrapping the real filter, or a dummy ServletContextInitializer, according to some logic which determines whether the filter is turned on or off. (This way avoids cluttering up the filter chain with deactivated filters.)
// This method is in some other class which is an `#Component`, and not within the same class as the `Filter` itself
#Bean
public ServletContextInitializer register() {
if (someLogic()) {
Filter filter = new MyRealFilter();
FilterRegistrationBean frb = new FilterRegistrationBean(filter);
// ... Stuff to do with configuring the filter patterns etc. ...
return frb;
} else {
return new DummyServletContextInitializer(); // a SCI which does nothing
}
}
Now, I want the filter to be context-aware. So I set the filter to be a #Component.
#Component
public class MyRealFilter implements Filter {
#Autowired
private ApplicationContext applicationContext;
// stuff
}
This works fine if the filter is turned on (someLogic() above returns true) and Spring still respects whatever configuration you specified on the FilterRegistrationBean. However, if the filter is turned off, the filter still gets registered because it is a #Component. It seems that Spring automatically registers any filter declared as a #Component, unless that filter was already registered in a FilterRegistrationBean.
Is there a way to tell Spring not to register a filter which is declared as an #Component? Or any other way to solve this problem where I want a filter to be context-aware but still be registered only via a FilterRegistrationBean and not directly?
UPDATE: I later found that actually the #ApplicationContext does not get autowired if I construct the bean myself within such a register() method. The way to get around this is to declare a subclass of FilterRegistrationBean as a #Component with a #PostConstruct, rather than using register().
Use the #ConditionalOn stuff to determine what needs to be registered. Instead of putting it on your filter or #Bean method do it on a #Configuration class.
#Configuration
public class OptionalWebFiltersConfiguration {
#Configuration
#ConditionalForFilterX
public static class FilterXConfiguration {
#Bean
public Filter filterX() {
return new FilterX();
}
#Bean
public FilterRegistrationBean filterXRegistrationBean() {
FilterRegistrationBean frb = new FilterRegistrationBean frb = new FilterRegistrationBean(filterX());
// other config
return verb;
}
}
}
This will only include the filter configuration when the condition is met, without duplication conditional instructions. This is also how Spring Boot registers filters, listeners etc. (See for instance how the DispatcherServlet is registered).