根据字符串,引入不通的 jdbc 驱动,为什么会错乱?

2021-09-07 22:00:15 +08:00
 among

   // 驱动
    public String GetDriver(String url) {
        try {
            if (url.startsWith("jdbc:as400")) {
                return "com.ibm.as400.access.AS400JDBCDriver";
            } else if (url.startsWith("jdbc:oracle")) {
                return "oracle.jdbc.driver.OracleDriver";
            } else if (url.startsWith("jdbc:mysql")) {
                return "com.mysql.cj.jdbc.Driver";
            } else if (url.startsWith("jdbc:sqlserver")) {
                return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
            } else if (url.startsWith("jdbc:informix-sqli")) {
                return "com.informix.jdbc.IfxDriver";
            } else if(url.startsWith("jdbc:sybase")) {
            	return "com.sybase.jdbc4.jdbc.SybDriver";
            }else if(url.startsWith("jdbc:db2")) {
            	return "com.ibm.db2.jcc.DB2Driver";
            }else if(url.startsWith("jdbc:dm")) {
                return "dm.jdbc.driver.DmDriver";
            }else {
                log.error("连接字符串有误:" + url);
                return "";
            }
        } catch (Exception e) {
            log.error("返回驱动异常:" + e);
            return "";
        }
    }

com.tp.util.DataBaseRun : 连接:jdbc:oracle:thin:@xx.xx.xx.xx:1521/db 异常:java.sql.SQLNonTransientConnectionException: Cannot load connection class because of underlying exception: com.mysql.cj.exceptions.WrongArgumentException: Malformed database URL, failed to parse the main URL sections.

在并发较多的情况下,会出现错乱的情况。

如 oracle 的,会到 mysql 的类中去寻找。

3042 次点击
所在节点    Java
25 条回复
Vedar
2021-09-07 22:56:06 +08:00
因为你这个方法就不是线程安全的
liangch
2021-09-07 23:02:02 +08:00
并发再多,url 不都是 oracle 开头么。不如你把 GetDriver 返回值 log 下看看。
teliang
2021-09-07 23:02:48 +08:00
@Vedar 这里没有修改数据,不存在数据一致问题吧
thetbw
2021-09-07 23:13:10 +08:00
我记得 jdbc 驱动是 不需要手动 Class.forName 的,java 有个 SPI 机制
EscYezi
2021-09-08 00:55:37 +08:00
具体场景是什么?单看这个方法没什么问题,看下用这个方法返回值的地方是不是线程安全的
chihiro2014
2021-09-08 01:26:18 +08:00
为什么会有这么诡异的写法,不考虑下 baomidou 的数据源切换么
chenshun00
2021-09-08 08:02:00 +08:00
Arthas 看下,这里就不存在并发问题,并发是由于存在共享数据导致的竞争条件才成立的,这里就是一个无状态的方法。

肯定是传进来的 URL 有问题。
xiao109
2021-09-08 08:36:35 +08:00
贴上来的代码是线程安全了。可后续的加载数据库驱动就不一定是线程安全了。
among
2021-09-08 09:03:56 +08:00
@Vedar @thetbw @EscYezi @all

这个最初的需求是这样的:
我有个 py 的项目,需要连接各种各样的数据库,informix 、oracle 、db2 、sybase 、达梦、mysql 等等等。
在 py 中连接,很多老的数据库并没有驱动,如 sybase,也会有些麻烦的环境和依赖问题。

所以,就找了外包的同事,做了一个基于 REST 的数据库服务,采用的是 sprint boot,等于说做了一个对外的接口。
接收 py 这边的数据库查询、修改请求,调用 jdbc 驱动去做。

后来,实现是实现了,但是并发较多的情况下,会发现会有串号的情况,就是传过去的字符串明明是 oracle 开头的,最后的 java 的报错信息里面,报的在 mysql 的 lib 中寻找。


对 java 不熟,外包的同事也不在了,结果。。。

代码,我都贴在这里了: https://gitee.com/among/demo

代码量很少,帮忙给点思路。
AlkTTT
2021-09-08 09:20:48 +08:00
DriverManager.getConnection 这里线程不安全,要么加锁,要么用框架连
JYii
2021-09-08 09:21:32 +08:00
没太仔细看代码,写的有点乱... 但是这种跨数据库查询,应该为每次加载数据库驱动单独设置一个线程变量把
sutra
2021-09-08 09:30:59 +08:00
@AlkTTT DriverManager.getConnection 这个静态方法本身是线程安全的吧。
lonenol
2021-09-08 09:31:03 +08:00
用数据库连接池就好了。。没必要搞这么费劲。。
你这个问题是因为 Class.forName 那里是线程不安全的
xiao109
2021-09-08 09:39:13 +08:00
@sutra 但 Class.forName 和 DriverManager.getConnection 一起就不一定是了
thetbw
2021-09-08 10:24:12 +08:00
@among 同楼上,获取驱动的时候加锁试下,还有就是 Class.forName 一次就可以了,没必要每次都加载,还有这段代码应该是多余的,对应驱动会自动加载,如果没有自动加载,系统启动的时候全加载就行了,没必要每次都加载一次
ffkjjj
2021-09-08 12:18:25 +08:00
@xiao109 #14
@thetbw #15
楼主这里都是 new 出来的对象, 而且没有类属性, 没有什么问题吧😅
x66
2021-09-08 13:14:30 +08:00
我看了半天我也没看到哪里有并发问题的风险,加点日志再找下原因吧,万一是参数穿错了呢
JinTianYi456
2021-09-08 13:47:35 +08:00
@among #9 报错行 https://gitee.com/among/demo/blob/master/SpringBoot/src/main/java/com/tp/util/DataBaseRun.java#L279
该行所在函数是没有线程安全问题的(没去看它调用别的函数),也不是他们说的 forName 等之类的
问题在于 getConnection 函数,该函数内部为:(删减版)

----
for (DriverInfo aDriver : registeredDrivers) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
if (reason != null) {
throw reason;
}
----

假设 registeredDrivers=[mysql,oracle]. 进入 getConnection,用 mysql 连,假设返回 SQLException,赋值给 reason,
然后用 oracle 连,假设返回 SQLException 但 reason 已有值了,又或者返回 null,最后都是 throw reason(by mysql)
所以错误原因是: 传入的 url jdbc:oracle:xxxxx 有错误(或者其它),导致获得不到 Connection
ikas
2021-09-08 13:59:02 +08:00
不要用 getConnection,换成 getDriver,然后调用 Driver 的 connect
MineDog
2021-09-08 14:33:50 +08:00
从 18 楼的代码你就能看到,jdbc getConnection 方法是一个个驱动去试的,能拿到非空的 Connection 就认为驱动能处理当前 url,你既然能根据 url 映射对应驱动,那直接通过 DriverManager 拿到驱动的实例自己调用 driver.connect 方法就行

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/800497

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX