JDBC入门及使用

什么是JDBC

  • java数据库连接(Java DataBase Connectivity)
  • JDBC可以让java通过程序操作关系型数据库
  • JDBC基于驱动程序实现与数据库的连接与操作

什么是驱动程序?

驱动程序是数据库厂商按照java所提供的JDBC接口实现的操作数据库的程序。每个厂商内部实现不一,但是接口总是一致,因此我们可以使用JDBC搭配不同的驱动来操作厂商的数据库产品。

JDBC的优点

  • 统一的API,提供一致的开发过程
  • 易于学习,容易上手,代码结构稳定
  • 功能强大,执行效率高,可以处理海量数据

JDBC开发流程

JDBC开发流程主要为以下五个步骤

  1. 加载并注册JDBC驱动
  2. 创建数据库连接
  3. 创建Statement对象并执行sql语句
  4. (遍历)查询SQL执行结果
  5. 关闭连接,释放资源

接下来将详细介绍这五个步骤

加载并注册JDBC驱动

这里使用的是mysql数据库,因此加载的是mysql数据库对应的驱动程序

Class.forName("com.mysql.cj.jdbc.Driver");

Class.forName方法会加载参数指定的类,并返回参数指定的类或接口关联的Class对象,这里我们只需要这个类被加载,所以不需要获取返回值。

这里贴一下forName的详细实现,注意一下这个Reflection.getCallerClass()方法,等会在创建数据库连接的时候会有用到,这里我们可以知道className所指定的类是由caller的classloader加载的。

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

类被加载过程中,会执行static参数以及static域的初始化,这里进入com.mysql.cj.jdbc.Driver方法,会看到Driver中有个static域,这个域会在Class.forName执行时被初始化

package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

代码中可以看到,com.mysql.cj.jdbc.Driver实现了java.sql.Driver接口,并且继承了NonRegisteringDriver类,这个NonRegisteringDriver类其实就是mysql驱动具体的针对java.sql.Driver的具体实现类。里面主要方法是connect方法,可以java创建mysql数据库连接。

另外,代码中主要执行的方法为DriverManager.registerDriver(),这里会将数据库驱动程序注册到JDBC的DriverManager,后续使用将会通过DriverManager调度。registerDriver具体实现如下,registeredDrivers这个变量是一个CopyOnWriteArrayList类型,里面存储了所有注册到DriverManager的驱动程序信息。

public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);

}

创建数据库连接

getConnection方法需要三个参数,分别是数据库url,以及用户名和密码。具体信息可以查看相关官网

Connection conn = DriverManager.getConnection(DBurl, DBname, DBpwd);

这里列举常见的几种数据库url连接格式

RDBMSURL格式默认端口举例
oraclejdbc:oracle:thin:@dbip:port:databasename1521jdbc:oracle:thin:@10.10.10.10:1521:dataBase
mysqljdbc:mysql://dbip:port/databasename3306jdbc:mysql://10.10.10.10:3306/dataBase
SQLServerjdbc:microsoft:sqlserver://dbip:port;DatabaseName=databasename1433jdbc:sqlserver://10.10.10.10:1433;DatabaseName=dataBase

getConnection方法内部可见详情如下,没有什么激动人心的高级操作,只是继续调用了一个同名的方法。不过这里有一个熟悉的操作,那就是同名方法的一个参数值是Reflection.getCallerClass()。我们刚刚在使用Class.forName()方法加载驱动类的时候有见到过,这里会保证我们获取到的驱动程序是和现在执行的程序是在同一个classloader下,保证程序正确运行。

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }

    return (getConnection(url, info, Reflection.getCallerClass()));
}

同名方法中我删掉了部分抛出异常的操作,不过不影响阅读。循环中会遍历所有已注册的驱动信息,并通过callerCL或者引导类加载器(若callerCL为null)来定位和加载驱动程序,并且在获取驱动类成功的情况下,调用driver.connect方法创建数据库连接。

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }

    SQLException reason = null;

    for(DriverInfo aDriver : registeredDrivers) {
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }
}

private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
    if(driver != null) {
        Class<?> aClass = null;
        try {
            aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
        } catch (Exception ex) {
            result = false;
        }

        result = ( aClass == driver.getClass() ) ? true : false;
    }

    return result;
}

