3

I have an application that uses Java, mybatis, and SQL Anywhere. Because of some circumstances, I have to manually load in the SQLA driver jar at runtime. I got this working with this code:

    public static URLClassLoader ucl;
    public static Driver saDriver;


    public static final void loadSqlAnywhere(SqlAnywhereVersions version)
    {
        if (saDriver == null)
        {
            try
            {
                File jarPath = "path\\to\\sajdbc4.jar";
                URL jarUrl = new URL("jar:file:" + jarPath.getAbsolutePath() + "!/");

                String driverClass = "sap.jdbc4.sqlanywhere.IDriver";

                ucl = URLClassLoader.newInstance(new URL[]{ jarUrl });
                saDriver = new DriverShim((Driver) Class.forName(driverClass, true, ucl).newInstance());
                DriverManager.registerDriver(saDriver);

            }
            catch (Exception ex)
            {
                // Could not load SQL Anywhere driver
            }
        }
    }

While the application is running, it's possible for SQL Anywhere to be upgraded. So, I need to completely unload the old driver, let the update happen, then init with the latest jar. I tried this:

    private static List<SqlSession> sessions = new ArrayList<>();
    private static SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder();
    public static final void unloadSqlAnywhere()
    {
        if (saDriver != null)
        {

            // Close out any existing sessions
            for (SqlSession session : sessions)
            {
                session.close();
            }
            sessions.clear();

             // Kill the session factory
            sessionFactory = null;

            try
            {
                DriverManager.deregisterDriver(saDriver);
                saDriver = null;

                ucl.close();
                ucl = null;
            }
            catch (Exception ex)
            {
                // Failed to unregister SQL Anywhere driver
            }
        }
    }

But using jprofiler, I can see that DriverManager is still referencing sap.jdbc4.sqlanywhere.IDriver, which is causing the SQL Anywhere upgrade to kill my application.

Is there something more I can do to ensure all driver references are removed? Or is there a better way to go about this?

Troncoso
  • 2,343
  • 3
  • 33
  • 52
  • are you sure there is no exception thrown on deregistration? spec says: `If a security manager exists and its checkPermission denies permission, then a SecurityException will be thrown.` – mangusta Sep 27 '19 at 20:42
  • I am sure. My actual code has logging throughout it, including in the catch statements, but I removed that for brevity. – Troncoso Sep 27 '19 at 20:44
  • does that happen with SQLA or any other driver as well? – mangusta Sep 27 '19 at 21:05
  • 3
    It’s very unusual (I’ve never heard of anyone doing this, nor ever considered doing it) to update libraries on the fly. What’s wrong with just deploying the new library and restarting your app? It’s not like new versions are coming out daily, or even monthly, and restarting your app is something that should be a routine operation, because you do this every time you release a new version of your code. – Bohemian Sep 27 '19 at 21:32
  • This application is actually responsible for installing the latest SQLA version, along with other application updates. However, it has to be able to connect to an SQLA database during this upgrade, so the driver has to be reloaded in order for that to work. – Troncoso Sep 30 '19 at 12:44

1 Answers1

0

Most JDBC drivers auto-register themselves, e.g. you used to just have to call Class.forName() to load the driver class, and that would auto-register it.

Nowadays you don't even have to do that anymore, since they use the service framework to auto-register by simply being on the classpath, but since your JDBC jar file is not on the classpath, that doesn't apply here.

Since you also register the driver, it is registered twice, and when you deregister, you only remove the registration you created, not the one that was auto-registered.

To remove the auto-registered instance, you have to enumerate all registered drivers to find the instance to deregister:

// Any Java version
for (Enumeration<Driver> e = DriverManager.getDrivers(); e.hasMoreElements(); ) {
    Driver driver = e.nextElement();
    if (driver.getClass().getName().equals("sap.jdbc4.sqlanywhere.IDriver"))
        DriverManager.deregisterDriver(driver);
}
// Java 9+
Optional<Driver> driver = DriverManager.drivers()
        .filter(d -> d.getClass().getName().equals("sap.jdbc4.sqlanywhere.IDriver"))
        .findAny();
if (driver.isPresent())
    DriverManager.deregisterDriver(driver.get());
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thank you for your answer. However, I attempted this for loop and printed out each driver found. The only one remaining after my initial DriverManager.deregisterDriver() call is "org.sqlit.JDBC", so I don't believe that my driver was auto-registered – Troncoso Sep 30 '19 at 12:44