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.
-
-
-
注册驱动:
Class.forName("com.mysql.jdbc.Driver");
MySQL提供的
Driver
的静态代码块会自动执行DriverManager.registerDriver()
方法来注册驱动. 所以我们只需加载Driver
即可. MySQL5之后的驱动包, 可以省略注册驱动的步骤. -
获取数据库连接:
Connection conn = DriverManager.getConnection(url, username, password);
-
其中,
url
,username
和password
都是String
类型. -
url
格式:jdbc:数据库软件名称://ip地址或域名:端口/数据库名称?参数键值对1&参数键值对2...
例如, 连接本地mysql中名为test的数据库:
jdbc:mysql://127.0.0.1:3306/test
本地mysql, 且端口为3306, url可简写为:
jdbc:mysql:///数据库名称?参数键值对
常用的参数键值对有:
useSSL=false // 禁用安全连接方式, 解决警告提示 useServerPrepStmts=true // 开启预编译(默认为false) serverTimezone=GMT%2B8 // 设置时区, 东八区(即GMT+8) serverTimezone=Asia/Shanghai // 设置时区东八区 useUnicode=true&characterEncoding=UTF-8 // 设置字符集为UTF-8
-
-
获取执行SQL对象:
执行SQL语句需要SQL执行对象 (
Statement
对象):Statement stmt = conn.createStatement();
Statement
对象存在安全问题 (SQL注入等问题), 而使用PreparedStatement
不仅可以提升查询速度, 而且还能防止SQL注入问题.String sql = "...SQL语句..."; PreparedStatement pstmt = conn.prepareStatement(sql);
-
执行SQL语句:
int count = pstmt.executeUpdate(sql);
用于执行DML, DDL语句.
或者:
ResultSet rs = pstmt.executeQuery(sql);
用于执行DQL语句.
-
处理返回结果
-
释放资源:
ResultSet
、Statement
和Connection
对象都要<i>
按照顺序</i>
释放资源.rs.close(); stmt.close(); conn.close();
大致代码如下:
import java.sql.*;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
// - 接收用户输入的用户名和密码
String name = "...";
String pwd = "...";
// 1. 注册驱动(装载类,并实例化)
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test" +
"?useServerPrepStmts=true";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 3. 定义SQL语句 (用?作占位符)
String sql = "SELECT id,username,password" +
" FROM tb_user" +
" WHERE username = ?" +
" AND password = ?";
// 4. 获取执行SQL的PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数(?)的值 pstmt.setXxx(index, value)
pstmt.setString(1, name);
pstmt.setString(2, pwd);
// 5. 执行SQL
ResultSet rs = pstmt.executeQuery();
// 6. 处理结果
while (rs.next) {
/*
...
*/
}
// 7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
}
PreparedStatement 对象
PreparedStatement 对象可以:
- 预编译SQL语句并执行
- 预防SQL注入问题
获取 PreparedStatement
需要先传入SQL语句:
// SQL语句中的参数值,使用 ? 占位符替代
String sql = "SELECT id,username,password" +
" FROM tb_user" +
" WHERE username = ?" +
" AND password = ?";
// 通过Connection对象获取PreparedStatement, 并传入对应的SQL语句
PreparedStatement 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()
方法:
int executeUpdate()
throws SQLException
如果该方法执行的是DML语句 (INSERT
, UPDATE
和 DELETE
), 则返回DML语句操作的行数; 如果是DDL语句则返回 0.
需要注意, 在开发中很少使用java代码操作DDL语句.
如果 prepareStatement()
方法传入的是DQL语句 (SELECT
), 使用的是 executeQuery()
方法:
ResultSet executeQuery()
throws SQLException
该方法返回的是DQL语句查询后的结果集.
在使用 PreparedStatement
对象后, 需要使用 close()
方法释放资源.
Statement 和 PreparedStatement
Statement
对象的一般用法如下:
String sql = "UPDATE tb_user SET password = \"abc\" WHERE id = 1";
Statement stmt = conn.createStatement();
int 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注入:
class Demo {
public static void main(String[] args) {
// useServerPrepStmts=true开启预编译
String url = "jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true";
String username = "root";
String password = "n546,Lin0";
Connection conn = DriverManager.getConnection(url, username, password);
// - 接收用户输入的用户名和密码
String name = "zhangsan";
String pwd = "' OR '1' = '1";
// - 定义SQL(用?作占位符)
String sql = "SELECT id,username,password" +
" FROM tb_user" +
" WHERE username = ?" +
" AND password = ?";
// - 获取PreparedStatement对象
// - 预编译SQL,性能更高
// 默认关闭,在url加上参数useServerPrepStmts=true开启
// - 防止SQL注入
PreparedStatement pstmt = conn.prepareStatement(sql);
// - 设置参数(?)的值
// - 防注入原理:
// 字符串参数在setString中会被转义,
// 即整个参数被当成sql里面的字符串,而不是java的字符串
pstmt.setString(1, name);
// 从mysql日志文件可以发现:
// ' OR '1' = '1 转义成了 \' OR \'1\' = \'1
pstmt.setString(2, pwd);
// - 执行SQL
ResultSet rs = pstmt.executeQuery();
// - 判读登录是否成功
if (rs.next()) {
System.out.println("登录成功!");
}
else {
System.out.println("登陆失败!");
}
rs.close();
pstmt.close();
conn.close();
}
}
下面代码演示了把SQL代码片段插入到SQL命令, 来进行免密登录:
class LoginInject {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql:///test";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入的用户名和密码
String name = "abcdefg"; // 用户名随意
String pwd = "' OR '1' = '1"; // 密码传入SQL代码片段
String sql = "SELECT id,username,password" +
" FROM tb_user" +
" WHERE username = '" + name +
"' AND password = '"+ pwd + "'";
// 将sql语句where部分展开:
// WHERE username = 'abcdefg' AND password = '' OR '1' = '1'
// 发现where语句条件始终为真
System.out.println(sql);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 判读登录是否成功
if (rs.next()) {
System.out.println("登录成功!");
}
else {
System.out.println("登陆失败!");
}
// 返回的是登录成功
rs.close();
stmt.close();
conn.close();
}
}
ResultSet 对象
ResultSet
(结果集对象) 作用: 封装了SQL查询语句的结果, 是 executeQuery()
方法的返回值类型.
ResultSet
对象有三个方法:
-
next()
:boolean next() throws SQLException
每次执行时, 将光标从当前位置向前移动一行 (光标从第0行开始), 并且判断当前行是否为有效行 (返回
true
则代表为有效行)。 -
getXxx()
:xxx getXxx(arg) throws SQLException
arg类型:
int
: 代表列的编号 (按照SELECT
语句中的查询顺序), 从1开始String
: 列的名称
-
close()
:void close() throws SQLException
释放
ResultSet
对象.
下面演示了 ResultSet
的使用:
class Demo {
public static void main(String[] args) {
// ...
String sql = "SELECT id,username,password FROM tb_user";
Statement stmt = conn.createStatement();
PreparedStatement pstmt = conn.prepareStatement(sql);
// - 处理结果,遍历rs中的所有数据
// - rs.next():光标向下移动一行,并判断当前行是否有效
while (rs.next()) {
// - 获取数据 getXxx()
int id = rs.getInt(1);
// getXxx()方法可以使用列索引(从1开始)也可以使用列名
String usrname = rs.getString("username");
String passwd = rs.getString(3);
System.out.println("id: " + id);
System.out.println("username: " + usrname);
System.out.println("passwd: " + passwd);
System.out.println("-----------------------");
}
// - 释放资源
// ResultSet、Statement和Connection都要按照顺序释放资源
// 先释放ResultSet, 再释放Statement, 最后是Connection
rs.close();
stmt.close();
conn.close();
}
}
操作实例
用户账号密码增删改操作.
在编写JDBC代码之前需要先完成创建数据库, 创建 pojo
包并编写 User
实体类的工作:
-
创建数据库并运行下方SQL代码:
-- 删除tb_user表 DROP TABLE IF EXISTS tb_user; -- 创建tb_user表 CREATE TABLE tb_user( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20), password VARCHAR(32) ); -- 添加数据 INSERT INTO tb_user VALUES(NULL, 'zhangsan', '123'), (NULL, 'lisi', '234');
-
创建
pojo
包, 并在包中添加User
实体类:package pojo; // pojo包存放实体类 public class User { private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Account{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
增删改操作
JDBC数据访问层的代码放在 DAO
包下:
package dao;
import pojo.User;
import java.sql.*;
public class UserDAO {
private static String URL = "jdbc:mysql:///test" +
"?useSSL=false&useServerPrepStmts=true";
private static String USERNAME = "root";
private static String PASSWORD = "1234";
/**
* 根据用户名和密码查询
* @param username
* @param password
* @return User
* @throws SQLException
*/
public User select(String username, String password) throws SQLException {
// 参数有null值时
if (username == null || password == null) {
return null;
}
// 连接数据库
Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取PreparedStatement对象, 并设置SQL语句
String sql = "SELECT id, username, password" +
" FROM tb_user" +
" WHERE username = ?" +
" AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
// 获取ResultSet
ResultSet rs = pstmt.executeQuery();
User user = null;
if (rs.next()) {
user = new User();
Integer id = rs.getInt("id");
String name = rs.getString("username");
String pw = rs.getString("password");
user.setId(id);
user.setUsername(name);
user.setPassword(pw);
}
rs.close();
pstmt.close();
conn.close();
return user;
}
/**
* 根据用户名和密码添加数据
* @param username
* @param password
* @return boolean
* @throws SQLException
*/
public boolean add(String username, String password) throws SQLException {
Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
String sql = "INSERT INTO tb_user" +
" VALUE(null, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
int count = pstmt.executeUpdate();
pstmt.close();
conn.close();
return count > 0;
}
/**
* 根据用户名和密码删除数据
* @param username
* @param password
* @return boolean
* @throws SQLException
*/
public boolean delete(String username, String password) throws SQLException {
Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
String sql = "DELETE FROM tb_user" +
" WHERE username = ?" +
" AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
int count = pstmt.executeUpdate();
pstmt.close();
conn.close();
return count > 0;
}
}
评论