migrate Spring app from XML to annotations - java

I've inherited a Spring 3 app that uses XML files to define and wire together the beans. I know that since Spring 2 these can mostly be replaced with annotations. I would like Spring to:
detect beans by scanning certain packages for classes with whatever annotation is used to indicate a Spring bean
attempt to autowire-by-name and field in a bean that I have marked with the relevant annotation
What are steps I need to take to make this happen?

The manual has all the info you need: http://static.springsource.org/spring/docs/3.1.0.M1/spring-framework-reference/html/beans.html#beans-annotation-config
The TL;DR version is that you need to add <context:annotation-config/> to your spring config and then annotate your beans with #Component and in your setter of properties annotate with #Autowired

I shall give you a xml configuration with it anotation based config equivalent respectively, so that you will easily see how to change your bean from xml to java and vice-versa
`<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:component-scan base-package="com.mycompany.backendhibernatejpa.controller" />
<mvc:annotation-driven />
<bean id="iAbonneDao" class="com.mycompany.backendhibernatejpa.daoImpl.AbonneDaoImpl"/>
<bean id="iAbonneService" class="com.mycompany.backendhibernatejpa.serviceImpl.AbonneServiceImpl"/>
<!-- couche de persistance JPA -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="generateDdl" value="true" />
<property name="showSql" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:bd.properties"/>
</bean>
<!-- la source de donnéees DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="${bd.driver}" />
<property name="url" value="${bd.url}" />
<property name="username" value="${bd.username}" />
<property name="password" value="${bd.password}" />
</bean>
<!-- le gestionnaire de transactions -->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- traduction des exceptions -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- annotations de persistance -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>`
this file is named springrest-sevlet. actually you can give the name
you want followed by "-servlet" and mention that name in the file
web.xml
` <web-app>
<display-name>Gescable</display-name>
<servlet>
<servlet-name>springrest</servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springrest</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>`
the two files should be place in the folder "WEB-INF".
Now the equivalent with anotation based config
`/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.backendhibernatejpaannotation.configuration;
import com.mycompany.backendhibernatejpaannotation.daoImpl.AbonneDaoImpl;
import com.mycompany.backendhibernatejpaannotation.daoInterface.IAbonneDao;
import com.mycompany.backendhibernatejpaannotation.serviceImpl.AbonneServiceImpl;
import com.mycompany.backendhibernatejpaannotation.serviceInterface.IAbonneService;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
*
* #author vivien saa
*/
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.mycompany.backendhibernatejpaannotation")
public class RestConfiguration {
#Bean
public IAbonneDao iAbonneDao() {
return new AbonneDaoImpl();
}
#Bean
public IAbonneService iAbonneService() {
return new AbonneServiceImpl();
}
// #Bean
// public PropertyPlaceholderConfigurer placeholderConfigurer() {
// PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
// placeholderConfigurer.setLocations("classpath:bd.properties");
// return placeholderConfigurer;
// }
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/gescable");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
#Bean
public HibernateJpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setShowSql(true);
return jpaVendorAdapter;
}
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource());
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactory.setLoadTimeWeaver(loadTimeWeaver());
return entityManagerFactory;
}
#Bean
public JpaTransactionManager jpaTransactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
#Bean
public PersistenceExceptionTranslationPostPro`enter code here`cessor persistenceExceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
return new PersistenceAnnotationBeanPostProcessor();
}
}`
this file must be accompanied by this one
`
package com.mycompany.backendhibernatejpaannotation.configuration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
*
* #author vivien saa
*/
public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RestConfiguration.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
`

I think the first thing to do is to go slow. Just pick one service at a time and migrate it as you have cause to touch it. Mucking with a ton of spring config is a sure way screw yourself in production with a dependency you only need once in a while.

Related

Spring MVC Hibernate without xml using DriverManagerDataSource how to map properties

