JDBC操作全攻略

JDBC操作全攻略

JDBC简介

JDBC(Java Database Connectivity),也称为Java数据库连接,是Java用于连接数据库的接口规范,需要注意的是,JDBC并没有提供的实现,具体的实现是由数据库提供商,比如MySQL,Oracle等负责提供,JDBC提供了一整套完整的连接规范,包括了DriverDriverManagerConnectionStatementPreparedStatement,ResultSetDatabaseMetaDataResultSetMetaDataParameterMetaData等等

连接数据库

在Java程序中,操作数据库时,通常需要几个步骤

  1. 导入对应的JDBC实现:由于JDBC本身没有提供对应的实现,也不太可能提供对应的实现,所以需要导入对应数据库供应商提供的实现,比如MySQL的实现
  2. 在程序中手动注册对应的数据库驱动
  3. 建立并且打开对应的连接
  4. 通过连接获取一个Statement对象,用于发送SQL,并且获得对应的结果
  5. 从Statement中获得结果集对象
  6. 关闭对应的结果集、Statement以及连接

接下来通过代码来具体查看如何操作

首先是导入MySQL的实现,对应的Maven依赖为


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>

然后建立测试类TestJDBC.java

注册MySQL驱动


    // 注册MySQL驱动
    Class.forName("com.mysql.jdbc.Driver");

建立连接


    private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring"; // 数据库连接地址
    private static final String SQL_USER_NAME = "root"; // 账号
    private static final String SQL_PASSWORD = "huanfeng"; // 密码

    // 建立连接
    Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

    // 获得Statment对象
    Statement statement = connection.createStatement();

完整的过程如下所示


    private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring";
    private static final String SQL_USER_NAME = "root";
    private static final String SQL_PASSWORD = "huanfeng";

    Connection connection = null;
    Statement statement = null;
    ResultSet resultSet = null;

    try {

        // 注册MySQL驱动
        Class.forName("com.mysql.jdbc.Driver");

        // 建立连接
        connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "SELECT * FROM user";
        
        // 获得Statement
        statement = connection.createStatement();
    
        // 获得ResultSet
        resultSet = statement.executeQuery(sql);

        // 操作ResultSet

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        // 注意关闭的顺序,应该从ResultSet再到Statement再到Connection
        // 关闭ResultSet
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 关闭Statement
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 关闭Connection
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

这里一定要注意,资源用完之后一定要关闭,因为建立一个对应的连接是非常消耗资源的,而如果使用完之后不关闭资源,会造成非常大的资源浪费,为了证实这一点,下面进行一个简单的测试,测试建立一个Connection所消耗的时间


 @Test
    public void testConnection() throws ClassNotFoundException, SQLException {

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

        long start = System.currentTimeMillis();
        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
        long end = System.currentTimeMillis();
        connection.close();
        System.out.printf("cost time %d ms\n", (end - start));

    }

对应的输出结果如下所示


    cost time 360 ms

可以看到,建立一个Connection连接需要消耗非常多的时间,原因在于,建立连接的时候,本质上是通过驱动程序与数据库之间建立一个Socket连接,对于Socket有了解的朋友应该知道,建立Socket连接是比较消耗时间的,而且维持一个Socket连接也是非常消耗资源的,所以当资源使用完毕之后,应该关闭对应的资源,当然,更好的做法是使用数据库连接池技术,将Connection对象缓存起来,来避免频繁创建Connection对象。

JDBC实战

上面大概了解了JDBC的操作步骤以及基本的注意事项,接下来通过一个例子来使用JDBC操作数据库,加深对JDBC使用的了解

建立连接的过程同上,这里不重复叙述

插入数据

    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "Insert into user(id, name) value(4, 'Tom')";
        Statement statement = connection.createStatement();
        int result = statement.executeUpdate(sql);
        System.out.println(result);
        statement.close();
        connection.close();

    }

上面是最简单的插入操作,不过一般来说,我们会插入参数而不是固定的数值,所以上面的内容会变成


    int id = 4;
    String name = "Tom";
    // 拼接SQL语句
    String sql = "Insert into user(id, name) value("+ id +", '"+ name +"')";

