JDBC

1.什么是JDBC?

JDBC:Java Database Connectivity的缩写,即Java数据库连接。

Java语言通过JDBC来操作数据库。

在没有JDBC这套接口之前,因为每个数据库的底层原理都是不一样的,所以每个数据库都要实现一套代码,这时候SUN公司就站出来了,写了一套JDBC的接口,各个数据库厂家,如MySQL、Oracle等等纷纷实现了这套接口,Java程序员就去调用各个数据库厂家实现的接口,想要哪个数据库,就调用哪个,而不需要管底层的实现原理。

SUN公司写了一套接口,java.sql.*;
各个数据库厂家实现这套接口,即数据库驱动,以jar包的形式存在,需要自己下载;
Java程序员通过反射获取需要的数据库对象,然后通过数据库对象来调用接口中的方法。

2.如何使用JDBC?

1.首先要注册驱动,即告诉JVM,要连接的数据库是哪套数据库;
2.获取连接,将JVM的进程和数据库的进程的通道打开;
3.获取数据库操作对象;
4.执行sql语句;
5.(如果是查询,则需要遍历处理结果集)
6.关闭资源(先判断,再从小到大的去关闭)

(1)注册驱动

在注册驱动前,需要先导入相关的jar包,jar包可以去网上下载。

在java.sql.*下有一个DriverManeger类,其中有一个方法:

static void registerDriver(Driver driver) :用于注册给定的驱动。

但是参数Driver是一个接口,所以我们需要使用其子类:com.mysql.jdbc.Driver,即com.mysql.jdbc包下的Driver类

DriverManager.registerDriver(new com.mysql.jdbc.Driver());

但是我们点进去Driver的源码可以发现:

package com.mysql.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!");
        }
    }
}

其源码已经实现了注册驱动的操作,而可以通过反射去实现只执行一个类中的静态代码:

package com.dh.jdbc;

public class JDBC01 {

