Spring Boot - Ehcache Second Level Cache Integration - java

I am trying to hold some variables in the second level cache. I have read and applied the instructions in Baeldung. However, it keeps giving this error:
Caused by: org.hibernate.cache.NoCacheRegionFactoryAvailableException: Second-level cache is used in the application, but property hibernate.cache.region.factory_class is not given; please either disable second level cache or set correct region factory using the hibernate.cache.region.factory_class setting and make sure the second level cache provider (hibernate-infinispan, e.g.) is available on the classpath.
at org.hibernate.cache.internal.NoCachingRegionFactory.buildTimestampsRegion(NoCachingRegionFactory.java:88)
at org.hibernate.cache.spi.UpdateTimestampsCache.(UpdateTimestampsCache.java:57)
at org.hibernate.internal.CacheImpl.(CacheImpl.java:53)
at org.hibernate.engine.spi.CacheInitiator.initiateService(CacheInitiator.java:28)
at org.hibernate.engine.spi.CacheInitiator.initiateService(CacheInitiator.java:20)
at org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.initiateService(SessionFactoryServiceRegistryImpl.java:49)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:254)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:228)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:207)
at org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.getService(SessionFactoryServiceRegistryImpl.java:68)
at org.hibernate.internal.SessionFactoryImpl.(SessionFactoryImpl.java:244)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:444)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:879)
I added this dependency to pom.xml:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.2.2.Final</version>
</dependency>
Then, added these lines to application.properties:
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.cache.use_query_cache=true
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
At the top of the entity classes, I added these caching annotations:
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
In the configuration file, I added #EnableCaching annotation.
Here are other beans and methods I added to configuration file:
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("spring.datasource.driver-class-name")));
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("spring.datasource.url")));
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("spring.datasource.username")));
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("spring.datasource.password")));
return dataSource;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", env.getProperty("hibernate.cache.use_second_level_cache"));
hibernateProperties.setProperty("hibernate.cache.use_query_cache", env.getProperty("hibernate.cache.use_query_cache"));
return hibernateProperties;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
Is there something wrong with the configuration methods?
Note: This is a spring-boot project.

Related

Spring Boot - multiple datasource - hbm2ddl issue

