Oauth2 Spring-boot read-only connection error - java

I am continuously getting below error , I did enable transaction with #EnableTransactionManagement, but still somehow transaction is not invoked in DefaultTokenServices.
Any help will be much appreciated
Note: it was working with spring-boot 1.5 and recently I upgraded to 2.1
2020-11-19 18:27:12.385 ERROR 49065 [tomcat-exec-2] - o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: TransientDataAccessResourceException, PreparedStatementCallback; SQL [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:110)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)

solution
I am able to fix with hack by manually attaching transaction to oauth jdbctokenservice.
private static final String AOP_POINTCUT_EXPRESSION = "execution (* org.springframework.security.oauth2.provider.token.store.JdbcTokenStore.*(..))";
#Autowired
public void txAdvice(TransactionInterceptor txAdvice) throws NoSuchMethodException {
DefaultTransactionAttribute required = new DefaultTransactionAttribute();
MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
final Method method = JdbcTokenStore.class.getMethod("storeAccessToken", OAuth2AccessToken.class, OAuth2Authentication.class);
source.addTransactionalMethod(method, required);
txAdvice.setTransactionAttributeSource(source);
}
#Bean
public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
I also created issue with spring-security-oauth but seems it is not supposed to support spring-boot 2.x.
Any Brilliant mind wanna help on why Transaction was not invoked in DefaultTokenServices.
Solution
Create Bean for DefaultTokenServices and pass it to configurer
#Autowired
private DefaultTokenServices tokenServices;
#Bean
#Primary
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore)
.authenticationManager(auth)
.addInterceptor(handlerInterceptor)
.tokenServices(tokenServices)
.approvalStoreDisabled();
}
link: https://github.com/spring-projects/spring-security-oauth/issues/1900

Related

Spring Boot oauth2 social login: Cannot login after session timeout

