0

I had a scenario where I need to perform database connectivity using Java11 twice using Windows Authentication on SQL server. Initially, the sqljdbc_auth.dll is loaded for the first call and the connection was successful. But, in order to make the connection second time at a different place, it throws an SQLException saying 'sqljdbc_auth.dll already loaded by another classloader'. So, I need to unload the dll in between the two calls.

There is an option to do the same till Java8 version by invoking the finalize() on the classloader using reflection mechanism but unable to find an alternative in Java11

Sample code:

Here, I placed the sqljdbc_auth.dll in the PATH and jar named sql_jdbc.jar in the urls list and these are compatible with Java11

private static void loadFile(){
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();;
ClassLoader loader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); // here, urls is an array and contains only a single sql_jdbc.jar path in it
Thread.currentThread().setContextClassLoader(loader);
Driver driver = (Driver)loader.loadClass("com.microsoft.sqlserver.jdbc.SQLServerDriver").newInstance();
Connection connection = driver.connect("jdbc:sqlserver://IP-addr:1433;DatabaseName=db_name;SelectMethod=cursor;integratedSecurity=true", props);
//perform db actions here
Thread.currentThread().setContextClassLoader(currentClassLoader);
unloadDLL("sqljdbc_auth.dll",loader);
}
private synchronized static void unloadDllFile(String dllName, ClassLoader classLoader) throws Throwable {
try {
    Field field = ClassLoader.class.getDeclaredField("nativeLibraries");
    field.setAccessible(true);
    Map<Object, Object> lib = (ConcurrentHashMap<Object, Object>) field.get(classLoader);
    Set<Object> keyset = lib.keySet();
    for (Object dllpath : keyset) {
        if (dllpath.toString().contains(dllName)) {                    
            Object o = lib.get(dllpath);                    
            classLoader = null;
            field = null;
            lib.remove(dllpath);                     
            keyset.remove(dllpath);                   
            o = null;
            System.gc();                   
        }
     } 
  } catch (Exception e) {
        System.out.println("Exception in dll is "+e.getMessage());
         
  }
}

There is a similar code in the second component but it throws an exception there. Exception stacktrace while loading in the second component is:

com.microsoft.sqlserver.jdbc.SQLServerException: This driver is not configured for integrated authentication. ClientConnectionId:d19de7a1-d099-477c-9c18-0c4cd5807f5e
at com.microsoft.sqlserver.jdbc.SQLServerConnection.terminate(SQLServerConnection.java:2892)
at com.microsoft.sqlserver.jdbc.AuthenticationJNI.<init>(AuthenticationJNI.java:72)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.logon(SQLServerConnection.java:3636)
at com.microsoft.sqlserver.jdbc.SQLServerConnection$LogonCommand.doExecute(SQLServerConnection.java:3627)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7194)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2935)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.java:2456)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.java:2103)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.java:1950)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.java:1162)
at com.microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.java:735)
at sample.java_samples.Sample2.loadFile(Sample2.java:66)
at sample.java_samples.Sample2.main(Sample2.java:23)
Caused by: java.lang.UnsatisfiedLinkError: Native Library C:\Windows\System32\sqljdbc_auth.dll already loaded in another classloader
at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2456)
at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2649)
at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:829)
at java.base/java.lang.System.loadLibrary(System.java:1867)
at com.microsoft.sqlserver.jdbc.AuthenticationJNI.<clinit>(AuthenticationJNI.java:52)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.logon(SQLServerConnection.java:3635)
... 10 more

Thanks

user_3093890
  • 85
  • 1
  • 8
  • 1
    Why don’t you reuse the `Driver` to make a new collection? – Holger Sep 09 '20 at 13:59
  • @Holger, I don't have an option to reuse the driver as these are calls in two different components of the software. Can you please suggest if there is any way in doing so in this scenario? – user_3093890 Sep 10 '20 at 05:45
  • 1
    Since you said that you used the hack to call `finalize()` on the class loader for older Java versions, you must have access to the class loader. Then, you also have access to the driver. – Holger Sep 10 '20 at 06:31
  • @Holger, Yes, I used to call the finalize() immediately after my first connection is done and the purpose is served in the first component and so there was no problem in the subsequent connections in the later component. – user_3093890 Sep 10 '20 at 09:31
  • @Holger, updated the post to avoid the confusion related to where the unloadDll() is called in my scenario. Can you point me in the right direction in Java 11 – user_3093890 Sep 11 '20 at 09:41
  • 1
    You can save a lot of time by fixing the application design, to share the database driver. – Holger Sep 11 '20 at 10:19
  • @Holger, sure, I'll check the possibility to share the driver among these two components – user_3093890 Sep 12 '20 at 11:07

0 Answers0