JDBC基础编程六步+应用实例_最易理解!

Posted 葡萄籽-June

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC基础编程六步+应用实例_最易理解!相关的知识,希望对你有一定的参考价值。

JDBC(Java DataBase Connectivity)

本文主要是通过简单的例子理解JDBC运行的基础原理,以及简单的JDBC工具类如何创建与使用。




前言

JDBC的理解

首先思考两个问题。
1、为何SUN定制的一套JDBC接口? (因为每一个数据库的底层实现原理都不一样。)
2、JDBC的本质? (一套接口。)
通过下面的图进行一个简单的梳理与关系介绍。
在这里插入图片描述

由上述可知,JDBC提供一套连接并操作数据库的接口,各大数据库厂家根据此接口编写了各自的相应实现类。


一、大致原理

本节主要通过简单的栗子来了解相应的底层原理。(作为编程人员,有时不需要磕相关的底层代码到底吖)

走一下思路,捋一下~
1、连接数据库的接口
2、实现者将各个数据库根据接口封装具体连接驱动的方法,实现此接口
3、调用者需要先创建我们想要连接的数据库对象
4、调用者根据相关的数据库来带调用此数据库的具体连接方法

自定义JDBC接口:

public interface JDBC {
     default void getConnection(){};
}

mysql实现类:

public class MySQL implements JDBC{
    @Override
    public void getConnection(){
//        具体的实现代码与java程序员无关系,只需要会使用即可
        System.out.println("连接MySQL数据库成功!");
    }
}

Oracle实现类:

public class Oracle implements JDBC{
    @Override
    public void getConnection(){
        System.out.println("连接Oracle数据库成功!");
    }
}

应用主类:

public class JavaProgrammer {
    public static void main(String[] args) throws Exception {

//       模拟实现
        
//        创建需要连接的数据库对象
        JDBC jdbc = new MySQL();
//        调用连接方法
        jdbc.getConnection();
//        如上,只是使用不同的数据库对象
        JDBC jdbc1 = new Oracle();
        jdbc1.getConnection();
    }
}

二、Java中JDBC编程六步(Important!!!)

1.JDBC六大步骤

  1. 注册驱动
    作用:告诉Java程序,即将要连接的是哪个品牌的数据库。
  2. 获取连接
    表示JVM进程和数据库进程之间的通道打开了,这属于进程之间的通信。重量级的,使用完后一定要关闭通道。
  3. 获取数据库操作对象
    专门执行sql语句的对象
  4. 执行SQL语句
    主要执行DQL和DML
  5. 处理查询结果集
    只有当第四步执行的是DQL(SELECT语句)时,才会有第五步的处理查询结果集。
  6. 释放资源
    使用完资源一定要关闭资源。Java和数据库之间的通信属于进程间的通信。若开启之后,一定要关闭!

【注】一般资源的释放需要从里到外进行释放。

2. 实际应用

在实际应用之前,先介绍一下需要了解的知识点

2.1 应用前准备

2.1.1 JDBC中url连接地址

格式:

jdbc:mysql://[host][,failoverhost...][:port]/[database] [?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]...

mysql的JDBC连接时的URL参数:

主要参数参数说明
user数据库用户名(用于连接数据库)
password用户密码(用于连接数据库)
useUnicode是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true
characterEncoding当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk
autoReconnect当数据库连接异常中断时,是否自动重新连接?
autoReconnectForPools是否使用针对数据库连接池的重连策略
failOverReadOnly自动重连成功后,连接是否设置为只读?
maxReconnectsautoReconnect设置为true时,重试连接的次数
initialTimeoutautoReconnect设置为true时,两次重连之间的时间间隔,单位:秒
connectTimeout和数据库服务器建立socket连接时的超时,单位:毫秒。 0表示永不超时,适用于JDK 1.4及更高版本
socketTimeoutsocket操作(读写)超时,单位:毫秒。 0表示永不超时

举个小例子:

String url = "jdbc:mysql://localhost:3306/my_employees?useUnicode=true&characterEncoding=utf8&useSSL=true";

上述例子中,my_employees是数据库的名字;serverTimezone:url的时区使用中国标准时间,UTC+8才是准确时间。

2.1.2 URL:统一资源定位符

URL包括:

  • 协议
  • IP
  • PORT
  • 资源名

2.2 Statement作为数据库操作对象的应用实例

下面的栗子主要通过 MySQL驱动进行应用吖(其实什么驱动都可以,只要了解了原理,就很容易定位需要改动哪里啦)
根据上述JDBC连接数据库的6大步骤进行实例操作。(上述主要的六大步骤已经在代码中注释。)

package demo;

import com.mysql.jdbc.Driver;
import com.mysql.jdbc.Statement;

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

