I am using #Configuration annotation for configuration of spring instead of xml file. I am configuring 2 datasources with different session factory and different transaction managers. I am stuck with a problem here for #EnableTransactionManagement annotation. I read in its documentation that,
#EnableTransactionManagement is more flexible; it will fall back to a
by-type lookup for any PlatformTransactionManager bean in the
container. Thus the name can be "txManager", "transactionManager", or
"tm": it simply does not matter.
This means whatever name I give to method, it will always search for the method which returns PlatformTransactionManager object while I have 2 transactionmanagers. Now the problem is, when I test this class, it gives me error:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single bean but found 2
I even tried to have 2 different Configuration classes but in vain. In xml configuration, this was not the case. I registered my both transaction managers with two <tx:annotation-driven transaction-manager="" /> tag and it worked fine. But not able to do same here with annotations.
What should I do if I want to configure 2 datasources with 2 different transaction managers in Spring annotated configuration class?
In your configuration class, use #EnableTransactionManagement annotation.
Define a transaction manager in this class as:
#Bean(name="txName")
public HibernateTransactionManager txName() throws IOException{
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(...);
txName.setDataSource(...);
return txName;
}
There on, in your class/method that executes transactional job(s), annotate as follows:
#Transactional("txName")
or
#Transactional(value = "txName")
This is how you would tie a name qualified transaction manager to wherever you need it. You can now have as many transaction managers as you want and use it accordingly wherever you need.
Just in case anyone runs into this problem, I found a solution:
#Configuration
#EnableTransactionManagement
#DependsOn("myTxManager")
#ImportResource("classpath:applicationContext.xml")
public class AppConfig implements TransactionManagementConfigurer {
#Autowired
private PlatformTransactionManager myTxManager;
...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return this.myTxManager;
}
In this way, you can use a specific txManager defined in an xml configuration.
In case you want to define the txManager used on service-level, you shall remove the #EnableTransactionManagement annotation from the #Configuration class and specify the txManager in the #Transactional annotations, e.g.
#Service
#Transactional(value="myTxManager", readOnly = true)
public class MyServiceImpl implements MyService { ... }
From the java doc
For those that wish to establish a more direct relationship between
#EnableTransactionManagement and the exact transaction manager bean to be used, the
TransactionManagementConfigurer callback interface may be implemented - notice the
implements clause and the #Override-annotated method below:
Your #Configuration class needs to implement TransactionManagementConfigurer interface - implement the annotationDrivenTransactionManager which will return the reference to the transactionManager that should be used.
I am not sure why you are using two TransactionManagers . You could consider using the same TransactionManager for multiple datasource via the AbstractRoutingDataSource . Please refer
http://blog.springsource.org/2007/01/23/dynamic-datasource-routing/
for a sample on its usage.
I have to use JPA and Reactive Mongo in one project. What works at last was:
create a #Configuraition class to explicitly create a JPA transaction manager, like here:
private Environment env;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean dbEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dbDatasource());
em.setPackagesToScan(new String[]{"projectone.mysql"});
em.setPersistenceUnitName("dbEntityManager");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect",env.getProperty("hibernate.dialect"));
properties.put("hibernate.show-sql",env.getProperty("jdbc.show-sql"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public DataSource dbDatasource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Primary
#Bean
public PlatformTransactionManager jpaTransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
dbEntityManager().getObject());
return transactionManager;
}
}
Notice that the bean name jpaTransactionManager, which would be the txManager name used in JPA #Transactional.
create MongoConfiguration to explicitly create a Mongo transaction manager(a lot of beans to define)
in #Transactional, call them with name. The default one transactionManger will not work. You have to distinguish, like jpaTransactionManager and reactiveMongoTransactionManger.
#Transactional(value="jpaTransactionManager")
public void xxx() {
...
}
Note that JPA transaction methods cannot Reactor types as return value(Mono/Flux). Spring will force methods returning Mono/Flux to use ReactiveTransactionManager, it will cause confusion.
Some of the other answers imply that using two transaction managers is in some way wrong; however, Spring's XML configuration allows for using multiple transaction managers as stated in the online documentation (below). Unfortunately, there does not seem to be a way to make the #EnableTransactionManagement annotation work in a similar manner. As a result, I simply use an #ImportResource annotation to load an XML file that includes the <tx:annotation-driven/> line. This allows you to get a Java configuration for most things but still make use of #Transactional with an optional Transaction Manager qualifier.
http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/transaction.html
Most Spring applications only need a single transaction manager, but there may be situations where you want multiple independent transaction managers in a single application. The value attribute of the #Transactional annotation can be used to optionally specify the identity of the PlatformTransactionManager to be used. This can either be the bean name or the qualifier value of the transaction manager bean. For example, using the qualifier notation, the following Java code
Try to use chained TransactionalManager
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
#Configuration
public class ChainedDBConfig {
#Bean("chainedTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("database1TransactionManager") final PlatformTransactionManager db1PlatformTransactionManager,
#Qualifier("database2TransactionManager") final PlatformTransactionManager db2PlatformTransactionManager) {
return new ChainedTransactionManager(db1PlatformTransactionManager, db2PlatformTransactionManager);
}
}
And place the following annotation on your service class:
#Transactional(transactionManager = "chainedTransactionManager")
public class AggregateMessagesJobIntegrationTest {
...
}
You can also use it inside the integration tests:
#RunWith(SpringRunner.class)
#Transactional(transactionManager = "chainedRawAndAggregatedTransactionManager")
#Rollback
public class ExampleIntegrationTest extends AbstractIntegrationTest {
....
}
and it will do a rollback for both DB transaction managers.
Related
I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.
I am working with Spring Batch and JPA and I experienced the TransactionManager bean conflict. I found a solution by setting the TransactionManager as JpaTransactionManager in a step. But according to this link (https://github.com/spring-projects/spring-batch/issues/961), it is not correct even though it works for me.
#Autowired
private JpaTransactionManager transactionManager;
private Step buildTaskletStep() {
return stepBuilderFactory.get("SendCampaignStep")
.<UserAccount, UserAccount>chunk(pushServiceConfiguration.getCampaignBatchSize())
.reader(userAccountItemReader)
.processor(userAccountItemProcessor)
.writer(userAccountItemWriter)
.transactionManager(transactionManager)
.build();
}
}
I tried the suggested solution of implementing the BatchConfigurer but it conflicts with me disabling the metadata tables using this code:
#Configuration
#EnableAutoConfiguration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
#Override
public void setDataSource(DataSource dataSource) {
// override to do not set datasource even if a datasource exist.
// initialize will use a Map based JobRepository (instead of database)
}
}
What would be the problem using the first solution of setting the TransactionManager in a Step?
In Spring Batch, there are two places where a transaction manager is used:
In the proxy created around the JobRepository to create transactional methods when interacting with the job repository
In each step definition to drive the step's transaction
Typically, the same transaction manager is used in both places, but this is not a requirement. It is perfectly fine to use a ResourcelessTransactionManager with the job repository to not store any meta-data and a JpaTransactionManager in the step to persist data in a database.
By default, when you use #EnableBatchProcessing and you provide a DataSource bean, Spring Batch will create a DataSourceTransactionManager and set it in both places, because this is the most typical case. But nothing prevents you from using a different transaction manager for the step. In this case, you should accept that business data and technical meta-data can get out of sync when things go wrong.
That's why the expected way to provide a custom transaction manager is via a custom BatchConfigurer#getTransactionManager, in which case your custom transaction manager is set it in both places. This was not clearly documented, but it has been fixed since v4.1. Here is the section that mentions that: Configuring a JobRepository. This is also mentioned in the Javadoc of #EnableBatchProcessing:
In order to use a custom transaction manager, a custom BatchConfigurer should be provided.
I have two custom PlatformTransactionManager beans injected into the Spring framework with specific names as follows:
#Bean(name = "ubldbTransactionManager")
protected PlatformTransactionManager transactionManager(
#Qualifier("ubldbEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(name = "bpdbTransactionManager")
public PlatformTransactionManager bpdbTransactionManager(
#Qualifier("bpdbEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
A 3rd-party library has a #Autowired protected PlatformTransactionManager transactionManager; dependency. So, the 3rd party library is not supposed to use none of the two TransactionManagers. However, as you see there is no Qualifier for the dependency injection in the external library and I get an error as follows:
Field transactionManager in org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultDatasourceConfiguration required a single bean, but 2 were found:
- bpdbTransactionManager: defined by method 'bpdbTransactionManager' in class path resource [eu/nimble/service/bp/config/BusinessProcessDBConfig.class]
- ubldbTransactionManager: defined by method 'transactionManager' in class path resource [eu/nimble/service/bp/config/UBLDBConfig.class]
So, how can I restrict the visibility of the two Beans so that they would not be accessible by the 3rd-party library?
DefaultDatasourceConfiguration is provided to use default Spring beans e.g. DataSource named dataSource and PlatformTransactionManager named transcationManager. It's there to glue Camunda into a Spring Boot application which be default has a single data source.
Since you have created your own PlatformTransactionManager beans this disabled Spring Boot's default transaction manager bean named transcationManager (as per TransactionAutoConfiguration Spring Boot auto-configuration logic).
You most likely need to define one more transactionManager (and potentially dataSource) for Camunda's process engine, which requires it's own schema. Make sure to use the right bean name as below:
#Bean
public PlatformTransactionManager transactionManager() {
...
}
Starting from Spring 4 the bean name is the default qualifier when auto-wiring so the new transaction manager will be wired into DefaultDatasourceConfiguration as it matches the field name in the class.
Alternatively don't use DefaultDatasourceConfiguration and roll out your own configuration if Spring Boot defaults are not working for you.
Use #Qualifier annotation
The #Qualifier annotation is used to resolve the autowiring conflict, when there are multiple beans of same type.
#Bean
#Qualifier("ubldbTransactionManager")
protected PlatformTransactionManager transactionManager
and
#Bean
#Qualifier("bpdbTransactionManager")
public PlatformTransactionManager bpdbTransactionManager
I've created a pretty standard Spring Boot 2.0 app with Services and Repositories to access a database.
I had to set the standard Spring Boot application properties to get it to work, such as:
spring.datasource.url, spring.jpa.database, etc.
However, in order to prevent my properties from overwriting other properties in similar apps hosted in the same place, I need to rename these properties, such as:
myApp.spring.datasource.url, myApp.spring.jpa.database, etc.
Some of these properties will be set by environmental variables instead of the application.properties file.
However, I can't see any way to override those variables in my app.
The standard approach is to use #Value to configure those variables. However, the Spring Boot 2.0 setup for services looks up all these properties "behind the scenes," so that doesn't appear to be an option here.
Is there any way to configure my app to read all those myApp.common.property.name properties and treat them as common.property.name?
Thank you.
Yes, the standard way is to use #Value. But thw work doesn't stop there, you need to create DataSource and EntityManager with these values.
Springboot will create DataSource, Entitymanager and some other components bey looking into default properties(spring.xxx) from the file(hence Spring boot is opinionated). But when you change these names to non default values, then you need to create these components / beans yourself.
Instead of using #Value you could also use #Configurationproperties. #Value also works but you might need to declare like 6 or 7 values with #Value. If you wish to use #ConfigurationProperties, make sure you have #EnableConfigurationProperties annotation added somewhere in your project.
Here is a code snippet. You need to tune it to your project
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.xxx.yyy.repo" }
)
public class SomeDbConfig {
#Primary // Use this if you have multiple datasources or else no use
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "myApp.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.xxx.yyy.domain")
.persistenceUnit("somename")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
I have a Spring MVC application that uses Spring Data JPA for persistence with Hibernate as my JPA provider. I have a database table with a unique constraint on a column, hence why saving the corresponding entity may result in a unique constraint violation. I would like to detect if this happens within my service layer so that I can present a meaningful error message to the user. Below is my service method.
#Service
public class IndustryServiceImpl implements IndustryService {
#Autowired
private IndustryRepository industryRepository;
#Override
#Transactional
public void add(Collection<Industry> industries) {
this.industryRepository.save(industries);
this.industryRepository.flush();
}
}
And my Spring Data JPA repository looks like this:
#Repository
public interface IndustryRepository extends JpaRepository<Industry, Integer> { }
Now when a unique constraint violation happens, I get the following exception (shortened for brevity):
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
...
org.hibernate.exception.ConstraintViolationException: could not execute statement
...
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "industry_name_unique_index"
Detail: Key (name)=(SomeValue) already exists.
...
As you can see, the "main" exception is of type javax.persistence.PersistenceException, which is a quite general exception. Ideally I would like to catch an exception that tells me that the error was due to a unique constraint violation. Therefore I have a few questions:
Is it correct that the exception should be of type javax.persistence.PersistenceException, or is that because the Spring exception translation is not kicking in? And can I configure Spring to give me less general exceptions, for example DuplicateKeyException?
If this behavior is not related to missing exception translation, then do you know of a better way of detecting the more specific error than calling e.getCause()? I could easily do this, but this does not seem right to me for two reasons; first of all, the code would seem more "fragile" to me, and secondly, I would also have to check for a JPA provider specific exception, as the nested exception is of type org.hibernate.exception.ConstraintViolationException. If possible, I would like my service to not be aware of which JPA provider I am using.
Below is my persistence configuration in case it is relevant.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = { "com.example.company.repository", "com.example.account.repository" })
public class PersistenceConfig {
#Autowired
private LocalValidatorFactoryBean validator;
#Value("${jndi.data.source}")
private String dataSourceName;
#Bean
public DataSource dataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
return dsLookup.getDataSource(this.dataSourceName);
}
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
String[] packagesToScan = new String[] { "com.example.company.entity", "com.example.account.entity" };
factory.setPackagesToScan(packagesToScan);
factory.setDataSource(this.dataSource());
factory.setValidationMode(ValidationMode.NONE); // Prevents errors when using custom validators when persisting
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(this.entityManagerFactory());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
Thank you in advance!
Spring enables exception translation to be applied transparently through the #Repository annotation. The postprocessor automatically looks for all exception translators (implementations of the PersistenceExceptionTranslator interface) and advises all beans marked with the #Repository annotation so that the discovered translators can intercept and apply the appropriate translation on the thrown exceptions.
In summary: you can implement DAOs based on the plain persistence technology’s API and annotations, while still benefiting from Spring-managed transactions, dependency injection, and transparent exception conversion (if desired) to Spring’s custom exception hierarchies.
`
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
`
Taken directly from here.
Also, you can take a look at this.
HTH.