I have multiple datasources defined in my Spring Boot (version 2.1.3.Final) app, and for one of these I am setting hbm2ddl to update. However, if I ran the application for the second time, it always tries to execute CREATE TABLE statement (instead of alter, or no statement in case of no change in entity)
The datasource definition look like this:
// #formatter:off
#EnableJpaRepositories(entityManagerFactoryRef = "triggerEMF", transactionManagerRef = "triggerTM", basePackages = {
"com.customer.trigger.repository" }, excludeFilters = #Filter(CDHRepository.class))
// #formatter:on
#Configuration
#EnableTransactionManagement
public class TriggerDSConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(TriggerDSConfig.class);
#Autowired
private Environment env;
#Primary
#Bean(name = "triggerDS")
#ConfigurationProperties("trigger.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "triggerTM")
public PlatformTransactionManager psqlTransactionManager(#Qualifier("triggerEMF") EntityManagerFactory customerEntityManagerFactory) {
return new JpaTransactionManager(customerEntityManagerFactory);
}
#Primary
#Bean(name = "triggerEMF")
public LocalContainerEntityManagerFactoryBean psqlEntityManager(EntityManagerFactoryBuilder builder) throws SQLException {
Map<String, String> props = new HashMap<String, String>();
props.put("hibernate.hbm2ddl.auto", env.getProperty("trigger.hbm2ddl"));
props.put("hibernate.dialect", env.getProperty("trigger.dialect"));
props.put("hibernate.default_schema", "public");
DataSource ds = dataSource();
ds.getConnection(); // eager connection pool init
return builder.dataSource(ds) //
.packages("com.customer.trigger.model.entity") //
.persistenceUnit("trigger-pu") //
.properties(props) //
.build(); //
}
// Initialise & Populate DS in Local profile
#Bean
#Profile({ "local" })
public DataSourceInitializer triggerEngineDBInit(#Qualifier("triggerDS") DataSource datasource) {
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
resourceDatabasePopulator.addScript(new ClassPathResource("db/schema-triggerengine.sql"));
resourceDatabasePopulator.addScript(new ClassPathResource("db/data-triggerengine.sql"));
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(datasource);
dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
dataSourceInitializer.setEnabled(env.getProperty("trigger.datasource.initialize", Boolean.class, false));
return dataSourceInitializer;
}
}
The YAML definition of this datasource look like this:
trigger:
datasource:
jdbcUrl: jdbc:h2:~/triggerdb2;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL
username: sa
password: sa
hbm2ddl: update
dialect: org.hibernate.dialect.H2Dialect
I can confirm that DDL scripts are not executed. It's hbm2ddl which is failing. I was debugging it quite deep and I've got as far as this class:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java
And this is the problematic piece of code:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java#L69-L71
The table information is null - so hibernate generate create table statement (instead of alter or no statement). But of course the table exists, so this fails with following error:
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67)
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:559)
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigrator.java:504)
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.createTable(AbstractSchemaMigrator.java:277)
at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:71)
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.performMigration(AbstractSchemaMigrator.java:207)
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:114)
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:183)
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:72)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:310)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:467)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:939)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1821)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1758)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
at com.customer.trigger.Application.main(Application.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:558)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.h2.jdbc.JdbcSQLException: Table "SUGGESTION_VOD__C" already exists; SQL statement:
This issue occurs with PostgreSQL as well (not just H2)
Any ideas?
Reproducer:
https://github.com/agiertli/spring-boot-hbm-ddl-issue
I ran your project in my local. you are correct when I ran twice I got the table already exist error. and solution was
Map<String, String> props = new HashMap<String, String>();
props.put("hibernate.hbm2ddl.auto", "update");
props.put("hibernate.dialect", env.getProperty("trigger.dialect"));
props.put("hibernate.default_schema", env.getProperty("trigger.schema"));
you are maybe missing some property in your yml. Also use #Value for these properties.
After removing following yaml configuration:
# temp:
# use_jdbc_metadata_defaults: "false"
The issue no longer occurs. Of course, now we are receiving strange CLOB related errors, but they do not seem to have a real impact so that's a separate issue.
i read your problem. the problem is not depend on database and I think your problem is that you don't use database version control for your project.
the version control handle the all of these things for you and(it knows when to create or alter and very much more).
these are good database version control that you can use
liquibase
flyway
my opinion is to use liquibase because its easier with very good documentation

Spring 4 using class based database initialization instead of xml bean