/**
 * @description: 通过简单的栗子,使用JDBC连接数据库并进行交互。
 * @author: Grape_Pip
 */
public class JDBCDemo {
//		  主入口
    public static void main(String[] args) throws SQLException {
//        插入数据
        insertData();
//        更新数据
        updateData();
    }

/**
     * @MethodName : insertData()
     * @Description //TODO  插入数据
     * @Param  []
     * @return  void
     **/
    public static void insertData(){
        Statement stmt = null; //数据库操作对象
        Connection conn = null; //连接对象
        try {
            //        * 1. **注册驱动**
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//      oracle的驱动:      DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

            //        * 2. **获取连接**
            String url = "jdbc:mysql://localhost:3306/myemployees?characterEncoding=utf8&useSSL=true";
            String user = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, user, password);
//            System.out.println("数据库连接对象:"+conn);

            //        * 3. **获取数据库操作对象**
            stmt = (Statement) conn.createStatement();

            //        * 4. **执行SQL语句**
            String sql = "INSERT INTO departments(department_id,department_name,manager_id,location_id) \\n" +
                    "VALUES(117,'Twa',100,1700);";
            /*
             * 专门执行DML语句的(insert  delete  update)
             * 返回值是“影响数据库中的记录条数”
             * */
            int res = stmt.executeUpdate(sql);
            System.out.println(res == 1 ? "保存成功" : "保存失败");
            //        * 5. **处理查询结果集**
            /*若不是select则不需要处理结果集,只需要判断是否成功即可
             * */
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //        * 6. **释放资源**
            /*
             * 为了保证资源一定释放,在finally语句块中关闭资源
             * 并且要遵循从小到大依次关闭
             * 分别对其try-catch(如果一起try-catch,顺序执行,上面的捕获下面的语句就不执行了,所以要分开)
             * */
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e2) {
                e2.printStackTrace();
            }
        }
    }


/**
     * @MethodName : updateData()
     * @Description //TODO  更新数据
     * @Param
     * @return
     **/
    public static void updateData(){
        Statement stmt = null;
        Connection conn = null;
        try {
            //        * 1. **注册驱动**
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());

            //        * 2. **获取连接**
            String url = "jdbc:mysql://localhost:3306/myemployees?autoReconnect=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true";
            String user = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, user, password);

            //        * 3. **获取数据库操作对象**
            stmt = (Statement) conn.createStatement();

            //        * 4. **执行SQL语句**
            String sql = "UPDATE departments set department_name='Rup' where department_id=117;";
            int res = stmt.executeUpdate(sql);
            System.out.println(res == 1 ? "更新成功" : "更新失败");

            //        * 5. **处理查询结果集**
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //        * 6. **释放资源**
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e2) {
                e2.printStackTrace();
            }
        }
    }

}


2.3 PrepareStatement作为数据库操作对象的应用实例

PrepareStatement的大致原理是预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。
PrepareStatement 有以下特点:
1、接口继承了java.sql.Statement
2、属于预编译的数据库操作对象。


package demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * @description: 使用PreparedStatement作为数据库操作对象
 * @author: Grape_Pip
 */
public class PrepareStmtJDBCDemo {

    public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        String loginName="test";
        String loginPwd="1234";
        try{
//            1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
//            2、连接数据库
            String url = "jdbc:mysql://localhost:3306/myemployees?characterEncoding=utf8";
            String user = "root";
            String password = "root";
            conn=DriverManager.getConnection(url,user,password);
//            3、获取预编译的数据库操作对象
            //SQL语句的?,表示一个占位符。一个?接受一个值。【注】占位符不可使用单引号
            String sql = "select * from employees where loginName= ? and loginPwd= ? ";
            //程序执行到此,会发送sql语句给DBMS。然后DBMS进行sql语句的预先编译。
            pstmt= conn.prepareStatement(sql);
            //给占位符?传值,第一个问号下标是1,第二个?下标是2。【注】JDBC中,所有下标从1开始
            pstmt.setString(1,loginName);
            pstmt.setString(2,loginPwd);
//            4、执行sql语句
            rs=pstmt.executeQuery();
//            5、处理查询结果集
            if (rs.next()){
                //登陆成功
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //            6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
    }
}


2.4 Statement 与 PreparedStatement 的区别

StatementPreparedStatement
SQL注入问题存在解决
编译编译一次执行一次编译一次,可执行N次
效率较低较高
安全性直接编译,不做检查(会导致SQL注入)会在编译阶段做类型的安全检查

上述表中提到SQL注入,那么什么是SQL注入呢?有兴趣可以查看本篇文章☞SQL注入_实例总结


3、总结——JDBC工具类的封装

思考一个问题:
可以看到上述2.2中,以Statement为数据库操作对象时所举的例子包含有插入和更新方法,在每个方法中都有代码重复的地方,那么为了提高代码的复用率,是不是可以把代码重复(可复用)的地方进行封装呢?
————此时,JDBC工具类的封装变得更加有必要了。

在封装工具类之前,需要考虑清楚,哪一步是否值得封装,封装的时候使用什么方式会更合适等问题。带着这些问题,我们来进行工具类的封装。

首先,对于连接数据库的6步步骤中,都有哪些步骤可以进行封装?

  • 1、加载驱动;2、连接数据库;3、创建数据库对象 ;6、关闭资源

其次,考虑每个步骤的在使用过程中或者从其性质方面上看,用什么进行封装编译运行比较好?

  • 加载驱动和获取连接(加载驱动需要损耗一定的时间,而获取连接是要打开程序与数据库之间的通信,已经明确需要加载以及获取连接,此时可以在编译时就准备好。因此可以放在静态代码块中,使其在类编译的时候就加载驱动和获取对应的连接。)
  • 创建连接对象(每次进行通信都需要获取数据库操作对象,因此,把它封装进静态的方法中,其他类可以直接类名.调用,而不需要创建对象,造成资源的浪费。)
  • 关闭资源(关闭资源是很重要的一步,其实就是关闭通信。因此,把它封装进静态的方法中,并直接类名.调用即可。)

最终,封装为一个基本的JDBC工具类,如下代码所示。

package util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @description: JDBC工具类,简化JDBC编程
 * @description: 工具类中的方法都是私有的。因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用。
 * @author: Grape_Pip
 */
public class DBUtil {
    private static String driver;
    private static String url;
    private static String user;
    private static String password;
/**
 * @Description //TODO  静态代码块:类加载的时候就加载
 * @Param
 * @return
 **/
    static {
        //新建属性对象
        Properties property = new Properties();
        //通过反射,新建字符输入流,读取mysql.properties文件
        InputStream input = DBUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
        //将输入流中读取到的属性,加载到properties属性及对象中
        try {
            //将input 加载到property对象中
            property.load(input);
            //根据键,获取properties中对应的值赋值
            driver = property.getProperty("driver");
            url = property.getProperty("url");
            user = property.getProperty("user");
            password = property.getProperty("password");

        } catch (IOException e) {
            e.printStackTrace();
        }
//        加载驱动
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description //TODO  获取数据库对象
     * @Param  []
     * @return  java.sql.Connection
     **/
    private static Connection getConnection() throws SQLException {//不需要try-catch,向上抛出被捕获即可
        return DriverManager.getConnection(url,user,password);
    }

    /**
     * @Description //TODO 关闭资源
     * @Param  [conn] 连接对象
     * @Param  [stmt] 数据库操作对象
     * @Param  [rs] 结果集
     * @return  void
     **/
    private static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (stmt!=null){
            try {
                stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

[补充]_JDBC工具类的应用

本小结主要是根据上述JDBC工具类的封装后,进行实例应用。

package util;




import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * @description: JDBC工具类的使用 :测试DBUtil;模糊查询
 * @author: Grape_Pip
 */
public class JDBCTest {
    public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        try {
//            获取连接
            conn=DBUtil.getConnection();
//            获取预编译的数据库操作对象
            /*错误的写法:
            String sql="select employee_id,last_name" +
                    "from employees" +
                    "where last_name LIKE '?%'";
            pstmt=conn.prepareStatement(sql);
            pstmt.setString(1,"A");
            */
            String sql="select employee_id,last_name " +
                    "from employees " +
                    "where last_name like ?";
            pstmt=conn.prepareStatement(sql);
            pstmt.setString(1,"A%");
            rs=pstmt.executeQuery();
            while (rs.next()){
                System.out.println("员工编号:"+rs.getInt("employee_id")+
                        "\\t员工名:"+rs.getString("last_name"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
//            释放资源
            DBUtil.close(conn,pstmt,rs);
        }
    }
}

其实,上述工具类的封装只是一种封装方式。自己进行封装的时候,可以根据自己的需求设计JDBC的工具类。

本周的JDBC分享就到这里叭。~~~

以上是关于JDBC基础编程六步+应用实例_最易理解!的主要内容,如果未能解决你的问题,请参考以下文章

JDBC编程六步

Java中JDBC的本质及使用详解(妹子看了都说好!!!)

让Python更优雅更易理解

List去重复(不是最简单,但绝对是最易理解)

虚拟DOM -------- 最易理解的解释

JDBC(本质,配置环境变量,JDBC编程六步,类加载注册,sql注入,事务问题,封装工具类,悲观锁,乐观锁)