一文让你了解JDBC

JDBC简介

什么是JDBC?JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
在Java程序中访问数据库,并不是通过TCP连接数据库的,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
例如,我们在Java代码中如果要访问MySQL,那么必须编写代码操作JDBC接口。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,例如,MySQL的JDBC驱动由Oracle提供。因此,访问某个具体的数据库,我们只需要引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库,因为他们都提供了标准的JDBC驱动:
一文让你了解JDBC_第1张图片
实际上,一个MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器。

使用JDBC的好处是:

各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;

Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;

可随时替换底层数据库,访问数据库的Java代码基本不变。

JDBC连接

因为JDBC接口并不知道我们要使用哪个数据库,所以,用哪个数据库,我们就去使用哪个数据库的“实现类”,我们把某个数据库实现了JDBC接口的jar包称为JDBC驱动。

因为我们选择了MySQL 5.x作为数据库,所以我们首先得找一个MySQL的JDBC驱动。所谓JDBC驱动,其实就是一个第三方jar包,我们直接添加一个Maven依赖就可以了:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>

如果没有配置Maven环境的话,我可以把JDBC驱动的jar包下载下来,导入工程即可。
接下来开启数据库服务,确保MySQL在本机正常运行并且还需要准备一点数据。这里我们用一个脚本创建数据库和表,然后插入一些数据:

-- 创建数据库learjdbc:
DROP DATABASE IF EXISTS learnjdbc;
CREATE DATABASE learnjdbc;

-- 创建登录用户learn/口令learnpassword
CREATE USER IF NOT EXISTS learn@'%' IDENTIFIED BY 'learnpassword';
GRANT ALL PRIVILEGES ON learnjdbc.* TO learn@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

-- 创建表students:
USE learnjdbc;
CREATE TABLE students (
  id BIGINT AUTO_INCREMENT NOT NULL,
  name VARCHAR(50) NOT NULL,
  gender TINYINT(1) NOT NULL,
  grade INT NOT NULL,
  score INT NOT NULL,
  PRIMARY KEY(id)
) Engine=INNODB DEFAULT CHARSET=UTF8;

-- 插入初始数据:
INSERT INTO students (name, gender, grade, score) VALUES ('小明', 1, 1, 88);
INSERT INTO students (name, gender, grade, score) VALUES ('小红', 1, 1, 95);
INSERT INTO students (name, gender, grade, score) VALUES ('小军', 0, 1, 93);
INSERT INTO students (name, gender, grade, score) VALUES ('小白', 0, 1, 100);
INSERT INTO students (name, gender, grade, score) VALUES ('小牛', 1, 2, 96);
INSERT INTO students (name, gender, grade, score) VALUES ('小兵', 1, 2, 99);
INSERT INTO students (name, gender, grade, score) VALUES ('小强', 0, 2, 86);
INSERT INTO students (name, gender, grade, score) VALUES ('小乔', 0, 2, 79);
INSERT INTO students (name, gender, grade, score) VALUES ('小青', 1, 3, 85);
INSERT INTO students (name, gender, grade, score) VALUES ('小王', 1, 3, 90);
INSERT INTO students (name, gender, grade, score) VALUES ('小林', 0, 3, 91);
INSERT INTO students (name, gender, grade, score) VALUES ('小贝', 0, 3, 77);

SQL语法我就不在这里说了。
接下来就是连接:

public class DbUtil  {
     
	//连接数据库要使用的量
		private String dbUrl="jdbc:mysql://localhost:3306/?useUnicode=true&characterEncoding=utf-8&useSSL=true";
		private String name="root";//写上登陆数据库系统的用户名(安装数据库时候设置的用户名)
		private String password="123456";//写上登陆数据库系统的密码
		private String jdbcName="com.mysql.jdbc.Driver";//数据库驱动
		//公共的连接数据库方法
		public Connection getCon()throws Exception{
     
			Class.forName(jdbcName);
			Connection con = DriverManager.getConnection(dbUrl,name,password);
			return con;
		}
		//关闭数据库
		public void closeCon(Connection con)throws Exception{
     
			if(con!=null)
			{
     
				con.close();
			}
		}
		//测试连接成功
		public static void main(String[] args) {
     
			DbUtil db = new DbUtil();
			try {
     
				db.getCon();
				System.out.println("连接成功");
			} catch (Exception e) {
     

				e.printStackTrace();
				System.out.println("连接成功");

			}
		}

核心代码是DriverManager提供的静态方法getConnection()。DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。

因为JDBC连接是一种昂贵的资源,所以使用后要及时释放。使用try (resource)来自动释放JDBC连接是一个好方法
连接成功后就可以写SQL语言进行对应的操作了。

JDBC中的CRUD

数据库操作中,总结就四个字:增删改查,行话叫CRUD:Create,Retrieve,Update和Delete。

JDBC查询
第一步,通过Connection提供的createStatement()方法创建一个Statement对象,用于执行一个查询;

第二步,执行Statement对象提供的executeQuery(“SELECT * FROM students”)并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集;

第三步,反复调用ResultSet的next()方法并读取每一行结果。

完整查询代码如下:

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD) {
     
    try (Statement stmt = conn.createStatement()) {
     
        try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=\'M\'")) {
     
            while (rs.next()) {
     
                long id = rs.getLong(1); // 注意:索引从1开始
                long grade = rs.getLong(2);
                String name = rs.getString(3);
                String gender = rs.getString(4);
            }
        }
    }
}

