Solved Access to MySQL via Java-based JNI layer returns 'Driver not loaded'
-
I created a Windows-based application that accesses MySQL using Qt 5.3.1 some years ago. The model (DB) code is nice and modular. I also created a Java-based JNI layer to access the DB from our Eclipse (Java)-based application. This, also, has been working very well for years.
Now I'm in the middle of upgrading my VS to 2017 and Qt to 5.12. I built Qt 5.12 and added support to MySQL (just like I did in 5.3). I have the application working just fine accessing MySQL.
However, my Java JNI-based access is not working. I'm able to connect to and open the DB. However, subsequent calls to access the DB result in 'Driver not loaded' error. I've not really changed any of the code or application or library structure. The basic connect and open code:
QSqlDatabase::addDatabase(_driverName, _connectionName); QSqlDatabase db = QSqlDatabase::database (_connectionName, false); db.setHostName(), etc. db.open(); // returns true.
Then, in another Java JNI-based call, I issue the following:
QSqlDatabase db = QSqlDatabase::database (_connectionName, true); db.isOpen(); // returns false
Note 'QSqlDatabase::contains(_connectionName)' returns true so the connection is there.
The only other thing I see is that these two operations (the connection/open) and subsequent ' QSqlDatabase::database (_connectionName, true);' to use the Db appear to be in separate threads.
However, all of this worked in Qt 5.3 (separate threads, etc.). Also, I see the ' QSqlDatabase::database ()' code issues a 'qWarning()' if the connect thread and currentThread are different. But I'm not seeing any warnings dumped to the console. So that doesn't seem to be the problem.....
Ideas?
-
Definitely a threading issue. My JNI-accessed library is called from a different thread from our Eclipse/Java application for each call. Hmm.
I maintain (and did in the old code) a map collection of database configurations - keyed by a name. Sooo, now I use the following to generate a thread specific 'name'.
static QThreadStorage<std::string> _dbNameThreadStorage; . . std::string getThreadDBConnectionName(const std::string & configName) { std::string dbThreadConfigName; if (_dbNameThreadStorage.hasLocalData()) { dbThreadConfigName = _dbNameThreadStorage.localData(); } else { dbThreadConfigName = configName + "-" + QUuid::createUuid().toString().toStdString(); _dbNameThreadStorage.setLocalData(dbThreadConfigName); } return dbThreadConfigName; }
I also created another small structure to hold the DB config deets for databases based on the 'configName'. I use this when I have to create a real connection. As in:
DatabaseConnection * getDatabaseConnection(const std::string & configName) { std::string dbThreadConfigName = getThreadDBConnectionName(configName); DatabaseConnection * retVal = 0; DB_CONNECTION_MAP::iterator dItr = _dbConnectionMap.find(dbThreadConfigName); if (dItr != _dbConnectionMap.end()) { retVal = dItr->second; } else { // new thread, probably. Create new connection using deets from base connection. DB_CONNECTION_MAP::iterator dcItr = _dbConfigurationMap.find(configName); if (dcItr != _dbConfigurationMap.end()) { DatabaseConnection * dcConfig = dcItr->second; retVal = new DatabaseConnection( dcConfig->_driverName, dbThreadConfigName, dcConfig->_connectionType, dcConfig->_dbName, dcConfig->_dbHost, dcConfig->_dbUser, dcConfig->_dbPass, dcConfig->_port, true); _dbConnectionMap[dbThreadConfigName] = retVal; } else { // error } } return retVal; }
All of this code is used in both the '.exe' application and in the JNI-accessed library layer. It all works well in all cases.
-
Oop, definitely a thread issue.... The 5.3 QSqlDatabase::database() function looks like:
QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open) { const QConnectionDict *dict = dbDict(); Q_ASSERT(dict); dict->lock.lockForRead(); QSqlDatabase db = dict->value(name); dict->lock.unlock(); if (db.isValid() && !db.isOpen() && open) { if (!db.open()) qWarning() << "QSqlDatabasePrivate::database: unable to open database:" << db.lastError().text(); } return db; }
The 5.12 QSqlDatabase::database() function looks like:
QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open) { const QConnectionDict *dict = dbDict(); Q_ASSERT(dict); dict->lock.lockForRead(); QSqlDatabase db = dict->value(name); dict->lock.unlock(); if (!db.isValid()) return db; if (db.driver()->thread() != QThread::currentThread()) { qWarning("QSqlDatabasePrivate::database: requested database does not belong to the calling thread."); return QSqlDatabase(); } if (open && !db.isOpen()) { if (!db.open()) qWarning() << "QSqlDatabasePrivate::database: unable to open database:" << db.lastError().text(); } return db; }
I installed a QtMessageHandler and, sure enough, the 'QSqlDatabase::database()' 'qWarning()' dumps the 'QSqlDatabasePrivate::database: requested database does not belong to the calling thread.' message.
I'll post a solution once I devise one....
-
Hi,
The solution is to basically create a new connection in each separated thread.
-
Definitely a threading issue. My JNI-accessed library is called from a different thread from our Eclipse/Java application for each call. Hmm.
I maintain (and did in the old code) a map collection of database configurations - keyed by a name. Sooo, now I use the following to generate a thread specific 'name'.
static QThreadStorage<std::string> _dbNameThreadStorage; . . std::string getThreadDBConnectionName(const std::string & configName) { std::string dbThreadConfigName; if (_dbNameThreadStorage.hasLocalData()) { dbThreadConfigName = _dbNameThreadStorage.localData(); } else { dbThreadConfigName = configName + "-" + QUuid::createUuid().toString().toStdString(); _dbNameThreadStorage.setLocalData(dbThreadConfigName); } return dbThreadConfigName; }
I also created another small structure to hold the DB config deets for databases based on the 'configName'. I use this when I have to create a real connection. As in:
DatabaseConnection * getDatabaseConnection(const std::string & configName) { std::string dbThreadConfigName = getThreadDBConnectionName(configName); DatabaseConnection * retVal = 0; DB_CONNECTION_MAP::iterator dItr = _dbConnectionMap.find(dbThreadConfigName); if (dItr != _dbConnectionMap.end()) { retVal = dItr->second; } else { // new thread, probably. Create new connection using deets from base connection. DB_CONNECTION_MAP::iterator dcItr = _dbConfigurationMap.find(configName); if (dcItr != _dbConfigurationMap.end()) { DatabaseConnection * dcConfig = dcItr->second; retVal = new DatabaseConnection( dcConfig->_driverName, dbThreadConfigName, dcConfig->_connectionType, dcConfig->_dbName, dcConfig->_dbHost, dcConfig->_dbUser, dcConfig->_dbPass, dcConfig->_port, true); _dbConnectionMap[dbThreadConfigName] = retVal; } else { // error } } return retVal; }
All of this code is used in both the '.exe' application and in the JNI-accessed library layer. It all works well in all cases.