In our Spring 4 application, we currently configure database connection in applicationContext.xml :
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="poolName" value="springHikariCP" />
<property name="dataSourceClassName" value="org.postgresql.ds.PGSimpleDataSource" />
<property name="maximumPoolSize" value="10" />
<property name="idleTimeout" value="30000" />
<property name="dataSourceProperties">
<props>
<prop key="url">jdbc:postgresql://google/mydb?cloudSqlInstance=project:region:myinstance&socketFactory=com.google.cloud.sql.postgres.SocketFactory</prop>
<prop key="user">postgres</prop>
<prop key="password">password</prop>
</props>
</property>
</bean>
Instead on defining this in applicationContext.xml, can I define the database configuration in a class such as the following:
HikariConfig config = new HikariConfig();
config.setJdbcUrl(JDBC_URL);
config.setUsername(DB_USER);
config.setPassword(DB_PASS);
....
Is it possible to do this?
You can define properties on HikariDataSource
public DataSource getDataSource(){
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(JDBC_URL);
dataSource.setUsername(DB_USER);
dataSource.setPassword("DB_PASS);
return dataSource;
}
public class HikariDataSource extends HikariConfig implements DataSource, Closeable
Then declare your method as a #Bean in #Configuration class
#Bean
public DataSource dataSource() {
return DataSourceClass.getDataSource();
}
This is a king of more general solution, not specific for your DataSource but it can be useful:
#Configuration
public class DataSourceConfig {
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:test");
dataSourceBuilder.username("SA");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
}
What you actually need is a DataSource bean (in XML you define a bean right?) and also put it into #Configuration class so the Spring could pick it up.
What you put in that method should return pre-configured for your specific case data source.
Also, you could try something like this:
#Bean
public DataSource dataSource() throws SQLException {
HikariConfig config = new HikariConfig("/hikari.properties");
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
But then you need hikari.properties file in the classpath. Example:
driverClassName=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/myDb
connectionTestQuery=SELECT 1
maximumPoolSize=20
username=...
password=...
Remember to keep #Bean in #Configuration :)
Simply annotate a class with #Configuration, making sure that this class is scanned by Spring when the application starts.
Within this class declare a datasource as #Bean in this way:
#Configuration
public class DataSourceConfig {
// More code...
#Bean(name="datasource")
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(org.postgresql.Driver.class.getName());
hikariConfig.setJdbcUrl("jdbc:postgresql://....");
hikariConfig.setUsername("postgres");
hikariConfig.setPassword("password");
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setIdleTimeout(30000);
hikariConfig.setPoolName("springHikariCP");
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
return hikariDataSource;
}
// More code...
}
And that's all.

How to get Jndi name in java class in Spring

I want to get a JNDI value in my java conf Spring.
the context.xml file:
<Environment name="foo" type="java.lang.String" value="bar" />
the xml spring config:
<jee:jndi-lookup id="foobar" jndi-name="java:comp/env/foo" default-value="nothing"/>
the java spring config:
#Bean
public String foobar() {
???
}
If someone could you give me an example it'll be very useful. Thanks
Edit:
Try autowiring JndiObjectFactoryBean it will have a getJndiName() method, which is inherited from JndiObjectLocator will expose your jndi name
Try this
#Bean
public DataSource dataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
DataSource dataSource = dsLookup.getDataSource("jdbc/yourJdbcGoesHere");
return dataSource;
}

Is there a way to integrate spring-batch-admin and spring-boot properly?

