"SQLException: Unwrap error" with JDBI and PGJDBC-NG Postgres Driver - java

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

Related

Does different database system implementations (e.g. MySQL, and Microsoft SQL Server) affect `DataSource` and `DriverManager` differently?

Here is some comparison of using DataSource and using DriverManager from https://stackoverflow.com/a/19674833/1224441
With JDBC, we can use an abstracted class, such as
java.sql.DataSource, which defines a database. However,
the MySQL DataSource and a Microsoft SQL DataSource are different
implementations. JDBC doesn't know which one you want to use.
So you use DriverManager to specify to JDBC that you want to use the
MySQL implementation, and you load that driver's class.
Say that later on you switch to Microsoft SQL. Now, all you have to do
is change the DriverManager to load the Microsoft SQL driver's class,
and all of your code that uses the abstract java.sql
classes will still work.
I found some examples of using DataSource and using DriverManager at http://zetcode.com/tutorials/jeetutorials/datasource/
But I don't see how different implementations (e.g. implementations by MySQL, and Microsoft SQL Server) would affect DataSource and DriverManager differently. Or do I misunderstand something?
When you acces you database with JDBC, you will handle java.sql.Connection, java.sql.Resultset... which are interfaces. You will actually use implementations that are specific to your database.
That way most of the code you wrote for one database can be used with another one and you don't have to learn a new API each time you need to access a new DB.
You need to specify first which implementations you need which are provided by the JDBC driver. You can do it with the old school DriverManager or with the now preferred DataSource.
DriverManager
The DriverManager is a concrete class. It isn't specific to any database. In summary it is just the place where you register your driver so you can get later all the specific implementations you need. It isn't specific by itself but it provides all the specific stuff you need.
Here is an excerpt from the example you mentioned with some of my comments.
final String url = "jdbc:mysql://localhost:3306/books";
//This line returns the Class of the Jdbc drive
//It will not be used but doing this will allow
//static initializations where it will register
//the driver to the DriverManager
Class.forName("com.mysql.jdbc.Driver");
//Here you get your connection implmentation
Connection con = DriverManager.getConnection(url, "root", "");
//MySQL specific Statement
Statement stmt = con.createStatement();
//MySQL specific ResultSet
ResultSet result = stmt.executeQuery("SELECT * FROM books");
The "trap" here is that the registration of the driver (to the DriverManager) is done during the static init done when you call the the Class.forName method.
For your information, here is the piece of code in com.mysql.jdbc.Driver doing the the driver registration in the init:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
DataSource
Using DataSource is more suitable for external configuration. DataSource is an interface and the implementation depends directly on the database.
In your example, the Driver implementation is specified in the web.xml file.
First we define the driver to be able to find it with JNDI
<database>
<jndi-name>jdbc/mysql</jndi-name>
<driver>
<type>com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource</type>
<url>jdbc:mysql://localhost:3306/books</url>
<user>root</user>
<password></password>
</driver>
<database>
And then we pass the reference to the servlet :
<servlet>
<servlet-name>datasource</servlet-name>
<servlet-class>com.zetcode.DataSourceExample</servlet-class>
<init>
<data-source>${jndi:lookup('jdbc/mysql')}</data-source>
</init>
</servlet>
That way, you had not to write any specific line of code. If you change your database, (in theory) you just have to change your configuration. Not the code.
Important notes
As Kayaman mentioned in the comments calling Class.forName is no longer needed since Java 6. All you need is having the driver in your classpath.
As a reminder using DriverManager is not recommended anymore. See this excerpt from the javadoc
NOTE: The DataSource interface, new in the JDBC 2.0 API, provides another way to connect to a data source. The use of a DataSource object is the preferred means of connecting to a data source.

provide connection to jdbc template

