How to make SQLExceptions thrown by DB2 JDBC driver more descriptive?
Currently I am getting this kind of exceptions. It is cumbersome to work with these cryptic SQLCODE and SQLSTATE numeric values. Is there a way where to make the SQL exception to contain code description.
Caused by: com.ibm.db2.jcc.b.SqlException: DB2 SQL error: SQLCODE: -302,
SQLSTATE: 22001, SQLERRMC: null
at com.ibm.db2.jcc.b.hh.c(hh.java:1662)
at com.ibm.db2.jcc.b.hh.a(hh.java:1238)
at com.ibm.db2.jcc.c.db.n(db.java:737)
....
e.g. SQLSTATE 22001 has this description:
Character data, right truncation occurred; for example, an update or insert value is a string that is too long for the column, or a datetime value cannot be assigned to a host variable, because it is too small.
Edit: I am also using Spring and Hibernate frameworks.
Set the JDBC driver property retrieveMessagesFromServerOnGetMessage to true. Example connection url:
jdbc:db2://host:50128/MYDB:retrieveMessagesFromServerOnGetMessage=true;
See also DB2 11.1 Documentation
Check that the exception implements com.ibm.db2.jcc.DB2Diagnosable
On a DB2Diagnosable, you can call getSqlca() to get a DB2Sqlca. It will return the SQL error code (getSqlCode()), the state (getSqlState()) and you can call getMessage() to get a properly formatted and readable error message.
There is probably a smart reason why IBM didn't map this to getMessage() of the exception. My guess is that they didn't because DB2Sqlca.getMessage() can throw nasty exceptions, so you must wrap it in try-catch.
Spring contains translators for SQLException which convert the database-specific codes and states into a description exception class hierarchy.
It's part of the larger Spring API, but there's nothing stopping you from using just that class.
For example, if you have a DAO which extends JdbcDaoSupport, then you can have code like this:
try {
// ... some code that throws SQLException
} catch (SQLException ex) {
throw getExceptionTranslator().translate(null, null, ex);
}
This translates and wraps the SQLException is Spring's own strongly typed exception hierarchy.
If you're not using JdbcDaoSupport, then you can use the getExceptionTranslator() method of JdbcTemplate (and if you're not using that, then look at the source to see how it works.)
Related
While connecting to Derby database using OpenJPA, i have encountered this PersistenceException, which says connection could not be obtained for Derby EmbeddedDriver driver class, but that is not important (i know how to fix it):
77 RegistryManagement INFO [JavaFX Application Thread] openjpa.jdbc.JDBC - Using dictionary class "org.apache.openjp
a.jdbc.sql.DerbyDictionary".
<openjpa-3.1.2-r66d2a72 nonfatal general error> org.apache.openjpa.persistence.PersistenceException: There were errors i
nitializing your configuration: <openjpa-3.1.2-r66d2a72 fatal user error> org.apache.openjpa.util.UserException: A conne
ction could not be obtained for driver class "org.apache.derby.jdbc.EmbeddedDriver" and URL "jdbc:derby:db". You may ha
ve specified an invalid URL.
....
Important is that there is this Caused by in that exception:
....
Caused by: ERROR XJ004: XJ004.C : [0] db
at
....
which is one of Derby error codes (https://db.apache.org/derby/docs/10.5/ref/rrefexcept71493.html).
This PersistenceException was thrown when calling .createEntityManager(). Is it pretty long when it comes to text length.
I can catch that PersistenceException by wrapping .createEntityManager() with try-catch, but i can not figure out how to find more information about what is the cause, aka. get the error code, because PersistenceException can be thrown for various reasons. And i can not wrap it with catching SQLException or UserException, because my IDE says that method does not throw these exceptions.
I tried calling .getMessage() or .getCause() on that PersistenceException, but i got almost same long text.
When i called .getCause() it returns RuntimeException and I could see this, that Derby thrown SQLException:
....
Caused by: java.sql.SQLException: XJ004.C : [0] db
at
....
calling .getCause() again returns null.
I do not want to search the whole message (String) for occurences of some error codes, because that might be resource heavy. I think this problem might not be specific to Derby and happen while using other sql dbs, which might also throw some exception.
My code:
public class DatabaseManager
{
private EntityManagerFactory managerFactory;
private EntityManager manager;
public DatabaseManager(String persistenceUnitName, Map<String, String> properties)
{
managerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, properties);
try
{
manager = managerFactory.createEntityManager();
}
catch(PersistenceException e)
{
//stuff
}
}
....
EntityManagerFactory is an interface from javax.persistence package:
public interface EntityManagerFactory {
public EntityManager createEntityManager();
....
As i was debugging, found this methods in PersistenceExceptions class, which are
from OpenJPA:
#Override
public RuntimeException translate(RuntimeException re) {
return PersistenceExceptions.toPersistenceException(re);
}
this method translates caught RuntimeException, which is in question's case OpenJPA's UserException caused by SQLException, and then creates new PersistenceException based on it here and returns it:
/**
* Translate the given general exception.
*/
private static Throwable translateGeneralException(OpenJPAException ke) {
Exception e = new org.apache.openjpa.persistence.PersistenceException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
e.setStackTrace(ke.getStackTrace());
return e;
}
As you can see creating new org.apache.openjpa.persistence.PersistenceException based on OpenJPAException values (and not itself) causes that it is not possible to retrieve lower cause.
OpenJPA version 3.1.2
I'm using JDBI to connect to my Postgres db. This has been working fine. Today I decided to try replacing the native Postgres driver with PGJDBC-NG driver. Everything is fine until I try my first simple query:
jdbi.useExtension(FooDao.class, dao -> {
dao.insert(e);
});
This unfortunately results in:
org.jdbi.v3.core.ConnectionException: java.sql.SQLException: Unwrap error
Debugging into the app I find the exception occurs in the customizeHandle method of JDBI's PostgresPlugin class:
#Override
public Handle customizeHandle(Handle handle) {
PGConnection pgConnection = Unchecked.supplier(() -> handle.getConnection().unwrap(PGConnection.class)).get();
return handle.configure(PostgresTypes.class, pt -> pt.addTypesToConnection(pgConnection));
}
The exception is thrown on the first line, in the unwrap method. The problem it seems is that with the new driver, getConnection returns an instance of PGDirectConnection, which is not assignable from PGConnection, as the unwrap method call specifies.
Is there a work-around for this? I'm thinking I could just extend PostgresPlugin and override the implementation of customizeHandle to unwrap using PGDirectConnection but I'd prefer not to if possible.
Edit: Ugh, can't override customizeHandle because PostgresTypes.addTypesToConnection isn't public.
The PostgresPlugin JDBI is querying for PGConnection directly; which is a class only available in the standard driver. So until it's altered to work with NG, it won't.
Although, according to the docs...
The plugin configures mappings for the Java 8 java.time types like Instant or Duration, InetAddress, UUID, typed enums, and hstore.
but none of this is necessary because PGJDBC-NG supports all of these types natively!
Solution
Don't use the PostgresPlugin
Using Spring's JdbcTemplate, I've been trying to figure out a clean way to log exceptions in the DAO layer, but can't seem to figure it out. I want to log the SQL statement that was used and the parameters.
For example, where addStoreSql is a parameterized statement
public int addStore(Store store) {
return jdbcTemplate.update(addStoreSql, store.getId(), store.getName());
}
I'm doing something like..
public int addStore(Store store) {
try{
return jdbcTemplate.update(addStoreSql, store.getId(), store.getName());
} catch (DataAccessException ex) {
logger.error("exception on deleting store - " + store.toString(), ex);
throw ex;
}
}
My question, is there a way to write this any cleaner across many dao methods? Possibly at the logger level or some Spring library? Or is this the cleanest way (Or is the above code even bad)?
I have multiple methods that do basically the same thing, take in a object, pass the fields to a query and return the result.
The difficulty of doing this with Spring is that the JDBC objects that you would want to get this information from are not Spring-managed objects, they're created by the driver. So Spring AOP won't apply (without using AspectJ).
Spring can supply the query and parameters separately for you, if you log the category "org.springframework.jdbc.core.JdbcTemplate" at DEBUG level and "org.springframework.jdbc.core.StatementCreatorUtils" at TRACE level.
There are existing libraries log4jdbc and p6spy that implement a wrapper around the JDBC driver, in order to generate a SQL statement with the parameters inserted in place. See this question. Using either of these should be a matter of adding the jar to the project, changing your jdbc url to point to the wrapper, and tweaking the logging to get the level of information you want.
The existing logging code is not good because it is repetitious cut-n-paste code, and it will result in exceptions being logged multiple times. The logs will be harder to read and will roll more frequently.
Definitely don't use this pattern:
logger.error("exception on deleting store - " + store.toString(), ex);
throw ex;
because if often leads to duplicates log entries. There should be some global trap for exceptions and its responsibility is to log the error.
EDIT
By global trap for exceptions, I mean that every application should have some mechanism for catching most (ideally all) exceptions from Java code and log them. Imagine that you don't catch and miss log for some important error. You are than blind when trying to figure out what happened in production.
So let's pretend that we have such exception logging mechanism in place. Your pattern would log SQL error and throw exception that would be catched by global exception trap and logged again. You don't want that to happen, so don't log it in your code, save one line of code and don't create duplicate log entry.
There is a String object called detailMessage in java.lang.Throwable class, which says the reason of any exception thrown in java code.
This object is initialized through a constructor using super(string message) statement or setter or whatever from subclasses like Exception and again from its subclasses like SQLException.
When SQLException is thrown, error message can be displayed using sqlExceptionObject.getMessage();
The error message in sqlExceptionObject.getMessage() is same as in MySQL tool (Incase of any error in query execution)
So is the error message copied from MySQL? If yes, then from where?
The JDBC driver is responsible for that. It could translate an error code it gets from the DB to a string or it could get the error string directly from the DB. I don't know which one is the case for MySQL.
I have entity Foo, which maps to sql table with some unique constraints. Thus saving Foo may fail. I am using FooDao to save Foo:
#Repository
public class FooDao
{
#Autowired
private SessionFactory sessionFactory;
#Transactional
#Override
public void add(Foo item) {
sessionFactory.save(item);
}
}
when I call method FooDao#add(Foo) it may fail for two reasons: either because of unique constraint violation (in this case, I know how to handle the problem) or because of some other problem (in this I probably should propagate the exception). How do I distinguish between those two situations?
I could add method find(Foo item) to FooDao and check, whether something like item, which I was trying to add is database. But this would require additional select from database and I am a bit worried about this.
Thats actually SQLState.
do something like this
Catch(HibernateException he){
SQLException sqe = he.getSQLEception();
String sqlState = sqe.getSQLState();
if(sqlState.equals("23000"){
// Handle your exception
}
}
Java doc:
SQLState - an XOPEN or SQL:2003 code identifying the exception
One link I found for ISO sqlStates,
link to reference
But look for exact reference and value..
One obvious (but maybe nasty) solution is that you catch javax.persistence.PersistenceException and parse the error message for "violant" or "constraint".
From my point of view you should do the select/find upfront. Remember that you are using an ORM! Hibernate has caches involved so neither the select/find nor the key contraint error might be the result of an actual db query but the result of an calculation of Hibernate based on your already in cache loaded data.
Sebastian
Catch org.hibernate.JDBCException. This has getErrorCode(). For unique constraint voilation its ORA-00001.
-Maddy
If there is a database exception it gets caught in a HibernateException, which is a checked exception. Spring wraps this in a DataAccessException, which is unchecked. This exception will run up to your Controller and out to the Servlet and end up in a stack trace in the browser and log file. You can catch this exception and print it out so you can see what is happening. This will at least get you to the point where you know what is actually breaking.
Bad keys is probably one issue. But bad values is probably another. Some non-null fields are null, or something isn't long/short enough etc. Fixing this probably involves validation. You can use the Hibernate Validator. You give your fields some nifty annotations and then you get validation errors in java before you even get to the database - errors happen faster.