According to the documentation spring batch admin is very easy to embed into the existing application. Simply copying web.xml and index.jsp then adding needed dependencies is enough getting it to work.
But if I want to use it in an existing spring boot project it getting worse. According to this example the configuration is a bit hacky but it works. UNTIL I try to use #EnableBatchProcessing annotation in my configuriton bean. Then I get the following exception.
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobBuilders' defined in class path resource [org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.batch.core.configuration.annotation.JobBuilderFactory org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders() throws java.lang.Exception] threw exception; nested exception is java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:597)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
at demo.Application.main(Application.java:35)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.batch.core.configuration.annotation.JobBuilderFactory org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders() throws java.lang.Exception] threw exception; nested exception is java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:188)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:586)
... 17 more
Caused by: java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.jobRepository(<generated>)
at org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders(AbstractBatchConfiguration.java:58)
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.CGLIB$jobBuilders$8(<generated>)
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04$$FastClassBySpringCGLIB$$d88bd05f.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.jobBuilders(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
... 18 more
My configuration is quite simple I have two configuration beans
#Configuration
#ImportResource({"classpath:/org/springframework/batch/admin/web/resources/servlet-config.xml",
"classpath:/org/springframework/batch/admin/web/resources/webapp-config.xml"})
public class BatchAdminConfiguration {
}
and
#Configuration
#EnableBatchProcessing
public class BatchImporterConfiguration {
}
When I remove #EnableBatchProcessing and try to create jobs with JobBuilderFactory and use #StepScope annotation I'm getting other ClassCastExceptions.
Now I'm using xml based configuration to create jobs, steps and other beans. It work well but I would actually preffer xml free configuration. Is there a way to easily integrate spring boot, spring batch and spring batch admin ?
Spring Batch Admin 2.0-BUILD-SNAPSHOT introduce a new Annoation #EnableBatchAdmin for easy to integrate with spring boot.
There is also a samples project https://github.com/spring-projects/spring-batch-admin-samples.
The short answer is that you won't want to use #EnableBatchProcessing with Spring Batch Admin. SBA provides a number of beans on a global scale that the #EnableBatchProcessing also provides. SBA 2.0 (currently in development) will probably fill the gaps between what is currently there and what #EnableBatchProcessing provides (specifically providing the JobBuilderFactory and StepBuilderFactory).
To get yourself running, you should be able to (I haven't tired this myself) configure in the META-INF/spring/batch/override/ directory a JobBuilderFactory and a StepBuilderFactory for global use. From there, you can use XML files in the META-INF/spring/batch/jobs directory that do nothing more than component scan for your #Configuration classes. However, leave off the #EnableBatchProcessing because of the duplication of beans.
For the record, this isn't an Spring Boot issue since #EnableBatchProcessing is a Spring Batch annotation, not a Boot one.
to complete the answer, here is the code to create the two beans once you disable the #EnableBatchProcessing annotation
#Autowired
JobRepository jobRepository;
#Autowired
PlatformTransactionManager transactionManager;
#Bean
public JobBuilderFactory jobBuilderFactory() {
return new JobBuilderFactory(jobRepository);
}
#Bean
public StepBuilderFactory stepBuilderFactory() {
return new StepBuilderFactory(jobRepository, transactionManager);
}
I've a working version here based on the same example (I forked the original one): https://github.com/vesperaba/spring-batch-admin-spring-boot.
I followed Michael Minella advice and I overwrote the SpringBatch property holder with a custom one.
I also added a job to check it's working now
This ClassCastException is caused by
classpath:/org/springframework/batch/admin/web/resources/servlet-config.xml
loading
META-INF/spring/batch/servlet/resources/resource-context.xml
which contains
<mvc:annotation-driven />
This conflicts with the mvc configuration in the Spring Java configuration class. The following class can be used to embed Spring Batch Admin within an existing application that uses Java configuration.
#Configuration
#EnableWebMvc
#ImportResource({"classpath*:/META-INF/spring/batch/bootstrap/**/*.xml"
, "classpath*:/META-INF/spring/batch/override/**/*.xml"
, "classpath*:/org/springframework/batch/admin/web/resources/webapp-config.xml"
, "classpath*:/META-INF/spring/batch/servlet/manager/**/*.xml"
, "classpath:base-menu-config.xml"
})
public class SpringBatchAdminConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/META-INF/");
}
#Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
#Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
#Bean
public BeanNameViewResolver beanNameViewResolver() {
return new BeanNameViewResolver();
}
#Bean(name = "defaultResources")
public PropertiesFactoryBean defaultResources() {
return new PropertiesFactoryBean();
}
#Bean(name = "jsonResources")
public PropertiesFactoryBean jsonResources() {
return new PropertiesFactoryBean();
}
#Bean
public HomeController homeController() throws IOException {
HomeController homeController = new HomeController();
homeController.setDefaultResources(defaultResources().getObject());
homeController.setJsonResources(jsonResources().getObject());
return homeController;
}
#Bean
public MenuManager menuManager() {
return new MenuManager();
}
#Bean(name = "freemarkerConfig")
public HippyFreeMarkerConfigurer hippyFreeMarkerConfigurer() {
HippyFreeMarkerConfigurer hippyFreeMarkerConfigurer = new HippyFreeMarkerConfigurer();
hippyFreeMarkerConfigurer.setTemplateLoaderPaths("/WEB-INF/web", "classpath:/org/springframework/batch/admin/web");
hippyFreeMarkerConfigurer.setPreferFileSystemAccess(false);
hippyFreeMarkerConfigurer.setFreemarkerVariables(Collections.singletonMap("menuManager", (Object) menuManager()));
Properties freemarkerSettings = new Properties();
freemarkerSettings.put("default_encoding", "UTF-8");
freemarkerSettings.put("output_encoding", "UTF-8");
hippyFreeMarkerConfigurer.setFreemarkerSettings(freemarkerSettings);
return hippyFreeMarkerConfigurer;
}
public AjaxFreeMarkerView parentLayout() {
AjaxFreeMarkerView ajaxFreeMarkerView = new AjaxFreeMarkerView();
FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver();
freeMarkerViewResolver.setExposeSpringMacroHelpers(false);
freeMarkerViewResolver.setAllowRequestOverride(true);
ajaxFreeMarkerView.setViewResolver(freeMarkerViewResolver);
Properties attributes = new Properties();
attributes.put("titleCode", "home.title");
attributes.put("titleText", "Spring Batch Admin");
ajaxFreeMarkerView.setAttributes(attributes);
return ajaxFreeMarkerView;
}
#Value("#{resourceService.servletPath}")
private String servletPath;
#Bean(name="standard")
public AjaxFreeMarkerView standard() {
AjaxFreeMarkerView standard = parentLayout();
standard.setUrl("/layouts/html/standard.ftl");
standard.setContentType("text/html;charset=UTF-8");
standard.getAttributesMap().put("body", "/layouts/html/home.ftl");
standard.getAttributesMap().put("servletPath", servletPath);
return standard;
}
#Bean(name="standard.rss")
public AjaxFreeMarkerView standardRss() {
AjaxFreeMarkerView standardRss = parentLayout();
standardRss.setUrl("/layouts/html/standard.ftl");
standardRss.setContentType("text/xml");
standardRss.getAttributesMap().put("body", "/layouts/rss/home.ftl");
standardRss.getAttributesMap().put("servletPath", servletPath);
return standardRss;
}
#Bean(name="standard.json")
public AjaxFreeMarkerView standardJson() {
AjaxFreeMarkerView standardJson = parentLayout();
standardJson.setUrl("/layouts/json/standard.ftl");
standardJson.setContentType("application/json");
standardJson.getAttributesMap().put("body", "/layouts/json/home.ftl");
standardJson.getAttributesMap().put("servletPath", servletPath);
return standardJson;
}
#Bean(name="home")
public AjaxFreeMarkerView home() {
return standard();
}
#Bean(name="home.json")
public AjaxFreeMarkerView homeJson() {
AjaxFreeMarkerView homeJson = standardJson();
homeJson.getAttributesMap().put("body", "/layouts/json/home.ftl");
return homeJson;
}
}
A single XML file is also required for the abstract base menu which is referenced elsewhere in the Spring Batch Admin project. This is required as abstract beans can not be provided from a Spring Java configuration.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="baseMenu" abstract="true">
<property name="prefix" value="#{resourceService.servletPath}" />
</bean>
</beans>
Maven dependencies. Take care to ensure only a single version of the base Spring framework is pulled in by Maven.
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-admin-manager</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
<version>1.8.0.10</version>
</dependency>
Spring batch also expects in a default configuration for the following files to exist at the root of the classpath.
batch-default.properties
# Default placeholders for database platform independent features
batch.remote.base.url=http://localhost:8080/spring-batch-admin-sample
# Non-platform dependent settings that you might like to change
batch.job.configuration.file.dir=/tmp/config
build.artifactId=1
build.version=1
build.buildNumber=1
build.timestamp=1
log.enableConsole=true
batch-hsql.properties
# Placeholders batch.*
# for HSQLDB:
batch.jdbc.driver=org.hsqldb.jdbcDriver
batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
# Override and use this one in for a separate server process so you can inspect
# the results (or add it to system properties with -D to override at run time).
# batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
batch.jdbc.user=sa
batch.jdbc.password=
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer
batch.schema.script=classpath*:/org/springframework/batch/core/schema-hsqldb.sql
batch.drop.script=classpath*:/org/springframework/batch/core/schema-drop-hsqldb.sql
batch.business.schema.script=classpath:/business-schema-hsqldb.sql
# Non-platform dependent settings that you might like to change
# batch.data.source.init=true
business-schedule-hsqldb.sql
DROP TABLE ERROR_LOG IF EXISTS;
CREATE TABLE ERROR_LOG (
JOB_NAME CHAR(20) ,
STEP_NAME CHAR(20) ,
MESSAGE VARCHAR(300) NOT NULL
) ;