创建Statement对象并执行sql语句

常用的Statement对象有两种,Statement和PreparedStatement

Statement

Statement创建和执行sql都由驱动程序完成,没啥可说的(当然我也没细看XD)

Statement stmt = conn.createStatement();
ResultSet rs =  stmt.executeQuery("select * from employee");
PreparedStatement

相比之Statement,PreparedStatement因为会对sql语句预编译,可以有效防止SQL注入攻击,并且执行效率比之Statement要高一些

String sql = "select * from employee where dname =? and eno > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, qdname);
pstmt.setInt(2, 1002);
ResultSet rs = pstmt.executeQuery();

除了单条执行sql,PreparedStatement也支持批处理操作,会比循环单条执行快一些

String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

for (int i = 200000; i < 300000; i++) {
    pstmt.setInt(1, i);
    pstmt.setString(2, "员工" + i);
    pstmt.setFloat(3, 5000f);
    pstmt.setString(4, "市场部");
    //                pstmt.executeUpdate();
    pstmt.addBatch();
}

pstmt.executeBatch();

(遍历)查询SQL执行结果

遍历查询结果如下,当然有些update操作会只返回一个int,具体情况具体分析,这里主要展示ResultSet的遍历,rs.getXXX()方法中的数字参数表示字段在数据库表中的位置,从1开始,每个数字都表示数据库表中的第几列元素

while (rs.next()) {
    Integer eno = rs.getInt(1);
    String ename = rs.getString(2);
    Float sal = rs.getFloat(3);
    String dname = rs.getString(4);
    Date hiredate = rs.getDate(5);
    System.out.println(dname + "-" + eno + "-" + ename + "-" + sal + "-" + hiredate);
}

不过不怎么推荐上面的获取数据方式,一般来讲,可以直接使用数据库表的列名作为参数获取数据行对应值

while (rs.next()) {
    Integer eno = rs.getInt("eno");
    String ename = rs.getString("ename");
    Float salary = rs.getFloat("salary");
    String dname = rs.getString("dname");
    Date hiredate = rs.getDate("hiredate");
    Employee employee = new Employee(eno, ename, salary, dname, hiredate);
    list.add(employee);
}

关闭连接,释放资源

使用完数据库,记得关闭数据库连接,释放相关资源

try {
    if (conn != null && !conn.isClosed()) {
        conn.close();
    }
} catch (Exception e) {
    e.printStackTrace();
}

JDBC的事务

JDBC为了满足对数据库的事务操作,也是有它自己的事务逻辑支持,JDBC支持两种事务模式

  • 自动提交事务模式
  • 手动提交事务模式

自动提交事务模式

  • 自动提交事务模式会在每次执行sql写操作后,自动提交事务
  • 自动提交事务模式开启方法:conn.setAutoCommit(true),该方法一般默认开启
  • 自动移交事务是JDBC的默认行为,一般无法保证多数据的一致性

手动提交事务模式

  • 顾名思义,手动提交模式是指程序员自己使用程序管理事务,常用方法commit()和rollback()
  • 手动开启事务方法:conn.setAutoCommit(false)
  • 手动提交事务可以保证多数据一致性,不过必须手动提交/回滚

方法示例

DBUtils封装了获取/关闭数据库连接的程序片断

public static void tc2() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    try {
        long startTime = System.currentTimeMillis();
        conn = DbUtils.getConnection();
        conn.setAutoCommit(false);

        String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
        pstmt = conn.prepareStatement(sql);

        for (int i = 200000; i < 300000; i++) {
            pstmt.setInt(1, i);
            pstmt.setString(2, "员工" + i);
            pstmt.setFloat(3, 5000f);
            pstmt.setString(4, "市场部");
            //                pstmt.executeUpdate();
            pstmt.addBatch();
        }
        pstmt.executeBatch();
        conn.commit();  //成功提交
        long endTime = System.currentTimeMillis();
        System.out.println("tc2 cost " +(endTime - startTime) + " ms");
    } catch (Exception e) {
        try {
            if (conn != null && !conn.isClosed()) {
                conn.rollback();//失败回滚
            }
        } catch (SQLException ex) {
            e.printStackTrace();
        }
        e.printStackTrace();
    } finally {
        DbUtils.closeConnection(conn);

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬天的锦绣江山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值