0

Is there any way to catch org.hibernate.exception.ConstraintViolationException in Liberty application server ?

I have a this repository code :

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class MarketRepository {

    @PersistenceContext
    private EntityManager em;

    @Resource
    private UserTransaction tx;

    public void insert(Market market) {
        try {
            tx.begin();
            em.persist(market);
            tx.commit();
        } catch (Exception ignore) {
        }
    }
}     

I always receive this exception :

[INFO] Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
[INFO]  at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:109)
[INFO]  at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
[INFO]  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
[INFO]  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
[INFO]  at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
[INFO]  at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3238)
[INFO]  at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3763)
[INFO]  at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
[INFO]  at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
[INFO]  at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
[INFO]  at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:723)
[INFO]  at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
[INFO]  at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
[INFO]  at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
[INFO]  at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
[INFO]  at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1360)
[INFO]  ... 20 more
[INFO] Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "ukmn9r4yewa9epwp1ekfsbm69gu"
[INFO]   Detail: Key (exchange, symbol)=(COINEX, ZECUSDT) already exists.
[INFO]  at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2533)
[INFO]  at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2268)
[INFO]  at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:313)
[INFO]  at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:448)
[INFO]  at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:369)
[INFO]  at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:159)
[INFO]  at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:125)
[INFO]  at jdk.internal.reflect.GeneratedMethodAccessor1090.invoke(Unknown Source)
[INFO]  at java.base/java.lang.reflect.Method.invoke(Method.java:567)
[INFO]  at org.postgresql.ds.PGPooledConnection$StatementHandler.invoke(PGPooledConnection.java:428)
[INFO]  at jdk.proxy11/jdk.proxy11.$Proxy136.executeUpdate(Unknown Source)
[INFO]  at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.executeUpdate(WSJdbcPreparedStatement.java:520)
[INFO]  at [internal classes]
[INFO]  at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
[INFO]  ... 31 more

this is my server.xml config :

 <dataSource jndiName="jdbc/jta-datasource" transactional="true">
        <jdbcDriver id="database-driver" libraryRef="project-libs"/>
        <properties databaseName="${database.name}" serverName="${database.hostname}" portNumber="${database.port}"
                    user="${database.username}" password="${database.password}"/>
    </dataSource>    

so, I want to ignore this exception, but still receive that !

How can fix this problem ?

mah454
  • 1,571
  • 15
  • 38
  • Possible duplication of https://stackoverflow.com/questions/2893969/hibernate-constraint-constraintviolationexception-is-there-an-easy-way-to-ignor – Will Dazey Sep 27 '21 at 15:01
  • I get the impression that JPA's `em` is running in a *different transaction* than the one initiated manually by your code. I don't have enough data to prove it. How is `MarketRepository.insert()` getting called? What is your persistence.xml? – Nikos Paraskevopoulos Sep 28 '21 at 06:22

2 Answers2

1

You can't. That exception, when using plain JDBC, can be catched through a SQLIntegrityConstraintViolationException, but when using JPA you can't because it should not be part of your business logic (the same SQLIntegrityConstraintViolationException will be wrapped many times in different JPA Data Exceptions). That kind of exception is not something you want to catch, but prevent. It generally indicates a missconception in your business logic. My recomendations is to analyze why is it happening, and refactor your logic to avoid it, it will most probably result in a more robust code.

If you want to explain further your business case I can give you an specific recommendation. But just to provide something:

You can check with a query if the data with the constraint exists. If it doesn't it will result in a NoResultException (that is an Exception that can be part of your business logic. It talks about data, not about SQL stuff), and in your catch block you can now safely perform the insert.

Another matter would be if that select can happen concurrently in two different transactions. In that case you need to fine tune your connection indicating an ISOLATION_LEVEL. But that would be another different question.

Off-topic recomendation for your code

UserTransaction: At least in the example you are using you don't need that. All Session Bean (aka EJB) (that is Stateless, Stateful and Singleton) provide transactionality by default. That means that before any business method a new transaction will automatically start, and will commit it at the end of the method. If the method throws an Exception the transaction will automatically Rollback.

If the business method is called from another EJB it will reuse the same transaction.

That is the default behaviour, and is really really powerful, allowing to manage most of the business cases without manually managing transactions.

gmanjon
  • 1,483
  • 1
  • 12
  • 16
0

If you use em.merge(market); instead of em.persist(market); JPA translates that to an insert-if-absent-else-update, which will avoid violating the constraint in your database that every (exchange, symbol) pairing be unique.

This may not work if the unique constraint isn't a primary key. Or even if it is ... My experience is you just can't catch an exception that the persistence container is throwing. One solution would be to make sure there isn't already a record with the same exchange and symbol:

String jpql = "select count(*) from Market where m.exchange = ? and m.symbol = ?";
tx.begin();
int count = em.createQuery(jpql, Integer.class)
.setParameter(1, market.getExchange())
.setParameter(2, market.getSymbol()).getSingleResult();
if(count == 0){
  em.persist(market);
  tx.commit();
}
pete_bc
  • 76
  • 4