I am facing a use case that I have not ran into before with respect to using the jdbc template. I looked all over the web and also here, but can't find an answer to my use case.
In the current framework that I am using which was developed at the company I am currently working at, jdbc connections are readily availabe and obtained in the following format:
ConnectionManager.getInstance().getConnection(some_database_name);
The database name is a parameter provided and it can be different at times.
So in order to use the jdbc template, I created a sub class of the commons dbcp data source and added a constructor which takes in the connection above
public CommonsDBCPExtension(Connection conn) {
this.conn = conn;
}
Then I just instantiate a jdbc template and set the datasource:
this.jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(CommonsDBCPExtension);
After that so far all my tests are working. The reason I want to stick with the jdbc template is so that I can avoid all the nightmare jdbc code that I'll have to write which in my opinion is worse than what I have done so far.
So I am wondering if there is a more elegant way of doing this in spring, or this is just flat out wrong, or if it's the right approach. This is the first time I have run into this use case in my career, so a bit of help from anyone who has had experience with something like this would be greatly appreciated.

Hibernate: Trouble using sessionFactory.getTypeHelper().custom(userType)

Hibernate.custom(userType) is gone in Hibernate 5.2.10.Final so I have to use sessionFactory.getTypeHelper().custom(userType). Is there any way to get TypeHelper without sessionFactory? Previously I was using hibernate 3.6.10.Final. I would rather not use sessionFactory but I can't really find a way around it.
The main goal is to take a org.hibernate.usertype.UserType and return a org.hibernate.type.Type.
I have this function in 3.6.10.Final
public Type getHibernateType() {
// Type is class that implements hibernates UserType
return Hibernate.custom(UserType)
}
in 5.2.10.Final I had to change it to something like
public Type getHibernateType() {
return sessionFactory.getTypeHelper().custom(UserType);
}
I don't really want to use sessionFactory if I can help it. So I was wondering if there was another way to get the Type.
Well, technically 5.2 still exposes the functionality via TypeFactory in a static method.
Type type = TypeFactory.custom( UserType.class, null, null );
However, be aware this method is marked #Deprecated and in fact, that entire class has been removed as a part of Hibernate 6.0's type system overhaul.
I would get used to the notion of using the SessionFactory to access this information because that is precisely how we have designed 6.0 to work at present.

Runtime exception while creating Blob

Trying to create BLOB object using BLOB.createTemporary(connection, false, BLOB.DURATION_SESSION) ,But getting Class Cast Exception
java.lang.ClassCastException:
org.apache.commons.dbcp.cpdsadapter.ConnectionImpl cannot be cast to
oracle.jdbc.OracleConnection.
I tried following suggestions but still same error .Apache Commons DBCP connection object problem, Thread: ClassCastException in org.apache.tomcat.dbcp.dbcp.PoolingDataSource$PoolGuardConnectionWrapper
Some one please suggest me to resolve this issue.
The Oracle BLOB.createTemporary() method expects the Connection parameter to be a oracle.jdbc.OracleConnection object, but connections from Tomcat are managed by DBCP, so the connection is wrapped in a DBCP class (org.apache.tomcat.dbcp.dbcp.PoolingDataSource$PoolGuardConnectionWrapper).
You either need to unwrap it to get the real Oracle connection object, or stop using the Oracle BLOB.
Just use the JDBC methods: Blob blob = connection.createBlob()
Update
The JDBC Blob is an interface. There are no implementing classes in the JDK, so you'll always get a DBMS specific implementation. If needed, you can cast to OracleBlob, which is also an interface, that provides additional Oracle-specific methods.
Interesting javadoc for OracleBlob:
Generally any new code should avoid the direct use of the class BLOB. For variable declarations use the interface Blob or this interface as required. Instead of the static methods BLOB.createTemporary(java.sql.Connection, boolean, int) and BLOB.empty_lob() please use Connection.createBlob() and BLOB.getEmptyBLOB() respectively.

How to make JDBC SQLExceptions for DB2 more descriptive?

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.)

Categories