I have these configurations inside spring-mvc-crud-demo-servlet.xml file which I would like to transfer to a Java config class:
<!-- Define Spring MVC view resolver -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- Step 1: Define Database DataSource / connection pool -->
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/web_User_tracker?useSSL=false&serverTimezone=UTC" />
<property name="user" value="springstudent" />
<property name="password" value="springstudent" />
<!-- these are connection pool properties for C3P0 -->
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxIdleTime" value="30000" />
</bean>
<!-- Step 2: Setup Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.luv2code.springdemo.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<!-- Step 3: Setup Hibernate transaction manager -->
<bean id="myTransactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- Step 4: Enable configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="myTransactionManager" />
<!-- Add support for reading web resources: css, images, js, etc ... -->
<mvc:resources location="/resources/" mapping="/resources/**"></mvc:resources>
I have these Java config files:
#Configuration
#EnableWebMvc
#ComponentScan("myappname")
public class DemoAppConfig implements WebMvcConfigurer {
}
public class MySpringMvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { DemoAppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
#Configuration
public class DatabaseConfig {
#Bean
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource bds = new DriverManagerDataSource();
bds.setDriverClassName("org.postgresql.Drive");
bds.setUrl("jdbc:postgresql://localhost:5432/myappname");
bds.setUsername("postgres");
bds.setPassword("mypass");
return bds;
}
}
I'm not sure however how to map properties like minPoolSize or the SessionFactory bean.
I also have this hibernate.cfg.xml file:
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost/myappname</property>
<property name="connection.username">postgres</property>
<property name="connection.password">mypass</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">5</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.PostgreSQL9Dialect</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.internal.NoCachingRegionFactory</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<mapping class="myappname.entity.User"/>
</session-factory>
</hibernate-configuration>
PS I'm using Spring MVC, not Spring Boot. I don't want to use Spring Boot for this project, because I want to understand how to do it with Spring MVC, so please, don't recommend it.
Following is a working code that I used to learn spring - hibernate integration.
You already have the datasource configured and you may modify the code as per your requirement. The transaction and session management is taken care of in this configuration .
package rg.so.poc.txn.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableTransactionManagement
public class DataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
//.addScript("data.sql")
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emFactoryBean = new LocalContainerEntityManagerFactoryBean();
emFactoryBean.setDataSource(dataSource);
emFactoryBean.setPackagesToScan(new String[] { "rg.so.poc.txn.entity" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
emFactoryBean.setJpaVendorAdapter(vendorAdapter);
emFactoryBean.setJpaProperties(hibernateProperties());
return emFactoryBean;
}
private final Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "none");
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return hibernateProperties;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}

Dependency injection Spring