How to get Spring Data Neo4j and Spring Data JPA to work together?

I have an application that does some batch jobs using MySQL and, via REST, Neo4j server version.
I can't figure out how to make them to work together correctly: I can get to make work both of them, but not at the same time. The posts I've found around are not specific to the server version of Neo4j, and maybe that's where the problem is, since everything else seems ok to me.
My configuration:
JpaConfig
#Configuration
#EnableTransactionManagement(order=Ordered.HIGHEST_PRECEDENCE)
#PropertySource("META-INF/database.properties")
#ImportResource("classpath*:META-INF/repository.xml")
public class JpaConfig {
#Autowired
Environment env;
#Bean(destroyMethod = "close")
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setDriverClassName(env.getProperty("database.driverClassName"));
dataSource.setUrl(env.getProperty("database.url"));
dataSource.setUsername(env.getProperty("database.username"));
dataSource.setPassword(env.getProperty("database.password"));
dataSource.setTestOnBorrow(true);
dataSource.setTestOnReturn(true);
dataSource.setTestWhileIdle(true);
dataSource.setTimeBetweenEvictionRunsMillis(1800000);
dataSource.setNumTestsPerEvictionRun(3);
dataSource.setMinEvictableIdleTimeMillis(1800000);
dataSource.setValidationQuery("SELECT 1");
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource());
entityManagerFactory.setPackagesToScan("it.smartblue.mcba.domain");
entityManagerFactory.setJpaDialect(new HibernateJpaDialect());
Map<String, String> jpaProperties = new HashMap<>();
jpaProperties.put("hibernate.connection.charSet", "UTF-8");
jpaProperties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.EJB3NamingStrategy");
jpaProperties.put("hibernate.bytecode.provider", "javassist");
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
entityManagerFactory.setJpaPropertyMap(jpaProperties);
entityManagerFactory.setPersistenceProvider(new HibernatePersistence());
return entityManagerFactory;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
Neo4j.xml
<!-- neo4j configuration -->
<neo4j:config graphDatabaseService="graphDatabaseService" entityManagerFactory="entityManagerFactory"/>
<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase">
<constructor-arg index="0" value="http://192.168.11.186:7474/db/data" />
</bean>
<neo4j:repositories base-package="it.smartblue.mcba.neo4j.repository" />
With this configuration Mysql works perfectly, but Neo4j doesn't save any property to the nodes it creates.
If I remove the attribute entityManagerFactory="entityManagerFactory" Neo4j works, but I can't write to MySQL.
My services methods are annotated with #Transactional or #Neo4jTransactional, not both at the same time.
Inside the class org.springframework.data.neo4j.rest.SpringRestGraphDatabase for the graphDatabaseService bean I've found:
#Override
public Transaction beginTx() {
// return super.beginTx();
return new NullTransaction();
}
#Override
public TransactionManager getTxManager() {
return new NullTransactionManager();
}
Maybe it's a work in progress? Or maybe I miss something...
I'm using Spring 3.1.2, Hibernate 4.1.4. Here is part of my pom.xml.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.2.0.RC1</version>
</dependency>
<!-- Neo4j dependencies -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>2.1.0.RC4</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-rest</artifactId>
<version>2.1.0.RC4</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-cross-store</artifactId>
<version>2.1.0.RC4</version>
</dependency>
Finally I made it.
Instead of having two different transactionManagers, now I have only one ChainedTransactionManager.
I removed the transactionManager bean from JpaConfig and the neo4j.xml file, and added the following Neo4jConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.rest.SpringRestGraphDatabase;
import org.springframework.data.neo4j.transaction.ChainedTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
#EnableNeo4jRepositories(basePackages = { "it.smartblue.mcba.neo4j.repository" })
#Configuration
public class Neo4jConfig extends Neo4jConfiguration {
#Autowired
LocalContainerEntityManagerFactoryBean entityManagerFactory;
#Bean
public SpringRestGraphDatabase graphDatabaseService() {
return new SpringRestGraphDatabase("http://192.168.11.186:7474/db/data");
}
#Override
#Bean(name = "transactionManager")
public PlatformTransactionManager neo4jTransactionManager() throws Exception {
return new ChainedTransactionManager(new JpaTransactionManager(entityManagerFactory.getObject()),
new JtaTransactionManagerFactoryBean(graphDatabaseService()).getObject());
}
}
Now I have to use on my methods only the #Transactional annotation

Categories

Resources