JDBC 简介
JDBC (Java DataBase Connectivity, Java 数据库连接) 是使用Java语言操作关系型数据库的一套 API.
JDBC其实是SUN公司制订的一套操作数据库的标准接口. JDBC中定义了所有操作关系型数据库的规则. 由各自的数据库厂商给出实现类 (驱动jar包).
Java, JDBC和各种数据库的关系如下图:
使用JDBC的好处:
- 不需要针对不同数据库分别开发.
- 可随时替换底层数据库, 访问数据库的Java代码基本不变.
JDBC 使用的基本步骤
-
导入JDBC驱动jar包:
-
下载MySQL jar驱动包, 菜鸟教程 Java MySQL 连接。
-
在项目中, 将下载好的jar包放入项目的
lib目录中. -
然后点击鼠标右键–>Add as Library (添加为库).
-
在添加为库文件的时候,有如下三个选项:
-
Global Library: 全局有效
-
Project Library: 项目有效
-
Module Library: 模块有效
选择Global Library.
-
-
-
注册驱动:
1Class.forName("com.mysql.jdbc.Driver");MySQL提供的
Driver的静态代码块会自动执行DriverManager.registerDriver()方法来注册驱动. 所以我们只需加载Driver即可. MySQL5之后的驱动包, 可以省略注册驱动的步骤. -
获取数据库连接:
1Connection conn = DriverManager.getConnection(url, username, password);-
其中,
url,username和password都是String类型. -
url格式:1jdbc:数据库软件名称://ip地址或域名:端口/数据库名称?参数键值对1&参数键值对2...例如, 连接本地mysql中名为test的数据库:
1jdbc:mysql://127.0.0.1:3306/test本地mysql, 且端口为3306, url可简写为:
1jdbc:mysql:///数据库名称?参数键值对常用的参数键值对有:
1useSSL=false // 禁用安全连接方式, 解决警告提示 2useServerPrepStmts=true // 开启预编译(默认为false) 3serverTimezone=GMT%2B8 // 设置时区, 东八区(即GMT+8) 4serverTimezone=Asia/Shanghai // 设置时区东八区 5useUnicode=true&characterEncoding=UTF-8 // 设置字符集为UTF-8
-
-
获取执行SQL对象:
执行SQL语句需要SQL执行对象 (
Statement对象):1Statement stmt = conn.createStatement();Statement对象存在安全问题 (SQL注入等问题), 而使用PreparedStatement不仅可以提升查询速度, 而且还能防止SQL注入问题.1String sql = "...SQL语句..."; 2PreparedStatement pstmt = conn.prepareStatement(sql); -
执行SQL语句:
1int count = pstmt.executeUpdate(sql);用于执行DML, DDL语句.
或者:
1ResultSet rs = pstmt.executeQuery(sql);用于执行DQL语句.
-
处理返回结果
-
释放资源:
ResultSet、Statement和Connection对象都要<i>按照顺序</i>释放资源.1rs.close(); 2stmt.close(); 3conn.close();
大致代码如下:
1import java.sql.*;
2
3public class JDBCDemo {
4
5 public static void main(String[] args) throws Exception {
6
7 // - 接收用户输入的用户名和密码
8 String name = "...";
9 String pwd = "...";
10
11 // 1. 注册驱动(装载类,并实例化)
12 Class.forName("com.mysql.jdbc.Driver");
13
14 // 2. 获取连接
15 String url = "jdbc:mysql://127.0.0.1:3306/test" +
16 "?useServerPrepStmts=true";
17 String username = "root";
18 String password = "1234";
19 Connection conn = DriverManager.getConnection(url, username, password);
20
21 // 3. 定义SQL语句 (用?作占位符)
22 String sql = "SELECT id,username,password" +
23 " FROM tb_user" +
24 " WHERE username = ?" +
25 " AND password = ?";
26
27 // 4. 获取执行SQL的PreparedStatement对象
28 PreparedStatement pstmt = conn.prepareStatement(sql);
29 // 设置参数(?)的值 pstmt.setXxx(index, value)
30 pstmt.setString(1, name);
31 pstmt.setString(2, pwd);
32
33 // 5. 执行SQL
34 ResultSet rs = pstmt.executeQuery();
35
36 // 6. 处理结果
37 while (rs.next) {
38 /*
39 ...
40 */
41 }
42
43 // 7. 释放资源
44 rs.close();
45 pstmt.close();
46 conn.close();
47 }
48}
PreparedStatement 对象
PreparedStatement 对象可以:
- 预编译SQL语句并执行
- 预防SQL注入问题
获取 PreparedStatement需要先传入SQL语句:
1// SQL语句中的参数值,使用 ? 占位符替代
2String sql = "SELECT id,username,password" +
3 " FROM tb_user" +
4 " WHERE username = ?" +
5 " AND password = ?";
6
7// 通过Connection对象获取PreparedStatement, 并传入对应的SQL语句
8PreparedStatement pstmt = conn.prepareStatement(sql);
接着我们需要设置SQL对象中的参数值:
使用 pstmt.setXxx(index, value), 给 ? 赋值. 其中, index的值从 1开始, value的类型为 Xxx或 xxx.
例如, 给 int类型的 value赋值使用 setInt(), String类型使用 setString(). 除此之外还有 setFloat(), setDouble(), setArray(), setByte()等.
如果 prepareStatement()方法传入的是DML, DDL语句, 则使用 executeUpdate() 方法:
1int executeUpdate()
2 throws SQLException
如果该方法执行的是DML语句 (INSERT, UPDATE和 DELETE), 则返回DML语句操作的行数; 如果是DDL语句则返回 0.
需要注意, 在开发中很少使用java代码操作DDL语句.
如果 prepareStatement()方法传入的是DQL语句 (SELECT), 使用的是 executeQuery() 方法:
1ResultSet executeQuery()
2 throws SQLException
该方法返回的是DQL语句查询后的结果集.
在使用 PreparedStatement对象后, 需要使用 close()方法释放资源.
Statement 和 PreparedStatement
Statement 对象的一般用法如下:
1String sql = "UPDATE tb_user SET password = \"abc\" WHERE id = 1";
2Statement stmt = conn.createStatement();
3int count = stmt.executeUpdate(sql);
Statement的SQL语句是作为 executeUpdate()和 executeQuery()的参数传入, 而 PreparedStatement则是在创建对象就已经作为 prepareStatement()方法的参数传入.
这是因为 PreparedStatement需要预先传入SQL语句, 来起到预编译SQL语句和预防SQL注入问题.
预编译
一般情况下, java执行SQL语句的过程如下:
java程序请求数据库执行SQL语句后:
- 检查: 数据库接收指令, 检查SQL语法
- 编译: 如果SQL语句无语法错误, 则将该语句编译成可执行的函数
- 执行: 编译完成后执行SQL语句
而检查SQL和编译SQL花费的时间比执行SQL的时间还要长, 如果需要一次性执行多条SQL语句, 那会浪费大量时间和资源. 所以, PreparedStatement的出现解决了这个问题.
通过使用 PreparedStatement对象, 并且在连接数据库的 url中添加 useServerPrepStmts=true参数来开启SQL语句预编译功能. 预编译功能会将我们设置的SQL语句 (如 "SELECT id,username,password FROM tb_user WHERE username = ? AND password = ?") 预先传给数据库, 让其先完成检查和编译的工作 (先完成耗时的工作), 然后再一次性执行所有SQL语句 (这些SQL语句都是相同的, 只是占位符处设置的值不同).
SQL注入
SQL注入是指通过把SQL命令插入到Web表单提交, 或输入域名或页面请求的查询字符串, 最终达到欺骗服务器执行恶意的SQL命令.
而 PreparedStatement通过在SQL语句中使用 ?占位符, 并且使用相应的 setXxx()方法来设置值 (设置的值如果含有特殊字符, 如 " 和 ' 等, 则会进行转义), 防止了SQL注入的发生.
下面代码说明了 PreparedStatement如何防止SQL注入:
1class Demo {
2 public static void main(String[] args) {
3 // useServerPrepStmts=true开启预编译
4 String url = "jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true";
5 String username = "root";
6 String password = "n546,Lin0";
7 Connection conn = DriverManager.getConnection(url, username, password);
8
9 // - 接收用户输入的用户名和密码
10 String name = "zhangsan";
11 String pwd = "' OR '1' = '1";
12
13 // - 定义SQL(用?作占位符)
14 String sql = "SELECT id,username,password" +
15 " FROM tb_user" +
16 " WHERE username = ?" +
17 " AND password = ?";
18
19 // - 获取PreparedStatement对象
20 // - 预编译SQL,性能更高
21 // 默认关闭,在url加上参数useServerPrepStmts=true开启
22 // - 防止SQL注入
23 PreparedStatement pstmt = conn.prepareStatement(sql);
24
25 // - 设置参数(?)的值
26 // - 防注入原理:
27 // 字符串参数在setString中会被转义,
28 // 即整个参数被当成sql里面的字符串,而不是java的字符串
29 pstmt.setString(1, name);
30 // 从mysql日志文件可以发现:
31 // ' OR '1' = '1 转义成了 \' OR \'1\' = \'1
32 pstmt.setString(2, pwd);
33
34 // - 执行SQL
35 ResultSet rs = pstmt.executeQuery();
36
37 // - 判读登录是否成功
38 if (rs.next()) {
39 System.out.println("登录成功!");
40 }
41 else {
42 System.out.println("登陆失败!");
43 }
44
45 rs.close();
46 pstmt.close();
47 conn.close();
48 }
49}
下面代码演示了把SQL代码片段插入到SQL命令, 来进行免密登录:
1class LoginInject {
2 public static void main(String[] args) throws Exception {
3 String url = "jdbc:mysql:///test";
4 String username = "root";
5 String password = "1234";
6 Connection conn = DriverManager.getConnection(url, username, password);
7
8 // 接收用户输入的用户名和密码
9 String name = "abcdefg"; // 用户名随意
10 String pwd = "' OR '1' = '1"; // 密码传入SQL代码片段
11
12 String sql = "SELECT id,username,password" +
13 " FROM tb_user" +
14 " WHERE username = '" + name +
15 "' AND password = '"+ pwd + "'";
16 // 将sql语句where部分展开:
17 // WHERE username = 'abcdefg' AND password = '' OR '1' = '1'
18 // 发现where语句条件始终为真
19 System.out.println(sql);
20
21 Statement stmt = conn.createStatement();
22 ResultSet rs = stmt.executeQuery(sql);
23
24 // 判读登录是否成功
25 if (rs.next()) {
26 System.out.println("登录成功!");
27 }
28 else {
29 System.out.println("登陆失败!");
30 }
31 // 返回的是登录成功
32
33 rs.close();
34 stmt.close();
35 conn.close();
36 }
37}
ResultSet 对象
ResultSet (结果集对象) 作用: 封装了SQL查询语句的结果, 是 executeQuery()方法的返回值类型.
ResultSet对象有三个方法:
-
next():1boolean next() 2 throws SQLException每次执行时, 将光标从当前位置向前移动一行 (光标从第0行开始), 并且判断当前行是否为有效行 (返回
true则代表为有效行)。 -
getXxx():1xxx getXxx(arg) 2 throws SQLExceptionarg类型:
int: 代表列的编号 (按照SELECT语句中的查询顺序), 从1开始String: 列的名称
-
close():1void close() 2 throws SQLException释放
ResultSet对象.
下面演示了 ResultSet的使用:
1class Demo {
2 public static void main(String[] args) {
3 // ...
4
5 String sql = "SELECT id,username,password FROM tb_user";
6 Statement stmt = conn.createStatement();
7 PreparedStatement pstmt = conn.prepareStatement(sql);
8 // - 处理结果,遍历rs中的所有数据
9 // - rs.next():光标向下移动一行,并判断当前行是否有效
10 while (rs.next()) {
11 // - 获取数据 getXxx()
12 int id = rs.getInt(1);
13 // getXxx()方法可以使用列索引(从1开始)也可以使用列名
14 String usrname = rs.getString("username");
15 String passwd = rs.getString(3);
16
17 System.out.println("id: " + id);
18 System.out.println("username: " + usrname);
19 System.out.println("passwd: " + passwd);
20 System.out.println("-----------------------");
21 }
22 // - 释放资源
23 // ResultSet、Statement和Connection都要按照顺序释放资源
24 // 先释放ResultSet, 再释放Statement, 最后是Connection
25 rs.close();
26 stmt.close();
27 conn.close();
28 }
29}
操作实例
用户账号密码增删改操作.
在编写JDBC代码之前需要先完成创建数据库, 创建 pojo包并编写 User实体类的工作:
-
创建数据库并运行下方SQL代码:
1-- 删除tb_user表 2DROP TABLE IF EXISTS tb_user; 3-- 创建tb_user表 4CREATE TABLE tb_user( 5 id INT PRIMARY KEY AUTO_INCREMENT, 6 username VARCHAR(20), 7 password VARCHAR(32) 8); 9 10-- 添加数据 11INSERT INTO tb_user VALUES(NULL, 'zhangsan', '123'), (NULL, 'lisi', '234'); -
创建
pojo包, 并在包中添加User实体类:1package pojo; // pojo包存放实体类 2 3public class User { 4 5 private Integer id; 6 private String username; 7 private String password; 8 9 public Integer getId() { 10 return id; 11 } 12 13 public void setId(Integer id) { 14 this.id = id; 15 } 16 17 public String getUsername() { 18 return username; 19 } 20 21 public void setUsername(String username) { 22 this.username = username; 23 } 24 25 public String getPassword() { 26 return password; 27 } 28 29 public void setPassword(String password) { 30 this.password = password; 31 } 32 33 @Override 34 public String toString() { 35 return "Account{" + 36 "id=" + id + 37 ", username='" + username + '\'' + 38 ", password='" + password + '\'' + 39 '}'; 40 } 41}
增删改操作
JDBC数据访问层的代码放在 DAO包下:
1package dao;
2
3import pojo.User;
4
5import java.sql.*;
6
7public class UserDAO {
8
9 private static String URL = "jdbc:mysql:///test" +
10 "?useSSL=false&useServerPrepStmts=true";
11 private static String USERNAME = "root";
12 private static String PASSWORD = "1234";
13
14 /**
15 * 根据用户名和密码查询
16 * @param username
17 * @param password
18 * @return User
19 * @throws SQLException
20 */
21 public User select(String username, String password) throws SQLException {
22
23 // 参数有null值时
24 if (username == null || password == null) {
25 return null;
26 }
27
28 // 连接数据库
29 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
30
31 // 获取PreparedStatement对象, 并设置SQL语句
32 String sql = "SELECT id, username, password" +
33 " FROM tb_user" +
34 " WHERE username = ?" +
35 " AND password = ?";
36 PreparedStatement pstmt = conn.prepareStatement(sql);
37 pstmt.setString(1, username);
38 pstmt.setString(2, password);
39
40 // 获取ResultSet
41 ResultSet rs = pstmt.executeQuery();
42
43 User user = null;
44 if (rs.next()) {
45 user = new User();
46
47 Integer id = rs.getInt("id");
48 String name = rs.getString("username");
49 String pw = rs.getString("password");
50
51 user.setId(id);
52 user.setUsername(name);
53 user.setPassword(pw);
54 }
55
56 rs.close();
57 pstmt.close();
58 conn.close();
59
60 return user;
61 }
62
63 /**
64 * 根据用户名和密码添加数据
65 * @param username
66 * @param password
67 * @return boolean
68 * @throws SQLException
69 */
70 public boolean add(String username, String password) throws SQLException {
71
72 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
73
74 String sql = "INSERT INTO tb_user" +
75 " VALUE(null, ?, ?)";
76 PreparedStatement pstmt = conn.prepareStatement(sql);
77 pstmt.setString(1, username);
78 pstmt.setString(2, password);
79
80 int count = pstmt.executeUpdate();
81
82 pstmt.close();
83 conn.close();
84
85 return count > 0;
86 }
87
88 /**
89 * 根据用户名和密码删除数据
90 * @param username
91 * @param password
92 * @return boolean
93 * @throws SQLException
94 */
95 public boolean delete(String username, String password) throws SQLException {
96
97 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
98
99 String sql = "DELETE FROM tb_user" +
100 " WHERE username = ?" +
101 " AND password = ?";
102 PreparedStatement pstmt = conn.prepareStatement(sql);
103 pstmt.setString(1, username);
104 pstmt.setString(2, password);
105
106 int count = pstmt.executeUpdate();
107
108 pstmt.close();
109 conn.close();
110
111 return count > 0;
112 }
113}
评论