I am building a web application using spring MVC which is connected to the database. This is part of my bean config file.
<!-- Define Database DataSource / connection pool -->
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/java_task?useSSL=false" />
<property name="user" value="me" />
<property name="password" value="me" />
<!-- these are connection pool properties for C3P0 -->
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxIdleTime" value="30000" />
</bean>
<!-- Define Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.javatask.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
As you can see my data source is defined in a hard way. In code I am using #Autowired annotation to inject sessionFactory. But I would like to inject sessionFactory (jdbc, username, password etc.) with data which I will obtain from user during run time. For example he will write me those data to the textfeild and then I will create sessionFactory (based on his data) which will be connected to his database.
I was looking for an answer but unluckily I have not found anything that would fit to this problem.
There are multiple ways that spring bean definition can be changed in run time. One way is to refresh the Spring Application Context in run time which would have the bean definition specific to the functionality rather than all bean definition.
((ConfigurableApplicationContext)applicationContext).refresh();
Let me explain using one simple use case:
Create a PropertyBean which will load value from external properties file.
Create PropertiesApplicationContext which would have only PropertyBean definition config rather than all beans belongs the application.
Use ConfigurableApplicationContext refresh() method to reload ProperiesBean definition in run time.
More details about below example code snippet:
Web Application should display a Property Value from a bean which
will read the value from a external property file.
Property value will change anytime in external properties file.
Web Application should not restarted.
Here, bean definition should be changed in run time.
Spring Boot Application Code:
#SpringBootApplication(scanBasePackages = {"vijay.controller","vijay.configuration"})
public class SpringContextRefreshApplication extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringContextRefreshApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringContextRefreshApplication.class, args);
}
}
MVC Configuration:
package vijay.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
/**
* MVC Configuration
* #author Vijay
*/
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
registry.viewResolver(resolver);
}
}
Custom (Property) Application Context Aware:
package vijay.configuration;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* PropertyApplicationContext Class to load Property Configuration XML.
* #author Vijay
*/
public class PropertyApplicationContext implements ApplicationContextAware{
private ApplicationContext applicationContext;
private static PropertyApplicationContext propertyApplicationContext;
private PropertyApplicationContext(){
applicationContext = new ClassPathXmlApplicationContext("property-config.xml");
}
#Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
if(applicationContext == null){
this.applicationContext = ac;
}
}
public ApplicationContext getApplicationContext(){
return applicationContext;
}
public static PropertyApplicationContext getInstance(){
if(propertyApplicationContext == null){
propertyApplicationContext = new PropertyApplicationContext();
}
return propertyApplicationContext;
}
}
MVC Controller:
package vijay.controller;
import vijay.beans.PropertyDto;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import vijay.configuration.PropertyApplicationContext;
/**
* Property List Controller.
* #author Vijay
*/
#Controller
public class PropertyController {
#RequestMapping("/")
public String testController(){
System.out.println("vijay.controller.PropertyController.testController()");
return "sample";
}
#RequestMapping(path = "/getProperties")
public String testPropertiesController(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.testPropertiesController() " + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
/**
* Method will refresh ApplicationContext
* #param modelMap as ModelMap
* #return viewName as String
*/
#RequestMapping(path = "/refreshProperties")
public String refreshBean(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
// Refresh Application Context
((ConfigurableApplicationContext)applicationContext).refresh();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.refreshBean()" + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
}
Spring Bean (Property-config.xml) definition File:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:C://sample.properties" />
</bean>
<context:annotation-config/>
<context:component-scan base-package="vijay.beans"/>
</beans>
View JSP (sample.jsp):
<!DOCTYPE html>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<body>
<div>
<div>
<h1>Spring Boot JSP Example</h1>
<h2>Property Value :: ${message} !</h2>
</div>
</div>
</body>
</html>
Here, MVC Controller has /refreshProperties which will refresh the PropertiesBean defined in PropertiesApplicationContext. As properties-config.xml the only bean definition defined in PropertiesApplicationContext, propertiesBean alone will be refreshed and no other bean definition will be refreshed.
Similar way, you can create Application Context by extending ApplicationContextAware which will have bean definition of sessionFactory. You can get SessionFactory wherever required from this context and refresh the context wherever required.
This is one way to achieve the bean definition change in run time.
Did you try this [Change SessionFactory datasource jdbcurl late in runtime] (Change SessionFactory datasource jdbcurl late in runtime)?

How to secure a Spring soap service with certificates

I managed to create a soap server and client that requires a username and password to call functions. The following code shows how i've done this
Client:
package hello;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SpringUsernamePasswordCallbackHandler;
#Configuration
public class SoftlayerConfiguration {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("be.elision.soap.cloud");
return marshaller;
}
#Bean
public SoftlayerClient softlayerClient(Jaxb2Marshaller marshaller) {
SoftlayerClient client = new SoftlayerClient();
client.setDefaultUri("http://192.168.137.107:8080/ws");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
client.setInterceptors(new ClientInterceptor[]{securityInterceptor()});
return client;
}
#Bean
XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setSecureRequest(true);
securityInterceptor.setValidateRequest(true);
securityInterceptor.setCallbackHandler(callbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
return securityInterceptor;
}
#Bean
SpringUsernamePasswordCallbackHandler callbackHandler() {
SecurityContextHolder.setContext(new SecurityContextImpl());
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user", "password"));
return new SpringUsernamePasswordCallbackHandler();
}
}
Server:
package be.elision.main;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler;
import org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(
ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "devices")
public DefaultWsdl11Definition defaultWsdl11Definition(
XsdSchema devicesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("DevicesPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://elision.be/soap/cloud");
wsdl11Definition.setSchema(devicesSchema);
return wsdl11Definition;
}
// toevoegen van xsd aan bean
#Bean
public XsdSchema devicesSchema() {
return new SimpleXsdSchema(new ClassPathResource("devices.xsd"));
}
#Bean
XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(callbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource(
"securityPolicy.xml"));
return securityInterceptor;
}
#Bean
SimplePasswordValidationCallbackHandler callbackHandler() {
SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
callbackHandler.setUsersMap(Collections
.singletonMap("user", "password"));
return callbackHandler;
}
#Bean
PayloadLoggingInterceptor payloadLoggingInterceptor() {
return new PayloadLoggingInterceptor();
}
#Bean
PayloadValidatingInterceptor payloadValidatingInterceptor() {
final PayloadValidatingInterceptor payloadValidatingInterceptor = new PayloadValidatingInterceptor();
payloadValidatingInterceptor.setSchema(new ClassPathResource(
"devices.xsd"));
return payloadValidatingInterceptor;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(payloadLoggingInterceptor());
interceptors.add(payloadValidatingInterceptor());
interceptors.add(securityInterceptor());
}
}
Security policy:
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:UsernameToken digestPassword="false" useNonce="false" />
</xwss:SecurityConfiguration>
But now i want to replace this username and password authentication with a certificate. I prefere not to do this in xml, but rather implement this in my existing code as shown above.
We finaly figured this out some months ago, from what I remember our configuration looked like this.
add this to your web.xml
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
this needs to be present in your mvc-dispatcher-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/web-services
http://www.springframework.org/schema/web-services/web-services-2.0.xsd">
<context:component-scan base-package="com.yourpackage" />
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory" />
<property name="defaultUri"
value="${backend.ip}devices" />
<property name="interceptors">
<list>
<ref local="xwsSecurityInterceptor" />
</list>
</property>
</bean>
<bean id="xwsSecurityInterceptor"
class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="callbackHandlers">
<list>
<ref bean="keyStoreHandler" />
</list>
</property>
</bean>
<bean id="keyStore"
class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="password" value="yourpassword" />
<property name="location" value="/WEB-INF/yourkeystore.jks" />
</bean>
<bean id="keyStoreHandler"
class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore" />
<property name="privateKeyPassword" value="yourpassword" />
<property name="defaultAlias" value="client" />
</bean>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<!-- LOAD PROPERTIES -->
<context:property-placeholder
location="WEB-INF/config.properties"
ignore-unresolvable="true" />
<mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:annotation-driven />
</beans>
The jks file is a java keystore file wich holds your certificate.
SecurityPolicy.xml
<xwss:SecurityConfiguration dumpMessages="true" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:Sign includeTimestamp="true">
<xwss:X509Token certificateAlias="yourcertificatealias" />
</xwss:Sign>
</xwss:SecurityConfiguration>
This is all done using the xws-security library from oracle, you need the following dependency for this.
<dependency>
<groupId>com.sun.xml.wss</groupId>
<artifactId>xws-security</artifactId>
<version>3.0</version>
<exclusions>
<exclusion>
<groupId>javax.xml.crypto</groupId>
<artifactId>xmldsig</artifactId>
</exclusion>
</exclusions>
</dependency>
We finnaly got this working after we found some good explanation about certificate authentication in this awesome book!
'Spring Web Services 2 Cookbook by Hamidreza Sattari'
If you have any further questions regarding this implementation then just ask away! :d