I use social login in a spring boot container. This works fine, but when I keep the browser open over night, the next morning I cannot login any more. I need to restart the container and refresh the website, after this it works again.
I hope to get this work by storing the authentication token also in the database by adding JdbcOAuth2AuthorizedClientService. Now, and this is the actual problem, the token is NOT PERSITED although I get these lines in the log:
o.s.jdbc.core.JdbcTemplate : Executing prepared SQL query
o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [SELECT client_registration_id, principal_name, access_token_type, access_token_value, access_token_issued_at, access_token_expires_at, access_token_scopes, refresh_token_value, refresh_token_issued_at FROM oauth2_authorized_client WHERE client_registration_id = ? AND principal_name = ?]
o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [google], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 2, parameter value [XXXXXX], value class [java.lang.String], SQL type 12
o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update
o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [INSERT INTO oauth2_authorized_client (client_registration_id, principal_name, access_token_type, access_token_value, access_token_issued_at, access_token_expires_at, access_token_scopes, refresh_token_value, refresh_token_issued_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)]
o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [google], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 2, parameter value [XXXXXX], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 3, parameter value [Bearer], value class [java.lang.String], SQL type 12
o.s.jdbc.support.lob.DefaultLobHandler : Set bytes for BLOB with length 165
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 5, parameter value [2022-04-25 09:56:57.137607903], value class [java.sql.Timestamp], SQL type 93
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 6, parameter value [2022-04-25 10:56:56.137607903], value class [java.sql.Timestamp], SQL type 93
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 7, parameter value [https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email,openid], value class [java.lang.String], SQL type 12
o.s.jdbc.support.lob.DefaultLobHandler : Set BLOB to null
o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 9, parameter value [null], value class [null], SQL type 93
o.s.jdbc.core.JdbcTemplate : SQL update affected 1 rows
The table keeps empty. I tried with H2 and also PostgreSQL but no difference. The application itself is able to write records to tables using Spring data. No problem at all.
Has anyone an idea? I appriciate for every hint :-)
Thx,
SteveP
This is my security configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired final MyAppMemberService memberService;
#Autowired private MyAppProperties properties;
#Resource private ClientRegistrationRepository repo;
#Resource private DataSource dataSource;
#Override
protected void configure(final HttpSecurity http) throws Exception {
final RequestMatcher[] unprotectedGuiApi = new RequestMatcher[] {
new AntPathRequestMatcher("/api/v*/gui/currentUser"),
new AntPathRequestMatcher("/api/v*/gui/oauth2-clients"),
};
final RequestMatcher[] administrationApi = new RequestMatcher[] {
new AntPathRequestMatcher("/api/v*/administration/**")
};
final RequestMatcher[] guiApi = new RequestMatcher[] {
new AntPathRequestMatcher("/api/v*/**"),
new AntPathRequestMatcher("/logout"),
new AntPathRequestMatcher("/**/oauth2/**"),
};
http
.requestMatchers()
.requestMatchers(guiApi)
.and()
.csrf()
.disable()
.cors()
.configurationSource(httpRequest -> properties.getCors())
.and()
.headers()
.contentTypeOptions().disable()
.frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("**/websocket/**").permitAll()
.requestMatchers(unprotectedGuiApi).permitAll()
.requestMatchers(administrationApi).hasRole(Role.ADMIN.name())
.anyRequest().authenticated()
.and()
.logout()
.permitAll()
.logoutSuccessUrl(properties.getGatewayUrl()) // SPA endpoint
.and()
.oauth2Login()
.loginPage(properties.getGatewayUrl() + "/login")
.authorizedClientService(oAuth2AuthorizedClientService())
.userInfoEndpoint()
.userService(oauth2UserService())
.and()
.successHandler(authenticationSuccessHandler());
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
// just registers new users on successful auth
return new MyAppAuthenticationSuccessHandler(properties, memberService);
}
#Bean
public OAuth2UserService oauth2UserService() {
// load member and roles from database
return new MyAppOAuth2UserService(memberService);
}
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService() {
return new JdbcOAuth2AuthorizedClientService(new JdbcTemplate(dataSource), repo);
}
}
This post brought me the idea that it might not be committed into DB:
When sharing a Jdbc connection pool between spring-data-jpa and spring-security-oauth's JdbcTokenStore, how to handle transactions?
Using this logging technique I could see the INSERT-statement was sent to DB
https://vladmihalcea.com/the-best-way-to-log-jdbc-statements/
but no final log statement
o.s.jdbc.core.JdbcTemplate : SQL update affected 1 rows
I adopted my WebSecurityConfiguration like this:
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService() {
return new TransactionalJdbcOAuth2AuthorizedClientService(jdbcTemplate, repo);
}
and added:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class TransactionalJdbcOAuth2AuthorizedClientService extends JdbcOAuth2AuthorizedClientService {
public TransactionalJdbcOAuth2AuthorizedClientService(final JdbcOperations jdbcOperations, final ClientRegistrationRepository clientRegistrationRepository) {
super(jdbcOperations, clientRegistrationRepository);
}
#Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(final String clientRegistrationId, final String principalName) {
return super.loadAuthorizedClient(clientRegistrationId, principalName);
}
#Override
public void saveAuthorizedClient(final OAuth2AuthorizedClient authorizedClient, final Authentication principal) {
super.saveAuthorizedClient(authorizedClient, principal);
}
#Override
public void removeAuthorizedClient( final String clientRegistrationId, final String principalName) {
super.removeAuthorizedClient(clientRegistrationId, principalName);
}
}
Now it is persisted! I asked myself: Am I the first person using Spring's JdbcOAuth2AuthorizedClientService? No hint in the JavaDoc or in any Spring documentation. Anyway, hopefully this hint will help someone else.

Spring Boot - manual JPA configuration with profiles problem