通过拼接SQL语句,然后使用Statement来执行语句是解决插入参数的一种方式,但是,通过这种方式进行数据库操作会出现SQL注入的危险,比如说,当有人把参数name的内容填写为 Tom'); DELETE FROM USER; --,那么此时拼接后的SQL语句就会变成insert into user(id, name) values(4, 'tom'); delete from user; -- 此时就非常危险了,虽然这里是不会出现问题的,因为statement一次只能执行一条语句,但是如果是查询后面被加上这些内容,那就非常危险了。为了避免被注入的情况发生,JDBC提供了另外一种方式,采用预编译处理SQL,然后使用PreparedStatement设置参数以及执行对应的SQL语句,如下。


    // 采用占位符来处理
    String sql = "Insert into user(id, name) value(?, ?)";

    PreparedStatement statement = connection.prepareStatement(sql);
    // 设置对应的参数,注意这里是从1开始,不是从0开始
    statement.setInt(1, id);
    statement.setString(2, name);
    // 这里也可以直接使用
    // statement.setObject(1, id);
    // statement.setObject(2, name);
    // 不需要指定对应的参数类型,将其交给JDBC进行判断即可

JDBC是推荐使用PreparedStatement来进行SQL处理,而不是Statement来处理,原因在于PreparedStatement提供了预处理机制,可以预防SQL注入的发送,而且由于是采用预处理机制,所以PreparedStatement的执行效率要高于Statement

删除数据


    @Test
    public void testDelete() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 3;
        String sql = "DELETE FROM  USER where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        int result = statement.executeUpdate();

        statement.close();
        connection.close();
    }

修改数据


    @Test
    public void testUpdate() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 2;
        String name = "Jack";
        String sql = "UPDATE USER SET NAME = ? where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, name);
        statement.setObject(2, id);

        int result = statement.executeUpdate();

        statement.close();
        connection.close();
    }

查询数据

谈到查询数据,这里就有一个新的对象需要介绍,ResultSet,在JDBC中,查询的结果一般都是以行为单位的,可以是单行也可以是多行,JDBC将每一行都封装成一个ResultSet对象,对于ResultSet的操作,其实就是一个迭代的过程,在获取的时候,需要通过 ResultSet.getXX(ID) 或者ResultSet.getXX(CLO_NAME)来获取对应行的数据,然后通过ResultSet.next()将指针移动到下一行。


    @Test
    public void testQuery() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 1;
        String sql = "SELECT id, name FROM USER where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        // 查询返回的ResultSet
        ResultSet resultSet = statement.executeQuery();

        // 遍历ResultSet
        while (resultSet.next()){
            // 取出对应的数据
            int n_id = resultSet.getInt("id");
            String n_name = resultSet.getString("name");
            // 封装成对应的对象
            User user = new User(n_id, n_name);
            System.out.println("user: " + user);
        }
        // 关闭对应的连接
        resultSet.close();
        statement.close();
        connection.close();
    }

大对象处理