注意要点:

Statment和ResultSet都是需要关闭的资源,因此嵌套使用try (resource)确保及时关闭;

rs.next()用于判断是否有下一行记录,如果有,将自动把当前行移动到下一行(一开始获得ResultSet时当前行不是第一行);

ResultSet获取列时,索引从1开始而不是0;

必须根据SELECT的列的对应位置来调用getLong(1),getString(2)这些方法,否则对应位置的数据类型不对,将报错。

使用Statement拼字符串非常容易引发SQL注入的问题,这是因为SQL参数往往是从方法参数传入的。

我们来看一个例子:假设用户登录的验证方法如下:

User login(String name, String pass) {
     
    ...
    stmt.executeQuery("SELECT * FROM user WHERE login='" + name + "' AND pass='" + pass + "'");
    ...
}

其中,参数name和pass通常都是Web页面输入后由程序接收到的。

如果用户的输入是程序期待的值,就可以拼出正确的SQL。例如:name = “bob”,pass = “1234”:

SELECT * FROM user WHERE login='bob' AND pass='1234'

但是,如果用户的输入是一个精心构造的字符串,就可以拼出意想不到的SQL,这个SQL也是正确的,但它查询的条件不是程序设计的意图。例如:name = “bob’ OR pass=”, pass = " OR pass=’":

SELECT * FROM user WHERE login='bob' OR pass=' AND pass=' OR pass=''

这个SQL语句执行的时候,根本不用判断口令是否正确,这样一来,登录就形同虚设。

要避免SQL注入攻击,一个办法是针对所有字符串参数进行转义,但是转义很麻烦,而且需要在任何使用SQL的地方增加转义代码。

还有一个办法就是使用PreparedStatement。使用PreparedStatement可以完全避免SQL注入的问题,因为PreparedStatement始终使用?作为占位符,并且把数据连同SQL本身传给数据库,这样可以保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。上述登录SQL如果用PreparedStatement可以改写如下:

User login(String name, String pass) {
     
    ...
    String sql = "SELECT * FROM user WHERE login=? AND pass=?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setObject(1, name);
    ps.setObject(2, pass);
    ...
}

所以,PreparedStatement比Statement更安全,而且更快。
使用Java对数据库进行操作时,必须使用PreparedStatement,严禁任何通过参数拼字符串的代码!
我们把上面使用Statement的代码改为使用PreparedStatement:

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD) {
     
    try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
     
        ps.setObject(1, "M"); // 注意:索引从1开始
        ps.setObject(2, 3);
        try (ResultSet rs = stmt.executeQuery()) {
     
            while (rs.next()) {
     
                long id = rs.getLong("id");
                long grade = rs.getLong("grade");
                String name = rs.getString("name");
                String gender = rs.getString("gender");
            }
        }
    }
}

使用PreparedStatement和Statement稍有不同,必须首先调用setObject()设置每个占位符?的值,最后获取的仍然是ResultSet对象。

另外注意到从结果集读取列时,使用String类型的列名比索引要易读,而且不易出错。

注意到JDBC查询的返回值总是ResultSet,即使我们写这样的聚合查询SELECT SUM(score) FROM …,也需要按结果集读取:

ResultSet rs = ...
if (rs.next()) {
     
    double sum = rs.getDouble(1);
}

JDBC插入
插入操作是INSERT,即插入一条新记录。通过JDBC进行插入,本质上也是用PreparedStatement执行一条SQL语句,不过最后执行的不是executeQuery(),而是executeUpdate()。示例代码如下:

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD) {
     
    try (PreparedStatement ps = conn.prepareStatement(
            "INSERT INTO students (id, grade, name, gender) VALUES (?,?,?,?)")) {
     
        ps.setObject(1, 999); // 注意:索引从1开始
        ps.setObject(2, 1); // grade
        ps.setObject(3, "Bob"); // name
        ps.setObject(4, "M"); // gender
        int n = ps.executeUpdate(); // 1
    }
}

设置参数与查询是一样的,有几个?占位符就必须设置对应的参数。虽然Statement也可以执行插入操作,但我们仍然要严格遵循绝不能手动拼SQL字符串的原则,以避免安全漏洞。

当成功执行executeUpdate()后,返回值是int,表示插入的记录数量。此处总是1,因为只插入了一条记录。

JDBC更新
更新操作是UPDATE语句,它可以一次更新若干列的记录。更新操作和插入操作在JDBC代码的层面上实际上没有区别,除了SQL语句不同:

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD) {
     
    try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
     
        ps.setObject(1, "Bob"); // 注意:索引从1开始
        ps.setObject(2, 999);
        int n = ps.executeUpdate(); // 返回更新的行数
    }
}

executeUpdate()返回数据库实际更新的行数。返回结果可能是正数,也可能是0(表示没有任何记录更新)

JDBC删除
删除操作是DELETE语句,它可以一次删除若干列。和更新一样,除了SQL语句不同外,JDBC代码都是相同的:

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD) {
     
    try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
     
        ps.setObject(1, 999); // 注意:索引从1开始
        int n = ps.executeUpdate(); // 删除的行数
    }
}

注意:看此文前先去了解一下SQL语句。

你可能感兴趣的:(java,数据库,mysql,java,jdbc)