    public static void main(String[] args) {
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

(2)获取数据库连接

在DriverManager类中,有一个获取连接的方法:

static Connection getConnection(String url, String user, String password) ;

其中url为统一资源定位符,由协议+ip+端口+资源名组成,即:jdbc:mysql://localhost:3306/要连接的数据库名;

user和password分别为数据库的用户名和密码。

//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/practice","root","root");

(3)获取数据库操作对象

Connection接口中有一个方法,可以用于发送sql语句:

Statement createStatement();

 //3.获得执行者对象
 Statement statement = conn.createStatement();

(4)执行sql语句

首先要书写sql语句,然后调用Statement接口中的方法去执行sql语句:

//4.执行sql
String sql = "";
//DCL
ResultSet resultSet = statement.executeQuery(sql);//返回查询结果集
//DML
int i = statement.executeUpdate(sql);//返回影响数据库中数据的条数

(5)处理结果集

当执行的语句是DQL时,就需要处理查询出来的结果集,在JDBC中,使用ResultSet接口来接收:

//5.处理结果集
while(resultSet.next()){//获取一行
    resultSet.getInt("empno");//获取一行中的某一个字段值,可以是字段名(如果有别名,则应该是别名),以及字段所在的列数,列数从1开始
}

(6)关闭资源

在上述中有提到,需要打开JVM进程和数据库进程间的通道,这是重量级的,所以必须要关闭资源。

在关闭资源前,需要先判断是否需要关闭,即判断是否为null,关闭也要按照从小到大的原则去关闭;

在finally子句中关闭:

if(resultSet != null){
    try {
        resultSet.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
if(statement != null){
    try {
        statement.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
if(conn != null){
    try {
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

3.sql注入

假设现在有一个登陆系统,用户输入用户名和密码,连接数据库进行查询,如果存在该用户,并且密码也一致的话,就登陆成功:

假设有一张用户表user,里面有两个字段:用户名字段username和密码字段password;有一条记录:用户名为admin,密码为123

我们在执行sql语句时,应该先拿到用户名和密码两个参数,假设有不法分子知道了用户的用户名,但是他不知道用户的密码,但是该不法分子又懂得sql语句,那么此时他就可以想办法了:

输入密码为:111' or '1' = '1,那么sql语句就会变为:(111可为任意字符)

select * from user where username = 'admin' and password = '111' or '1' = '1';

而上述sql语句是永远成立的。

而根本的原因就算,用户输入的信息中含有sql语句的关键字,而这些关键字还参与了sql语句的编译。

解决sql注入的问题:

Statement有一个子接口,PreparedStatement,就可以解决sql注入的问题。

PreparedStatement采用预编译的方式,先向DBMS发送一个sql语句的框架,然后在sql语句中使用占位符,传递参数:

 String sql = "delete from dept where deptno = ?";
 ps = conn.prepareStatement(sql);
 ps.setInt(1,50);//第一个占位符为参数的顺序,从1开始,第二个参数为值
//DML
 ps.executeUpdate();
//DCL
 ps.executeQuery();

Statement和PreparedStatement的对比:

  • Statement会产生sql注入的问题,PreparedStatement不会产生sql注入的问题;
  • PreparedStatement效率更高,因为sql语句的框架是不变的,不必每次都重新编译sql语句,而Statement可能需要重新编译;
  • PreparedStatement会做类型安全检查,setXXX,而Statement不会做类型安全检查,直到执行程序才知道类型是否有错误。

大多数的情况下都是选择PreparedStatement,但是也有一些需求是需要sql注入的,此时就需要使用Statement了。

如:实现升序和降序的功能

String sql = "select * from emp order by empno ?";

如果此时使用PreparedStatement的话,就需要setString(1,"desc"),那么sql语句就会变成:

String sql = "select * from emp order by empno "desc"";

就有sql语法错误了,此时就必须使用Statement了:

String sql = "select * from emp order by empno" + "参数";

传递一个字符串类型的参数,如"desc",就会变成字符串的拼接了,就是正确的sql语句了:

String sql = "select * from emp order by empno desc";

4.JDBC实现事务

在JDBC中,事务是默认开启的,所以我们需要先禁用自动提交,Connection接口中包含了对事务的操作:

 conn.setAutoCommit(false);//关闭自动提交
 conn.commit();//提交数据(在try的最后)
 conn.rollback();//回滚数据(在catch中)

5.JDBC封装成工具类

可以发现,上述中有许多的操作是重复的,像注册驱动,获取数据库连接,以及关闭资源,并且其中的一些参数我们是可以放在配置文件中的,所以我们可以将这些操作封装起来:

配置文件:jdbc.properties

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/practice
username = root
password = root

JDBCUtils

package com.dh.util;

import java.sql.*;
import java.util.ResourceBundle;

public class JDBCUtils {

    private JDBCUtils() {
    }

    //注册驱动
    static {
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //获取连接
    public static Connection getConn() throws SQLException {
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String url = bundle.getString("url");
        String username = bundle.getString("username");
        String password = bundle.getString("password");
        return DriverManager.getConnection(url, username, password);
    }

    //释放资源
    public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

测试

package com.dh.jdbc;

import com.dh.util.JDBCUtils;

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

public class JDBCTest05 {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //调用JDBCUtils获取连接
            conn = JDBCUtils.getConn();
            conn.setAutoCommit(false);
            String sql = "update count set money = money - 10000 where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,1);
            ps.executeUpdate();

            int i = 1/0;

            String sql2 = "update count set money = money + 10000 where id = ?";
            ps = conn.prepareStatement(sql2);
            ps.setInt(1,2);
            ps.executeUpdate();

            conn.commit();

            String sql3 = "select * from count";
            ps = conn.prepareStatement(sql3);
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getInt("id"));
                System.out.println(rs.getDouble("money"));
            }
        } catch (SQLException e) {
             if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
             //调用JDBCUtils释放资源
            JDBCUtils.close(conn,ps,rs);
        }
    }
}
posted @ 2021-03-09 22:18  deng-hui  阅读(137)  评论(0编辑  收藏  举报