从此
文章
📄文章 #️⃣专题 🌐上网 📺 🛒 📱

JDBC(Java Database Connectivity) - 关系数据库管理系统(RDBMS)交互接口

🕗2019-07-03

一、JDBC

1、JDBC简介

  (1) JDBC(Java Database Connectivity),即Java数据库连接。用于在Java程序中实现数据库操作功能。
  (2)是一种用于执行SQL语句的Java API,使用户以相同的方式连接不同的数据库。
  (3)JDBC提供一套标准接口,即访问数据库通用的API,不同的数据库厂商会根据各自的数据库特点去实现这些接口。

 

2、JDBC工作步骤

  (1)加载驱动(使用Class.forName),建立连接(DriverManager)并返回连接(Connection)。
  (2)创建语句对象。(Connection 创建一个 Statement 或 PreparedStatement , 用于执行SQL语句)
  (3)执行SQL语句。(Statement 或 PreparedStatement执行SQL语句)
  (4)处理结果集。(SELECT产生结果集ResultSet)
  (5)关闭连接。(依次将ResultSet、Statement、PreparedStatement、Connection对象关闭,释放资源。)

3、为什么要释放资源、关闭连接?

  (1)释放资源:

    由于JDBC驱动在底层通常通过网络I/O实现SQL命令以及数据传输的,所以资源的释放有利于系统的运行。且由于是操作I/O,所以代码块需要抛出异常(即代码块写在try-catch语句或者通过throws抛出。)。

  (2)关闭连接:

    JDBC只有建立与数据库的连接才能访问数据库,而与数据库连接是非常重要的连接,若不能及时释放,会浪费资源。同时每次使用createStatement()方法以及prepareStatement()方法都会在数据库中打开一个游标(cursor),若不能即使关闭,可能导致程序抛出异常。

 

二、加载驱动、建立连接

  使用JDBC连接Oracle数据库时,涉及I/O操作,需要捕获异常,可以写在try-catch中。如果抛出异常:java.lang.ClassNotFoundException,则可能是未导入连接数据库的相关jar包。

1、oracle

 
//加载JDBC驱动,通过反射机制加载
Class.forName("oracle.jdbc.driver.OracleDriver");

//连接路径,用于连接数据库
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String userName = "LYH"; //默认用户名 
String userPwd = "SYSTEM"; //密码 

//建立连接
Connection conn = DriverManager.getConnection(url, userName, userPwd);

注:
Oracle连接方式:
     jdbc:oracle:thin:@<IP地址>:<port端口号>:<SID>     
 路径,jdbc:oracle:thin:@是固定写法,
localhost是主机号(IP地址),1521是端口号(Port),xe是SID(用于标识数据库)。
LYH是数据库(用户)名
SYSTEM是密码(口令) 
 

 

2、mysql(8.0之后的版本)

 
//加载JDBC驱动,通过反射机制加载
Class.forName("com.mysql.cj.jdbc.Driver");

//连接路径,用于连接数据库
//String url = "jdbc:mysql://localhost:3306/test";
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false";
String userName = "LYH"; //默认用户名 
String userPwd = "SYSTEM"; //密码 

//建立连接
Connection conn = DriverManager.getConnection(url, userName, userPwd);

注:
mySql连接方式:
    jdbc:mysql://<IP地址>:<port端口号>/<dbname相当于SID>
    
JDBC连接mysql 5需用com.mysql.jdbc.Driver
即:
Class.forName("com.mysql.jdbc.Driver");
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
 

 

3、sql server 2005

 
//加载JDBC驱动,通过反射机制加载
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 

//连接路径,用于连接数据库 tempdb
String url = "jdbc:sqlserver://localhost:1433;DatabaseName=tempdb";
String userName = "sa"; //默认用户名 
String userPwd = "123456"; //密码 

//建立连接
Connection conn = DriverManager.getConnection(url, userName, userPwd); 
 

 

三、Statement、PreparedStatement 、CallableStatement、ResultSet、ResultSetMetaData

1、Statement

  (1)Statement主要用于执行静态SQL语句(不带参数),即内容固定不变的SQL语句。
  (2)Statement每执行一次都要对传入的SQL语句编译一次,效率较差。

 
创建Statement的方式:
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
Statement state = conn.createStatement();

1、执行DDL语句:
格式:
boolean flag = state.execute(sql);

2、执行DML语句:(返回值为DML影响的数据库中的数据总条数)
格式:
int length = state.executeUpdate(sql);

3、执行DQL语句:(SELECT语句)
格式:
ResultSet rs = state.executeQuery(sql);

注:sql语句中不要带分号(;)。
 

 

2、PreparedStatement(可以预防sql注入攻击)

  (1) PreparedStatement可用于动态SQL语句,即SQL部分参数改变,其余条件不变。适用于多次执行SQL语句的时候,以提高效率。
  (2)PreparedStatement是一个接口,继承Statement。但其execute等三个方法不需要参数了。
  (3)PreparedStatement实例包含已编译的SQL语句。以符号(?)作为占位符,?表示一个任何类型的参数。

 
创建PreparedStatement的方式:
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
prepareStatement pstate = conn.createStatement();
sql = "select * from where id = ?";
pstate = conn.prepareStatement(sql); //预编译sql语句

设定参数
pstate.setString(1, "12345");

1、执行DDL语句:
格式:
boolean flag = pstate.execute();

2、执行DML语句:(返回值为DML影响的数据库中的数据总条数)
格式:
int length = pstate.executeUpdate();

3、执行DQL语句:(SELECT语句)
格式:
ResultSet rs = pstate.executeQuery();

注:sql语句中不要带分号(;)。
 

 

3、 CallableStatement

  提供了用来调用数据库存储过程的接口,如果有输出参数需要注册,则说明是输出参数。其调用存储过程有两种方式:带结果参数(一种输出参数,是存储过程的返回值) 与 不带结果参数。CallableStatement继承了PreparedStatement处理输入参数的方法,且还增加了调用数据库的存储过程和函数以及设置输出类型参数的功能。

4、ResultSet、ResultSetMetaData

  ResultSet表示的是DQL语句的结果集,但其结果集仍在数据库上(每次只取一定数量的数据放入内存,具体数量由不同的数据库驱动决定),若想取数据,需使用next方法每次取一条,其是与数据库一直连接的,当查询结束后,最好将其关闭,要不然可能会抛出异常。

 
ResultSet获取结果集
格式:
    ResultSet Statement.executeQuery(sql);
    ResultSet PreparedStatement.executeQuery();

ResultSetMetaData获取结果集的元数据,元数据指数据的数据,用于描述数据的属性、信息。
格式:
    ResultSetMetaData ResultSet.getMetaData();
 

 

5、PreparedStatement相比于Statement的优点

  (1)效率更高。

    使用PreparedStatement对象执行SQL语句时,命令会被数据库进行编译和解析,并将其放在命令缓冲区。每次执行PreparedStatement对象时,由于在缓冲区可以找到预编译命令,虽然会被解析一次,但是不会被再次编译,可以重复使用,从而提高了效率。
  (2)代码可读性与可维护性更高。

    由于PreparedStatement使用setInt()或者setString()方法为参数进行赋值,而Statement需要在SQL语句中写出,相比之下PreparedStatement更加清晰。
  (3)安全性更好。

    使用PreparedStatement可以有效防止SQL注入攻击。所谓SQL注入攻击,通常指的是将SQL语句通过插入到Web表单提交输入域名或者查询页面请求的查询字符串,最终达到欺骗服务器并且执行恶意SQL命令的目的。由于Statement是将输入的字符串当成SQL语句进行拼接,再进行编译,所以其不能防范SQL注入攻击,而PreparedStatement是将输入的字符串当成一个值来处理,避免了SQL注入攻击的问题。

 
【sql注入举例:】
SQL语句为:
String str = " SELECT *
FROM emp
WHERE name = '"
+ name + "' AND password = '"
+ password'";
即相当于SQL语句
SELECT *
FROM emp
WHERE name = '' AND password = '?'

如果输入name = "aa", password = "bb' OR  '1' = '1"
那么相当于SQL语句:
SELECT *
FROM emp
WHERE name = 'aa' AND password = 'bb' OR  '1' = '1'
那么不管输入是否正确,此WHERE条件均是成立的。即SQL注入攻击成功。
 

 

四、使用连接池(DBCP)连接JDBC

1、什么是连接池?

  连接池是管理与创建数据库连接的缓冲池技术,将连接提前准备好,方便使用。连接池也是一个接口,具体由不同厂商进行实现。

2、未使用DBCP时

  一次数据库连接对应着一次物理连接,而每次操作数据都要打开、关闭这个连接,会消耗大量资源。

3、使用DBCP时

  系统启动并连接数据库后,会自动创建一定数目的连接,并将这些连接组成一个连接池。每次连接时,会从连接池中取出一个连接,使用完后,将该连接放回连接池(注:此处不是关闭连接)。这样可以使连接重复使用,提高程序运行效率。

4、DBCP原则:

  (1)系统启动时,连接到数据库后,会初始化创建一定数目的连接,并将其组成一个池。
  (2)每次连接时,从池内取出已有连接,使用完后,将连接归还(不是关闭连接)给连接池。
  (3)若池内无连接或达到最小连接数时,按增量增加新连接。
  (4)保持最小连接数,其有动态检查、静态检查的方法。
    动态检查:定时检查池,若数量小于最小连接数,则补充连接。
    静态检查:当连接不足时,才检查数量是否小于最小连接数。

5、连接池导包

  使用连接池,需要导包。

commons-dbcp-1.4.jar ;用于连接池的实现
commons-pool-1.5.jar ;连接池实现的依赖库
commons-collections4-4.0.jar ;

 

五、实例:

1、使用普通的JDBC,直接在代码中赋值

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

/**
 * 使用JDBC连接Oracle数据库
 * 涉及I/O操作,需要捕获异常,可以写在try-catch中。
 * 
 * 如果抛出异常:java.lang.ClassNotFoundException,
 * 那么表示未导入包,比如:Oracle 11g JDBC_ojdbc6.jar。
 *
 */
public class Test {
    public static void main(String[] args) {
        try {
            //1、加载驱动    
            Class.forName("oracle.jdbc.driver.OracleDriver");
            
            //建立连接,并返回Connection连接
            Connection conn = DriverManager.getConnection(
                    "jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
            
            //2、创建Statement对象
            Statement state = conn.createStatement();
            
            //定义sql语句
            String sql = "SELECT * FROM emp";
            
            //执行SQL查询语句,结果集由ResultSet保存
            ResultSet rs = state.executeQuery(sql);
            
            //循环输出结果集
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                System.out.println("id = " + id + ",name = " + name + ",salary = " + salary);
            }
            rs.close();//关闭SQL连接
                   conn.close();//关闭数据库连接
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}
 

 

2、使用properties的配置文件赋值

  通过修改配置文件来实现代码功能的改变,可以不用修改class文件,便于开发。

 
【格式:】
    string1 = string2     # 尾部没有分号(;)
# 表示注释

java.util.Properties;该类用于读取properties文件,并以Map集合的形式存储文件内容。

【读取文件步骤:】
1、先使用InputStream is(或其他输入流)读取文件。
2、再使用load(is) 加载文件。
3、使用getProperty(String key),通过等号左边(key)值,获取等号右边(value)值。
4、有一个线程ThreadLocal<Connection>,其内部操作的为一个Map集合,相当于<Thread, Connection>,将线程作为key。
ThreadLocal有个set方法,会将当前线程作为key,并将给定的值放入Map中,这样便于知道具体某个值是哪个线程所调用的,方便操作。

【举例:】
//定义一个连接数据库,并可以关闭数据库的类。
//DefineConnection.class

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;


/**
 * 自定义一个连接数据库的类,
 * 写在Static块中,用于从properties文件中获取值,并加载驱动。
 * 
 * 声明一个连接数据库方法,一个关闭数据库方法。
 *
 */
public class DefineConnection {
    private static String driver;  //保存驱动
    private static String url;  //保存路径
    private static String user;  //保存数据库(用户)名
    private static String password;   //保存密码(口令)
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); //用于管理不同的线程获取的连接。
    
    static {
        try {
            //实例化一个Properties类,用于读取properties文件
            Properties prop = new Properties();
            
            //通过相对路径获取文件
            InputStream is = DefineConnection.class.getClassLoader().getResourceAsStream("config.properties");
            
            //加载文件,并以类似Map的形式保存
            prop.load(is);
            
            //关闭输入流
            is.close();
            
            //从文件中将等号左边作为Key,获取等号右边的值
            driver = prop.getProperty("driver");
            url = prop.getProperty("url");
            user = prop.getProperty("user");
            password = prop.getProperty("password");
            
            //加载驱动
            Class.forName(driver);
            
             
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 用于连接数据库
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception{
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            tl.set(conn);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw e; //通知调用者,调用出错
        }
    }
    
    /**
     * 用于关闭数据库
     * @param conn
     * @throws Exception
     */
    public static void closeConnection() throws Exception{
        try {
            /*
             * 通过ThreadLocal指定的线程,获取指定的连接,并将其断开。
             * 使用ThreadLocal可以不给方法加指定参数,就能获取该值。
             * 适用于多个线程混合调用,获取具体值的时候。
             */
            Connection conn = tl.get();
            if(conn != null) {
                conn.close();
                tl.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}



//定义一个测试类:
//Test.class

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        try {
            //获取连接
            Connection conn = DefineConnection.getConnection();
            //获取Statement对象
            Statement state = conn.createStatement();
            //定义SQL语句
            String sql = "SELECT * FROM emp";
            //执行SQL语句,得结果集
            ResultSet rs = state.executeQuery(sql);
            //遍历结果集
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                System.out.println("id = " + id + ", name = " + name + ", salary = " + salary);
            }
            //关闭SQL连接
            rs.close();
            //关闭数据库连接
            DefineConnection.closeConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}