hibernate.cfg.xml alternative in java file

I am using spring framework and for database I am using hibernate.cfg.xml in the following way
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="show_sql">false</property>
<property name="hbm2ddl.auto">update</property>
<mapping class="com.lynas.test.Student" />
</session-factory>
</hibernate-configuration>
Now is there any way to write this code in the java file. Like a bean or something??
The Answer is YES. from http://java.dzone.com/articles/springhibernate-application
package com.sivalabs.springmvc.config;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* #author SivaLabs
*
*/
#Configuration
public class RepositoryConfig
{
//${jdbc.driverClassName}
#Value("${jdbc.driverClassName}") private String driverClassName;
#Value("${jdbc.url}") private String url;
#Value("${jdbc.username}") private String username;
#Value("${jdbc.password}") private String password;
#Value("${hibernate.dialect}") private String hibernateDialect;
#Value("${hibernate.show_sql}") private String hibernateShowSql;
#Value("${hibernate.hbm2ddl.auto}") private String hibernateHbm2ddlAuto;
#Bean()
public DataSource getDataSource()
{
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory)
{
HibernateTransactionManager htm = new HibernateTransactionManager();
htm.setSessionFactory(sessionFactory);
return htm;
}
#Bean
#Autowired
public HibernateTemplate getHibernateTemplate(SessionFactory sessionFactory)
{
HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory);
return hibernateTemplate;
}
#Bean
public AnnotationSessionFactoryBean getSessionFactory()
{
AnnotationSessionFactoryBean asfb = new AnnotationSessionFactoryBean();
asfb.setDataSource(getDataSource());
asfb.setHibernateProperties(getHibernateProperties());
asfb.setPackagesToScan(new String[]{"com.sivalabs"});
return asfb;
}
#Bean
public Properties getHibernateProperties()
{
Properties properties = new Properties();
properties.put("hibernate.dialect", hibernateDialect);
properties.put("hibernate.show_sql", hibernateShowSql);
properties.put("hibernate.hbm2ddl.auto", hibernateHbm2ddlAuto);
return properties;
}
}
A more modern, working solution:
#Configuration
#ComponentScan(basePackageClasses = {массив пакетов с классами #Component, #Service, #Repository, #Controller})
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.jdev.blog.admin.crud.repositories", entityManagerFactoryRef = "entityManagerFactory")
public class ApplicationConfiguration {
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setJpaProperties(hibProperties());
return entityManagerFactoryBean;
}
private Properties hibProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
return properties;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
NO the above database configration cannot be moved to a java file BUT can be moved to dispactcher servelet.Actually that whole hibernate.cfg.xml is not required from spring 2.5 version upwards.
You can do the set up of the hibernate properties inside the dispatcher servlet like this. The below code uses mysql dialect so replace it with your requirement also replace name of the database,username and password of the database.
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd">
<context:component-scan base-package="com.c.controllers" />
<context:component-scan base-package="com.c.service" />
<context:component-scan base-package="com.c.dao" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/"your database name"</value>
</property>
<property name="username">
<value>"insert username here"</value>
</property>
<property name="password">
<value>"insert password here"</value>
</property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="myDataSource" />
</property>
<property name="annotatedClasses">
<list>
<value>com.c.beans.Employee</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException

I am quite new to Spring and Spring-Batch in particular.
Still I somehow managed to install the Spring Batch-Admin. I added custom jobs and Hibernate/JPA for persistence.
Everything is working as expected, up to the point where the first chunk should be persisted. Then I receive the following error-message:
org.springframework.transaction.CannotCreateTransactionException:
Could not open JPA EntityManager for transaction;
nested exception is java.lang.IllegalStateException: Already value
[org.springframework.jdbc.datasource.ConnectionHolder#60d31437]
for key [org.springframework.jdbc.datasource.DriverManagerDataSource#12da4b19]
bound to thread [jobLauncherTaskExecutor-1]
This is the full stacktrace:
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource#84f171a] bound to thread [jobLauncherTaskExecutor-1]
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:427)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy41.saveIfUnique(Unknown Source)
at com.qompa.batch.ArticleItemWriter.write(ArticleItemWriter.java:28)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.writeItems(SimpleChunkProcessor.java:171)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.doWrite(SimpleChunkProcessor.java:150)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor$3.doWithRetry(FaultTolerantChunkProcessor.java:313)
at org.springframework.batch.retry.support.RetryTemplate.doExecute(RetryTemplate.java:240)
at org.springframework.batch.retry.support.RetryTemplate.execute(RetryTemplate.java:187)
at org.springframework.batch.core.step.item.BatchRetryTemplate.execute(BatchRetryTemplate.java:213)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor.write(FaultTolerantChunkProcessor.java:402)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:194)
at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:74)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:386)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:264)
at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:76)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:367)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:214)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:143)
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:250)
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:195)
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:135)
at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:61)
at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:60)
at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:144)
at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:124)
at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:135)
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:281)
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:120)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource#84f171a] bound to thread [jobLauncherTaskExecutor-1]
at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:189)
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:402)
... 36 more
The same Job executes fine in a standalone application. The problem occurs only in the Spring-Batch-Admin environment. Below you can see the project structure and dependencies:
This is the app-context.xml that overrides/extends the Batch-Admin 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" xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">
<context:component-scan base-package="com.company.batch" />
<context:property-placeholder location="classpath:batch.properties" />
<import resource="classpath:/META-INF/spring/batch/jobs/article-job.xml" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}" />
<property name="url" value="${batch.jdbc.url}" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.qompa.batch" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="POSTGRESQL"></property>
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="com.company.utils.persistence.CustomPGDialect" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto"></prop>
</props>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<!-- schedule tasks -->
<task:scheduled-tasks>
<task:scheduled ref="articleRetrieval" method="run"
cron="0 0 */4 * * *" />
<task:scheduled ref="articleConversion" method="run"
cron="0 15 */4 * * *" />
</task:scheduled-tasks>
</beans>
What I understand so far is that it has to do with the ThreadPoolTaskExecutor to which the jobLauncherTaskExecutor bean refers. It seems to handle connection pooling for concurrently running jobs ... but to be honest I have no clue how to change my configurations to make these things work.
[Edit]: I am not even sure wether it is the afromentioned ThreadPoolTaskExecutor. But it seem s to be an implementation of the TaskExecutor interface.
If anyone ran into a similar issue, or has a suggestion how to configure my application in a way that transactions can be created for my persistence methods: Please give me a hint!
The error comes from JpaTransactionManager line 403:
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
The error means that the transaction manager is trying to bind the datasource (not the entity manager) to the thread, but the datasource is already there and this is unexpected.
Note that the transaction manager had not started yet to bind the Entity Manager to the thread, that would happen next at JpaTransactionManager line 416:
There are two possible explanations:
Somebody (another transaction manager?) is adding the datasource to the thread before the transaction manager and this is unexpected.
Or no one is adding the datasource to the transaction manager, is just that at the end of the task execution no one cleans the thread before returning it to the pool, maybe due an error or an unhandled exception.
One question, does this also happen for only one execution thread, or only when there are several?
To find out what the problem is, these are some steps:
run with a minimal number of threads that cause the problem
put a breakpoint in TransactionSynchronizationManager.bindResource() to see who adds the connection to the thread. The breakpoint can be a conditional breakpoint with a condition on the thread name: "jobLauncherTaskExecutor-1".equals(Thread.currentThread().getName())
put also a breakpoint in TransactionSynchronizationManager.unbindResource(), to see if the datasource is unbound from the thread. when the breakpoints hit, scroll down the stacktrace and see which classes are causing this.
This normally happens when you have multiple transaction managers in place.
Some hints..
When using annotaion #EnableBatchProcessing, Spring Batch automatically registers a transaction manager , and your JpaTransactionManager may never get used.
If you want to change the transaction manager that spring batch uses, you have to implement the interface BatchConfigurer.(https://blog.codecentric.de/en/2013/06/spring-batch-2-2-javaconfig-part-3-profiles-and-environments/).
You can specify transaction manager for tasklets as follows:
<tasklet transaction-manager="transactionManager">
If you have 2 dataSource, I suggest you to read:
https://github.com/spring-projects/spring-boot/issues/3012
So... configure the main datasource (is important the transaction manager's name)
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManager",
transactionManagerRef = "transactionManager",
basePackages = "a.b.c")
#PropertySource({"classpath:db_persistence.properties"})
#EnableTransactionManagement
and the other datasource:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "another_EntityManager",
transactionManagerRef = "another_transactionManager",
basePackages = "x.y.z")
#PropertySource({"classpath:db_persistence.properties"})
#EnableTransactionManagement
I hope that this help you.
return stepBuilderFactory.get("orderStep1").<sourceBean, destBean>chunk(5)
.reader(reader)
.processor(batchFileRowProcessor)
.writer(batchFileRowDataWritter)
.taskExecutor(taskExecutor)
.transactionManager(platformTransactionManager)
.throttleLimit(1).build();
platformTransactionManager is the qualified bean from the data source configuration
I was able to solve a similar issue by implementing a spring batch configuration for JPA
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
#Configuration
public class JpaBatchConfigurer implements BatchConfigurer {
private static final Logger logger = LoggerFactory
.getLogger(JpaBatchConfigurer.class);
#Inject
private DataSource dataSource;
#Inject
private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;
protected JpaBatchConfigurer() {
}
#Override
#Bean
public JobRepository getJobRepository() {
return jobRepository;
}
#Override
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
#Override
#Bean
public JobLauncher getJobLauncher() {
return jobLauncher;
}
#Override
#Bean
public JobExplorer getJobExplorer() {
return jobExplorer;
}
#PostConstruct
public void initialize() {
try {
if (dataSource == null) {
logger.warn("No datasource was provided...using a Map based JobRepository");
if (this.transactionManager == null) {
this.transactionManager = new ResourcelessTransactionManager();
}
MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(
this.transactionManager);
jobRepositoryFactory.afterPropertiesSet();
this.jobRepository = jobRepositoryFactory.getObject();
MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(
jobRepositoryFactory);
jobExplorerFactory.afterPropertiesSet();
this.jobExplorer = jobExplorerFactory.getObject();
} else {
this.jobRepository = createJobRepository();
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(this.dataSource);
jobExplorerFactoryBean.afterPropertiesSet();
this.jobExplorer = jobExplorerFactoryBean.getObject();
}
this.jobLauncher = createJobLauncher();
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
private JobLauncher createJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.setTaskExecutor( new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setValidateTransactionState(false);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public JobBuilderFactory jobBuilderFactory(JobRepository jobRepository){
return new JobBuilderFactory(jobRepository);
}
#Bean
public StepBuilderFactory stepBuilderFactory(JobRepository jobRepository, PlatformTransactionManager transactionManager){
return new StepBuilderFactory(jobRepository, transactionManager);
}
}
This was copied from:https://github.com/hantsy/spring4-sandbox/blob/master/batch-jpa/src/main/java/com/hantsylabs/example/spring/config/JpaBatchConfigurer.java
These kind of problems occur with older version of java like jdk 6 or further lower versions.Upgrade your jdk version to 7 or above. Even i do had the same kinda issue before which gone when i updated my jdk version to 7.

Categories

Resources