所谓的大对象,指的是数据库中定义的CLOB(Character Large Object )、BLOB(Binary Large Object),这两个数据类型是用于把包含信息量比较大的数据存储在列中,当做一个属性,其中CLOB主要是用于存放字符类型大对象,比如说一本书的内容,BLOB主要用于存放二进制类型的大对象,比如说一部小电影。JDBC同样为操作这两种数据类型提供支持,不过,操作起来跟普通的数据类型有所不同,具体如下所示


    @Test
    public void testBLOB() throws ClassNotFoundException, SQLException, IOException, InterruptedException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        // 存入Blob类型的数据
        File file = new File("d:/img.jpg");
        int id = 121;
        String name = "jack";
        String sql = "insert into user(id, name, b_data) values(?, ?, ?)";

        PreparedStatement statement = connection.prepareStatement(sql);

        statement.setObject(1, id);
        statement.setObject(2, name);
        // 这里需要注意,类型应该为Blob,直接传一个输入流即可
        statement.setBlob(3, new FileInputStream(file));

        int result = statement.executeUpdate();
        System.out.println(result);

        // 读取Blob类型的数据
        sql = "SELECT b_data FROM user WHERE id = ?";
        statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        ResultSet resultSet = statement.executeQuery();

        if (resultSet.next()){
            // 注意这里,返回的类型是BLob
            Blob b_data = resultSet.getBlob("b_data");
            InputStream inputStream = b_data.getBinaryStream();
            byte[] buf = new byte[inputStream.available()];
            FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/new_img.jpg"));
            inputStream.read(buf);
            fileOutputStream.write(buf);
            fileOutputStream.flush();
            System.out.println("finish");
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

CLOB的操作基本同BLOB,只是对应的类型设置为CLOB即可,需要注意的是,在MySQL中,没有数据类型为CLOB的数据类型,其对应的是text,这里在创建数据表的时候需要注意一下。

批量处理

所谓的批处理,其实就是同时指定多个SQL语句,通常由两种做法,一种是循环执行多个SQL语句,另外一种则是采用JDBC提供的Batch功能,具体如下所示

 @Test
    public void testBatch() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "insert into user(id, name) values(?, 'Tom')";
        PreparedStatement statement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        // 普通循环执行
        for (int i = 0; i < 1000; i++){
            statement.setObject(1, i);
            statement.executeUpdate();
        }

        long end = System.currentTimeMillis();

        System.out.printf("cost time %d\n", end - start);

        statement.close();

        statement = connection.prepareStatement(sql);

        start = System.currentTimeMillis();
        // 使用Batch执行
        for (int i = 1001 ; i < 2000; i++){
            statement.setObject(1, i);
            statement.addBatch();
        }
        statement.executeBatch();
        end = System.currentTimeMillis();

        System.out.printf("cost time %d\n", end - start);

        statement.close();
        connection.close();
    }

有点可惜的是,经过测试,这两种方式的性能很接近,不知道是我操作的问题还是事实确实是如此,还望有懂这个的朋友指点指点,

元数据

所谓的元数据,也就是是用于定义其他数据的数据,在SQL中,元数据可以理解为就是数据库、表的定义数据,在JDBC中,有三种类型的元数据,分别是DatabaseMetaDataResultSetMetaDataParameterMetaData,分别是数据库相关信息,结果集相关信息,以及PreparedStatement语句中的相关参数信息,具体操作如下所示


    @Test
    public void testMetaData() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        // DatabaseMetaData 相关的信息,比如数据库供应商名称,版本等
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        System.out.println("version : " + databaseMetaData.getDatabaseMajorVersion());
        System.out.println("name : " + databaseMetaData.getDatabaseProductName());

        int id = 4;
        String sql = "select id, name from user where id < ? ";
        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);
        
        // ParameterMetaData 相关信息,比如参数个数
        ParameterMetaData parameterMetaData = statement.getParameterMetaData();
        int paramCount = parameterMetaData.getParameterCount();
        System.out.println("parameter count : " + paramCount);

        ResultSet resultSet = statement.executeQuery();
      
        // ResultSetMetaData 相关信息,比如取出来的数据的列名
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int resultCount = resultSetMetaData.getColumnCount();
        System.out.println("resultCount : " + resultCount);
        for (int i = 0; i < resultCount; i++) {
            System.out.print(resultSetMetaData.getColumnName(i + 1) + "  ");
        }
        System.out.println();
    }

事务管理

所谓的事务,是指做一件事情,这件事情有多个步骤,这多个步骤这件,要么都完成,要么都不完成,事务具有四个特性ACID,分别是原子性,一致性,独立性,持久性
在JDBC中,可以通过connection.setAutoCommit(true/false);开控制事务,默认为true,也就是默认自动提交事务。通过connection.commit()手动提交事务,connection.setSavepoint();设置保存点,connection.rollback();回滚到保存到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • JDBC概述 在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如...
    usopp阅读 3,533评论 3 75
  • 本节介绍Statement接口及其子类PreparedStatement和CallableStatement。 它...
    zlb阅读 1,143评论 0 0
  • 1 引言# 本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybait...
    七寸知架构阅读 76,442评论 36 980
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,460评论 0 4
  • 你用月光杯 干尽了一江春水 而我想用一首诗 起舞你波涛汹涌的江湖 不必介怀 是什么弄伤了你的心 以文字做针线吧 缝...
    花千魂阅读 218评论 0 0