I've got a problem with my Spring Boot app JPA configuration. I've got two profiles - dev (H2 db) and prod (PostgreSQL). I want to manually set up JPA without Spring Boot "magic", so I've created configuration class shown below
#Configuration
#EnableTransactionManagement
public class PersistenceContext {
#Primary
#Bean
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource dataSource(DataSourceProperties properties) {
return properties
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource(dataSourceProperties()));
em.setPackagesToScan("model");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return em;
}
#Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
The problem occurs when I want to test saving to database in the dev profile. When I'm performing test I've got this error :
10:37:47.951 [main] DEBUG org.hibernate.SQL - insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?)
10:37:47.955 [main] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper - could not prepare statement [insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?)]
org.h2.jdbc.JdbcSQLException: Table "BOOK" not found; SQL statement:
insert into Book (id, author, bookType, bookstore, new_price, old_price, title, url) values (null, ?, ?, ?, ?, ?, ?, ?) [42102-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
My application-dev.properties file looks like this
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.platform=h2
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.generate-ddl=true
spring.data.jpa.repositories.enabled=true
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
I figured out that it might be a problem with spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop, cause when I set this property via property map inside PersistenceContext class it's working properly. I don't know how to set it correctly via a properties file. Thank you in advance for your help.
spring.jpa.hibernate.ddl-auto=update and spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop does the same thing. But since you are opted not using Spring Autoconfiguration Magic these properties are ineffective.
So you have to use JPA Property Map and set it in LocalContainerEntityManagerFactoryBean.

Spring batch: storedProcedureItemReader Invalid parameter index 0

I trying to use spring batch with storedProcedureItemReader
and always i get this exception
and i cant found the error i use Sybase at BBDD
org.springframework.batch.item.ItemStreamException: Failed to initialize the reader
Caused by: org.springframework.jdbc.BadSqlGrammarException: Executing stored procedure; bad SQL grammar [{call PROCEDURE(?)}]; nested exception is java.sql.SQLException: Invalid parameter index 0.
Caused by: java.sql.SQLException: Invalid parameter index 0.
this is java spring code
#Bean
protected ItemReader<AuditKpi> auditKpiRead(DataSource dataSource) throws Exception{
StoredProcedureItemReader<AuditKpi> storedProcedureItemReader = new StoredProcedureItemReader<AuditKpi>();
SqlParameter[] parameter = {new SqlParameter("#INDATE", Types.SQL_DATETIME)};
PreparedStatementSetter stament = new PreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setDate(1, new java.sql.Date(new Date().getTime()));
}
};
storedProcedureItemReader.setDataSource(dataSource);
storedProcedureItemReader.setProcedureName("PROCEDURE");
storedProcedureItemReader.setRowMapper(new BeanPropertyRowMapper<AuditKpi>(AuditKpi.class));
storedProcedureItemReader.setParameters(parameter);
storedProcedureItemReader.setPreparedStatementSetter(stament);
storedProcedureItemReader.afterPropertiesSet();
storedProcedureItemReader.setVerifyCursorPosition(false);
System.out.println(storedProcedureItemReader.getSql());
return storedProcedureItemReader;
}
And this is my SQL code
CREATE PROCEDURE PROCEDURE (IN #INDATE DATE)
AS
...
GO
Thranks for your help

Spring: JDBC Template Invalid column type

Using the following code to get the list of data from table but getting invalid column error.
String sql = "select * from employees WHERE emp_status = :statusCode";
Map parameters = new HashMap();
parameters.put("statusCode", "Active");
MapSqlParameterSource parametersSourceMap = new MapSqlParameterSource(parameters );
List<Employee> employees rowSet = jdbcTemplate.queryForList(sql, parametersSourceMap);
Exception
Exception in thread "main" org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [select * from employees WHERE emp_status = :statusCode SQL state [null]; error code [17004]; Invalid column type; nested exception is java.sql.SQLException: Invalid column type
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:83)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:603)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:812)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:868)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:876)
at com.spring.EmployeeDAOImpl.addEmployee(EmployeeDAOImpl.java:46)
at com.spring.MainApp.main(MainApp.java:33)
Caused by: java.sql.SQLException: Invalid column type
Table have column as Varchar2
Anyone have idea why we can't get data based on String column?
I recently faced the same issue. In my case, I was using JdbcTemplate instead of NamedParameterJdbcTemplate. I am not sure if the issue is same for you.
You can define a bean of type NamedParameterJdbcTemplate and then autowire it in your repository class.
Configuration class :
#Bean
public NamedParameterJdbcTemplate yourNamedParameterJdbcTemplate(DataSource yourDataSource) {
return new NamedParameterJdbcTemplate(yourDataSource);
}
Repository class :
#Repository
public class YourRepositoryImpl implements YourRepository {
#Autowired
NamedParameterJdbcTemplate jdbcTemplate;
Try to pass parameters like this :
String sql = "SELECT * FROM employees WHERE emp_status = ?";
List<Employee> employees = jdbcTemplate.queryForList(
sql,
new Object[]{"Active"},
new BeanPropertyRowMapper<Employee>(Employee.class)
);

ExceptionHandling in Spring

I am using spring with hibernate and when I am trying to insert a data which already exists, it throws DataIntegrityViolationException.
So to handle this exception I have placed a try/catch block in my save method in DAO layer, but it's not getting caught.
After that I placed a try/catch block in save method of service layer also but there also its not getting caught.
I have a testMain method in my service layer and when I am handling there its getting caught there.
Please help how to handle this in my DAO layer
public class TestMain {
/**
* #param args
*/
public static void main(String[] args) {
ApplicationContext applicationContext = SpringUtil
.getApplicationContextInstance();
UserDaoService userDaoService = applicationContext.getBean("userDaoService",
UserDaoService.class);
UserDaoImpl userDao = applicationContext.getBean("userDaoImpl",
UserDaoImpl.class);
User user = new User();
user.setActive(true);
user.setEmail("shariquealam01#gmail.com");
user.setFirstName("Md");
user.setMiddleName("Sharique");
user.setLastName("Alam");
user.setId(1);
user.setPassword("123");
user.setUserName("shariquealam01");
userDaoService.saveUser(user);
}
}
#Service
public class UserDaoService {
#Autowired
public UserDao userDao;
#Transactional
public void saveUser(User user){
System.out.println("User Saving");
/*user.setActive(true);
user.setEmail("shariquealam06#gmail.com");
user.setFirstName("Md");
user.setMiddleName("Sharique");
user.setLastName("Alam");
user.setId(6);
user.setPassword("123");
user.setUserName("shariquealam06");*/
userDao.saveUser(user) ;
/*try{
System.out.println("Inside Service Try");
userDao.saveUser(user);
} catch (Exception e){
System.out.println("Exception Occured "+e);
}*/
// deleteUser(3);
}
}
#Repository
public class UserDaoImpl implements UserDao {
#Autowired
private SessionFactory sessionFactory;
public Role getRole(int id){
Session session = getSessionFactory().getCurrentSession();
Role role = (Role) session.get(Role.class, id);
return role;
}
#Override
public void saveUser(User user) {
// Session session = getSessionFactory().getCurrentSession();
/*role.setRoleId(102);
role.setRoleName("User");*/
// role.getUsers().add(user);
/*session.save(user);*/
//logger.info( "Executing Query to ADD User"); //Since this query is important for the state of application, have info logging
Role role = getRole(102);
user.getRoles().add(role);
Session session = sessionFactory.getCurrentSession();
session.save( user );
//userId = user.getId();
//logger.info( "User ADDED to DB with userId as {}", user.getId() );
//session.save(role);
}
}
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:163)
at org.springframework.orm.hibernate4.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:730)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:592)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:515)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.sharique.service.UserDaoService$$EnhancerBySpringCGLIB$$956522ab.saveUser(<generated>)
at com.sharique.main.TestMain.main(TestMain.java:40)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:129)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:211)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:62)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3581)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:104)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:584)
... 9 more
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK__USER_DET__F3BEEBFF77DFC722'. Cannot insert duplicate key in object 'dbo.USER_DETAILS'.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:216)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1515)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:404)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:350)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:5696)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:1715)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:180)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:155)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:314)
at org.apache.commons.dbcp2.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:98)
at org.apache.commons.dbcp2.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:98)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
... 22 more
The exception, as the stack trace should show, doesn't happen when you call persist() or save(). It happens when flush() is called, i.e. when the SQL insert statements are actually executed. And flush() is called automatically just before the transaction commits.
You could call flush() explicitely and catch the exception, but that would be useless, because Hibernate exceptions are irrecoverable. They leave the session in an unusable state. The only safe thing to do when facing such an exception is to rolback the transaction and close the session.
So do the right thing: use autogenerated primary keys and/or check that the data doesn't exist, using a query, before trying to insert it.

Categories