Spring MVC Handle Unique Constraint Violation - java

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.

Related

"BasicBatchConfigurer" has protected access - Spring Batch/Spring Boot not persisting data to database

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.

Why is setting TransactionManager as JPATransactionManager in a Step not correct?

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.

Spring - conflicts with existing, non-compatible bean definition of same name and class

The specific exception is:
Failed to instantiate [org.springframework.context.annotation.AnnotationConfigApplicationContext]: Constructor threw exception;
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'myService' for bean class [my.package.ejb.MyService] conflicts with existing, non-compatible bean definition of same name and class [my.package.other.ejb.MyService]
Those MyService interfaces are not even annotated, they represent EJB 2.0 stateless beans.
My annotation configuration is as follow.
#Configuration
#ComponentScan("my.package")
#MapperScan("my.package")
public class ApplicationConfiguration {
#Bean
public DataSource dataSource() {
return new JndiDataSourceLookup().getDataSource("...");
}
#Bean
public SqlSessionFactoryBean sqlSessionFactory(final DataSource dataSource) {
final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setConfigLocation(new ClassPathResource("..."));
return sqlSessionFactory;
}
#Bean
public DataSourceTransactionManager dataSourceTransactionManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Might be an incompatibility with #MapperScan (from MyBatis) and #ComponentScan?
The exception come from the SpringBeanAutowiringInterceptor I'm using to Autowire EJB 3.0 fields.
The documentation for MapperScannerConfigurer says that:
This class supports filtering the mappers created by either specifying
a marker interface or an annotation. The annotationClass property
specifies an annotation to search for. The markerInterface property
specifies a parent interface to search for. If both properties are
specified, mappers are added for interfaces that match either
criteria. By default, these two properties are null, so all interfaces
in the given basePackage are added as mappers.
Basically I was mapping thousands of interfaces as beans. Not cool!
Guys, my fault.

Build EntityManagerFactory using Hibernate and JPA

I'm building a web application using SpringFramework. In the data access layer I was using Hibernate to query data in MySQL, which was working fine. My SessionFactory is build from this method:
public SessionFactory sessionFactory() throws HibernateException {
return new org.hibernate.cfg.Configuration()
.configure()
.buildSessionFactory();
}
Today I'm going to integrate my data access with JPA, which needs a EntityManagerFactory, IMO I only need to change the code above into the following:
#Bean
public EntityManagerFactory entityManagerFactory() throws HibernateException {
return new org.hibernate.cfg.Configuration()
.configure()
.buildSessionFactory();
}
simply because SessionFactory extends EntityManagerFactory. However I got an exception
java.lang.ClassCastException:
org.hibernate.internal.SessionFactoryImpl cannot be cast to
javax.persistence.EntityManagerFactory
This is quite weird, because SessionFactoryImpl implements SessionFactory while SessionFactory extends EntityManagerFactory. I don't know why the cast fails.
My question is: 1. why the cast is invalid? 2. what's the correct way to build a EntityManagerFactory using Hibernate?
EDIT Debugger says
factory instanceof SessionFactory //true
factory instanceof EntityManagerFactory //false
and the source of SessionFactory
public interface SessionFactory extends EntityManagerFactory, HibernateEntityManagerFactory, Referenceable, Serializable, Closeable
I'm sure all the above EntityManagerFactory refers to javax.persistence.EntityManagerFactory.
Hmmm, unless you're using Hibernate 5.2 Hibernate's org.hibernate.SessionFactory does not extend javax.persistence.EntityManagerFactory, hibernate-entitymanager was only recently merged into hibernate-core. Seems to me that you're browsing source code from a version newer than the one you're using in your project.
In either case, to see the correct way of using Hibernate with JPA, refer to this link.
You can reuse the Session factory by following the steps here, setting up the SessionFactory bean in Spring.
But since you've already coded in terms of the EntityManagerFactory, you should follow the easier route described in this tutorial It's in German, unfortunately, but the code should speak for itself. You declare Hibernate as your JPA vendor, then the EntityManagerFactory with the vendor and the datasource, which can be defined explicitly or be a JNDI datasource, defined in your container and requested like this:
<jee:jndi-lookup id="dataSource" jndi-name="myDataSource"/>
And then it's very nice to declare a Spring transactionManager to allow the #Transactional annotation so you won't have to deal with explicit transaction management.
Then you can let your UserDAO look like this:
public class UserDAO {
#PersistenceContext
private EntityManager em;
#Transactional
public List<User> findUserByName(String name) {
return em.createNamedQuery("User.findByName")
.setParameter("name", name)
.getResultList();
}
}
where User is something like
#Entity
#NamedQueries({ #NamedQuery( name="User.findByName", query="select u from User where u.name = :name") })
public class User {
#Id
private Long id;
#Column
private String name;
...
}
There's many ways to do this, though.

#EnableTransactionManagement annotation with 2 transaction managers

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.

Categories