JDBC英文名为:Java Data Base Connectivity(Java数据库连接),是JAVA和数据库之间独立于数据库的链接标准的JAVA API。是Java与数据库的连接的桥梁或者插件,用Java代码就能操作数据库的增删改查、存储过程、事务等。

在JDK中对应的包为 java.sql。里面就定义了大量的接口,不同类型的数据库,都可以通过实现此接口,编写适用于自己数据库的实现类。而不同的数据库厂商实现的这套标准,我们称为数据库驱动

使用 JDBC 连接数据库

这里使用 mysql 安装

测试一下看是否成功:

一些链接错误问题解决

无法找到 driver

是因为没有导入 mysql-connector-java 包,去 https://static.runoob.com/download/mysql-connector-java-8.0.16.jar 下一个然后拖进 idea 项目目录后, 对它右键,最下面有个 ADD 导入即可。

server time zone value

Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value '�й���׼ʱ��' is

是时区设置不对,在 URL 后面加入这段字符串就行:

?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC

DriverManager

位于 java.sql.DriverManager, 是用来管理数据库驱动的。

可以通过调用getConnection()来进行数据库的链接:

public static void main(String[] args) throws SQLException {  
    try (Connection connection = DriverManager.getConnection("localhost","root", "he77495161");  
         Statement statement = connection.createStatement()) {  
        ResultSet set = statement.executeQuery("select * from table");  
        while(set.next()) {  
            System.out.println(set.getType());  
        }  
    }catch (SQLException e) {  
        e.printStackTrace();  
    }  
}

方法原型:

@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()));   //内部有实现
}

添加日志打印

执行的数据库操作日志会在控制台实时打印。

static {
    DriverManager.setLogWriter(new PrintWriter(System.out));   //这里直接设定为控制台输出
}

Connection

Connection 是数据库的连接对象, 可以通过连接对象来创建一个Statement用于执行SQL语句。

Statement

之前使用了executeQuery()方法来执行select语句,此方法返回给我们一个ResultSet对象,查询得到的数据,就存放在ResultSet中。

Statement除了执行这样的DQL语句外,我们还可以使用executeUpdate()方法来执行一个DML或是DDL语句,它会返回一个int类型,表示执行后受影响的行数,可以通过它来判断DML语句是否执行成功。

也可以通过excute()来执行任意的SQL语句,它会返回一个boolean来表示执行结果是一个ResultSet还是一个int,我们可以通过使用getResultSet()或是getUpdateCount()来获取。

上述都是执行单个语句,通过 addBatchexecuteBatch 方法可以实现一次请求处理多条SQL语句,需要预先调用 statement.addBatch() 来添加一条一条语句,最后调用 executeBatch() 来批量执行。

执行 DML(操作) 操作

插入一行数据

比如在 Student(Sno, Sname, Sage) 表插入一行 (21164291, “李希”, 20):

int i = statement.executeUpdate("insert into Student VALUES (21164291, \"李希\", 20)");  
System.out.println("生效了 " + i + " 行。");

删除一行数据

int i = statement.executeUpdate("delete from Student WHERE Sno = 211164295");  
System.out.println("生效了 " + i + " 行。");

修改数据

nt i = statement.executeUpdate("update Student set Sname = '卢本伟' where Sno = 211164296");  
System.out.println("生效了 " + i + " 行。");

执行 DQL(查询) 操作

通过 statement.execQuery() 可以返回一个 ReasultSet 对象,其内部组织结构类似于:

而我们可以通过调用 .next() 方法来让当前指针指向下一行。

访问当前行的某一列可以用 .getXXX() 方法获得对应的数据类型数据,也可以填对应的列名来获取。

ResultSet res = statement.executeQuery("SELECT * FROM Student");  
while(res.next()) {  
    System.out.println(res.getInt(1) + " " +  res.getString(2) + " " +  res.getInt(3));  
}

将查询结果读取到对象

手动调用构造方法

public static ArrayList<Student> StudentFactory() {  
    ArrayList<Student> resArr = new ArrayList<>();  
  
    try(Connection connection = DriverManager.getConnection(url, user, password);  
        Statement statement = connection.createStatement();) {  
  
        ResultSet res = statement.executeQuery("Select * FROM Student");  
        while(res.next()) {  
            Student student = new Student(res.getInt(1), res.getString(2), res.getInt(3));  
            resArr.add(student);  
        }  
    }catch (SQLException e) {  
        e.printStackTrace();  
    }  
    if(resArr.size() >= 1)  
        return resArr;  
    return null;}

使用反射特性

使用反射的好处是,无论什么类型都可以通过我们的方法来进行实体类型映射。

public static <T> T convert(ResultSet set, Class<T> clazz) {  
    try {  
        Constructor<T> constructor = clazz.getConstructor(clazz.getConstructors()[0].getParameterTypes());  
        // 默认获取第一个构造方法  
        Class<?>[] param = constructor.getParameterTypes(); // 获取参数列表  
        Object[] object = new Object[param.length]; // 存放参数  
        for (int i = 0; i  < param.length; i++) {  
            object[i] = set.getObject(i + 1);  
            if(object[i].getClass() != param[i])  
                throw new SQLException("错误的类型转换:" + object[i].getClass() + "->" +param[i]);  
        }  
        return constructor.newInstance(object);  
    } catch (ReflectiveOperationException | SQLException e) {  
        e.printStackTrace();  
        return null;    }  
}

后面会学到 Mybatis 持久化数据库,它也是基于反射特征,深度封装JDBC,直接从数据库中读取一个对象。

应用:模拟用户登录

一种方案是获取到用户输入的账号密码后执行SQLQuery去数据库中查询:

public static void main(String[] args) {  
    Scanner sc = new Scanner(System.in);  
    System.out.print("输入账号:");  
    String username = sc.nextLine();  
    System.out.print("输入密码:");  
    String passwd = sc.nextLine();  
  
    try (Connection connection = DriverManager.getConnection(url, user, password);  
         Statement statemente = connection.createStatement();) {  
  
        ResultSet res = statemente.executeQuery("SELECT USERNAME FROM Account WHERE USERNAME = '" + username + "' AND PASSWORD = '" + passwd + "';");  
        boolean flag = true;  
        while(res.next()) {  
            System.out.println(res.getString(1) + " 登录成功! ");  
            flag = false;  
        }  
        if(flag) System.out.println("登录失败! 请检查你的用户名和密码。");  
  
    } catch (SQLException e) {  
        e.printStackTrace();  
    }  
}

但这种方式很容易被利用SQL注入漏洞,通过密码输入 1111' or 1=1; -- 就可以实现随意登录。

JAVA 对此提供了一种更安全的 SQL 查询函数 PreparedStatement

PreparedStatement

PreparedStatement 是增加了安全性的 Statement,通过在声明时预编译要用的 SQL 命令并在后续加入参数来避免SQL注入漏洞。

try (Connection connection = DriverManager.getConnection(url, user, password);  
     PreparedStatement statemente = connection.prepareStatement("SELECT * FROM Account WHERE username = ? AND password = ?;");) {  
  // 用 ? 来占位
    statemente.setString(1, username);  
    statemente.setString(2, passwd);  
  
    ResultSet res = statemente.executeQuery();  
    boolean flag = true;  
    while(res.next()) {  
        System.out.println(res.getString(1) + " 登录成功! ");  
        flag = false;  
    }  
    if(flag) System.out.println("登录失败! 请检查你的用户名和密码。");  
  
} catch (SQLException e) {  
    e.printStackTrace();  
}

通过将输入的参数用 '' 括起来,并且将里面的特殊字符自动执行转义,从而避免SQL注入。