V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
among
V2EX  ›  Java

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

  •  
  •   among · 2021-09-07 22:00:15 +08:00 · 3275 次点击
    这是一个创建于 1177 天前的主题,其中的信息可能已经有所发展或是发生改变。
    
       // 驱动
        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 的类中去寻找。

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

    肯定是传进来的 URL 有问题。
    xiao109
        8
    xiao109  
       2021-09-08 08:36:35 +08:00
    贴上来的代码是线程安全了。可后续的加载数据库驱动就不一定是线程安全了。
    among
        9
    among  
    OP
       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
        10
    AlkTTT  
       2021-09-08 09:20:48 +08:00
    DriverManager.getConnection 这里线程不安全,要么加锁,要么用框架连
    JYii
        11
    JYii  
       2021-09-08 09:21:32 +08:00
    没太仔细看代码,写的有点乱... 但是这种跨数据库查询,应该为每次加载数据库驱动单独设置一个线程变量把
    sutra
        12
    sutra  
       2021-09-08 09:30:59 +08:00 via iPhone
    @AlkTTT DriverManager.getConnection 这个静态方法本身是线程安全的吧。
    lonenol
        13
    lonenol  
       2021-09-08 09:31:03 +08:00
    用数据库连接池就好了。。没必要搞这么费劲。。
    你这个问题是因为 Class.forName 那里是线程不安全的
    xiao109
        14
    xiao109  
       2021-09-08 09:39:13 +08:00
    @sutra 但 Class.forName 和 DriverManager.getConnection 一起就不一定是了
    thetbw
        15
    thetbw  
       2021-09-08 10:24:12 +08:00
    @among 同楼上,获取驱动的时候加锁试下,还有就是 Class.forName 一次就可以了,没必要每次都加载,还有这段代码应该是多余的,对应驱动会自动加载,如果没有自动加载,系统启动的时候全加载就行了,没必要每次都加载一次
    ffkjjj
        16
    ffkjjj  
       2021-09-08 12:18:25 +08:00
    @xiao109 #14
    @thetbw #15
    楼主这里都是 new 出来的对象, 而且没有类属性, 没有什么问题吧😅
    x66
        17
    x66  
       2021-09-08 13:14:30 +08:00
    我看了半天我也没看到哪里有并发问题的风险,加点日志再找下原因吧,万一是参数穿错了呢
    JinTianYi456
        18
    JinTianYi456  
       2021-09-08 13:47:35 +08:00   ❤️ 1
    @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
        19
    ikas  
       2021-09-08 13:59:02 +08:00   ❤️ 1
    不要用 getConnection,换成 getDriver,然后调用 Driver 的 connect
    MineDog
        20
    MineDog  
       2021-09-08 14:33:50 +08:00   ❤️ 1
    从 18 楼的代码你就能看到,jdbc getConnection 方法是一个个驱动去试的,能拿到非空的 Connection 就认为驱动能处理当前 url,你既然能根据 url 映射对应驱动,那直接通过 DriverManager 拿到驱动的实例自己调用 driver.connect 方法就行
    selca
        21
    selca  
       2021-09-08 14:44:40 +08:00
    由于 SPI 机制,Class.forName 基本不会影响到下一行 DriverManager.getConnection 的调用,我跟 18 和 19 楼的观点一样,Java 的 getConnection 方法本身就是依次遍历所有注册驱动,如果成功后就直接返回,哪知道 mysql 的驱动只成功了一部分,既然有判断 url 类型的代码,就直接通过 url 去手动取得连接就行。
    among
        22
    among  
    OP
       2021-09-08 14:46:54 +08:00   ❤️ 1
    ikas
        23
    ikas  
       2021-09-08 15:08:55 +08:00
    @among 这里基本说的基本就是上面那个..最好的方式,就是你自己先 getDriver
    Driver 这个是可以缓存的..还有也没有必要每次 class.forname..
    buster
        24
    buster  
       2021-09-08 17:24:11 +08:00
    又学到新的一招,从没遇到过类似的问题。
    曾经做过的连多 DB 的事情,每种 DB 都会搞一个带线程池的工具类。
    pjntt
        25
    pjntt  
       2021-09-12 00:49:02 +08:00
    调用这个方法时候是线程安全的吗?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5529 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 07:02 · PVG 15:02 · LAX 23:02 · JFK 02:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.