注意: 如果转载文章请在文章明显处表明作者,以及原文链接,谢谢!
原文链接: https://blog.csdn.net/weixin_44170221/article/details/105285457
整个JDBC体系思维导图参考此文章:JDBC知识树
使用客户端工具访问数据库,需要手工建立链接,输入用户名和密码登录,编写SQL语句,点击执行,查看操作结果(结果集或受影响行数)。
在实际开发过程中,当用户的数据发生改变时,不可能通过客户端操作执行SQL语句,因为操作量过大!无法保证效率和正确性
JDBC(Java DataBase Connectivity) Java连接数据库,可以使用Java语言连接数据库完成CRUD操作
Java中定义了访问数据库的接口,可以为多种关系型数据库提供统一的访问方式。
由数据库厂商提供驱动实现类(Driver数据库驱动)
- mysql-connector-java-5.1.X 适用于5.X版本
- mysql-connector-java-8.0.X 适用于8.X版本
JDBC 是由多个接口和类进行功能实现
类型 | 全限定名 | 简介 |
---|---|---|
class | java.sql.DriverManager | 管理多个数据库驱动类,提供了获取数据库连接的方法 |
interface | java.sql.Connection | 代表一个数据库连接(当Connection不是NULL时,表示已连接一个数据库) |
interface | java.sql.Statement | 发送SQL语句到数据库的工具 |
interface | java.sql.ResultSet | 保存SQL查询语句的结果数据(结果集) |
class | java.sql.SQLException | 处理数据库应用程序时所发生的异常 |
- 在项目下新建 lib 文件夹,用于存放 jar 文件
- 将MySQL驱动文件mysql-connector-java-5.1.25-bin.jar 复制到项目的lib文件夹中
- 选中lib文件夹 右键选择 add as library,点击OK
重点
】使用Class.forName(“com.mysql.jdbc.Driver”); 手动加载字节码文件到JVM中
Class.forName("com.mysql.jdbc.Driver");
- 通过DriverManager.getConnection(url,user,password);获得数据库连接对象
- URL:jdbc:mysql://localhost:3306/database
- user:root
- password:1234
- 注意:如果设置字符编码集需要在数据库后加?useUnicode=true&characterEncoding=utf8
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf8","root","123456");
通过Connection对象获得Statement对象,用于对数据库进行通用访问的
Statement statement = connection.createStatement();
编写SQL语句,并执行,接收执行后的结果
int result = statement.executeUpdate("update stu set student_name='Ziph',sex='男' where student_id = 'class1'");
接收并处理操作结果
if(result > 0){
System.out.println("执行成功!");
} else {
System.out.println("执行失败!");
}
遵循的是先开后关的原则,释放过程中用到的所有资源对象
statement.close();
connection.close();
综合核心六步,实现增删改
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestInsert {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动,将驱动字节码文件加载到JVM中
Class.forName("com.mysql.jdbc.Driver");
//2.连接数据库
String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8";//数据库连接地址
String user = "root";//用户名
String password = "123456";//密码
Connection connection = DriverManager.getConnection(url, user, password);
//3.获取发送SQL语句的对象
Statement statement = connection.createStatement();
//4.编写SQL语句,并执行SQL语句
String sql = "insert into user (userName, password, address, phone) values ('Ziph', '123456', '河北', '11111111111')";
int result = statement.executeUpdate(sql);
//5.处理结果
if (result > 0) {
System.out.println("添加成功!");
} else {
System.out.println("添加失败!");
}
//6.释放资源,先开后关
statement.close();
connection.close();
}
}
在执行查询SQL后,存放查询到的结果集数据
ResultSet resultSet = statement.executeQuery(sql)
ResultSet resultSet = statement.executeQuery("SELECT * FROM user");
ResultSet以表(Table)结构进行临时结果的存储,需要通过JDBC API将其中的数据进行依次获取
- 数据行指针(resultSet.next()):初始位置在第一行数据前,每调用一次boolean返回值类型的next()方法,ResultSet中指针向下移动一行,结果为true,表示当前行有数据
- resultSet.getXxx(“列名”); 根据列名获得数据
- resultSet.getXxx(整数下标); 代表根据列的编号顺序获得!从1开始
boolean next() throws SQLException;//判断resultSet结果集中下一行是否有数据(注意:返回值boolean类型)
int getInt(int columnIndex) throws SQLException;//获得当前行的第N列的int值
int getInt(String columnLabel) throws SQLException;//获得当前行columnLabel列的int值
对user表所有的数据进行遍历
import java.sql.*;
public class TestSelect {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8", "root", "Mylifes1110");
//3.获取执行SQL的对象
Statement statement = connection.createStatement();
//4.编写SQL语句并执行SQL语句
String sql = "select userId, userName, password, address, phone from user";
ResultSet resultSet = statement.executeQuery(sql);
//5.处理结果(结果集)
while (resultSet.next()) {
//判断结果集中是否有下一行
//根据列名获取当前行每一列的数据
int userId = resultSet.getInt("userId");
String userName = resultSet.getString("userName");
String password = resultSet.getString("password");
String address = resultSet.getString("address");
String phone = resultSet.getString("phone");
System.out.println(userId + "\t" + userName + "\t" + password + "\t" + address + "\t" + phone);
}
//6.释放资源
resultSet.close();
statement.close();
connection.close();
}
}
//5.处理结果(结果集)
while (resultSet.next()) {
//判断结果集中是否有下一行
//根据列的编号获取当前行每一列的数据
int userId = resultSet.getInt(1);
String userName = resultSet.getString(2);
String password = resultSet.getString(3);
String address = resultSet.getString(4);
String phone = resultSet.getString(5);
System.out.println(userId + "\t" + userName + "\t" + password + "\t" + address + "\t" + phone);
}
- java.lang.ClassNotFoundException 找不到类(类名书写错误、没有导入jar包)
- com.mysql.jdbc.exceptions.jdbc.MySQLSyntaxErrorException 与SQL语句相关的错误(表名列名书写错误、约束错误、插入的值是String类型,但是没有加单引号)建议:在客户端工具中测试sql语句后,再粘贴到代码中来
- com.mysql.jdbc.exceptions.jdbc.MySQLIntegrityConstraintViolationException: Duplicate entry ‘1’ for key ‘PRIMARY’ 原因:主键值已存在!更改要插入的主键值
- com.mysql.jdbc.exceptions.jdbc.MySQLSyntaxErrorException:Unknown column ‘password’ in
- 可能输入的值的类型不对,确定插入元素时,对应的值的类型是否争取
- 创建一张用户表 User
- id 主键、自动增长
- username 字符串类型 非空
- password 字符串类型 非空
- phone 字符串类型
- 插入2条测试语句
#创建数据库
CREATE DATABASE temp CHARACTER SET utf8;
#使用该数据库
USE temp;
#创建用户表
CREATE TABLE user (
id INT PRIMARY KEY auto_increment,
username CHARACTER(20) NOT NULL,
password CHARACTER(20) NOT NULL,
phone CHARACTER(11)
) charset = utf8;
#初始化(插入)表中数据
INSERT INTO user (username, password, phone) VALUES ('ziph', '123456', '16688889999');
INSERT INTO user (username, password, phone) VALUES ('zhangsan', '123456', '16644445555');
- 通过控制台,用户输入用户名和密码
- 用户输入的用户名和密码作为参数,编写查询SQL语句。
- 如果查询到用户,则用户存在,提示登录成功,反之,提示失败!
import java.sql.*;
import java.util.Scanner;
public class TestLogin {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String userName = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象,连接数据库
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8", "root", "123456");
//3.创建执行SQL语句的对象
Statement statement = connection.createStatement();
//4.编写SQL语句,并执行SQL语句
String sql = "select * from user where username = '" + userName + "' and password = '" + password + "'";
ResultSet resultSet = statement.executeQuery(sql);
//5.处理结果
if (resultSet.next()) {
//通过参数,查到一行数据,就提示用户的登陆成功!
System.out.println("登录成功!");
} else {
System.out.println("登录失败!");
}
//6.释放资源
resultSet.close();
statement.close();
connection.close();
}
}
当用户输入的数据中有SQL关键字或语法时,并且参与了SQL语句的编译,导致SQL语句编译后条件结果为true,一直得到正确的结果。称为SQL注入
继续使用6.2实现登录案例,用户名、密码(用户名数据库里没有,密码随便录入的)我在控制台输入的图上内容,却出现了登录成功!这是怎么回事呢?那我们再来看一下,看下图吧!
看一下高亮显示的部分,这一部分是SQL执行的语句,而userName、password是我们控制台录入的信息(也参与SQL语句的执行)。但是当你输入lalala字符串是没问题的,但是当你再入’ 的时候,SQL执行的时候会认为’ 是userName的结束。随之,录入了or后面内容1=1参与SQL执行代替了录入的密码。这里我解释一下,因为1=1,默认为true,所以该密码就被越过了!; 被认为SQL语句的结束标志,随后一个#注释了password等后面的所有内容。当resultSet.next()判断时,因为它的结果返回的就是boolean类型,上述SQL注入自然而然的返回了一个true成功的越过了账号和密码的界限,登录成功!这就是SQL注入!大家可以理解了吧!那么我们继续看以下语句吧!该语句就是在SQL注入后,被SQL执行的语句:
sql = "select * from user where username='xxx' or 1=1;#'and password ='123456'";
由于编写的SQL语句,是在用户输入数据后,整合后再编译成SQL语句。所以为了避免SQL注入的问题,使SQL语句在用户输入数据前,SQL语句已经完成编译,成为了完整的SQL语句,再进行填充数据(需要在我们控制台录入后,提前把SQL语句编译成一行长长的字符串,防止注入),想知道怎么解决,那就继续看下面的重点,记住是重点哦!
重点
】PreparedStatement接口继承了Statement接口。执行SQL语句的方法没有区别!
作用:
- 1.预编译SQL语句,效率高!
- 2.安全,避免SQL注入
- 3.可以动态的填充数据,执行多个同构的SQL语句
//1.预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setXxx(下标,值); 参数下标是从1开始,为指定占位符下标绑定值
//2.为占位符下标赋值
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
import java.sql.*;
import java.util.Scanner;
public class TestSafeLogin {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8","root", "123456");
//3.创建执行sql语句的对象
String sql = "select * from user where username = ? and password = ?";
//预编译SQL语句————提前把SQL语句编译为字符串,其中用到了转义字符防止个别符号注入SQL
PreparedStatement preparedStatement = connection.prepareStatement(sql);
/**
* 查看预编译后的SQL语句字符串
* 此查看不计为jdbc的开发步骤中
*/
System.out.println(preparedStatement);
//为占位符下标赋值
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
/**
* 查看赋值后SQL语句的字符串
* 此查看不计为jdbc的开发步骤中
*/
System.out.println(preparedStatement);
//4.执行SQL语句————此时executeQuery()不需要在传入参数
ResultSet resultSet = preparedStatement.executeQuery();
//5.处理结果
if (resultSet.next()) {
//通过参数,查到一行数据,提示用户登录成功!
System.out.println("登陆成功!");
} else {
System.out.println("登录失败!");
}
//6.释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
输入SQL注入问题的用户名和密码得出以下结果!
查看预编译后的SQL语句字符串结果: com.mysql.jdbc.JDBC4PreparedStatement@4b9af9a9: select * from user where username = ** NOT SPECIFIED ** and password = ** NOT SPECIFIED **
查看赋值后SQL语句的字符串结果: com.mysql.jdbc.JDBC4PreparedStatement@4b9af9a9: select * from user where username = ‘lalal’ or 1=1;#’ and password = ‘asdsad’
最终结果: 登录失败!
总结: 我们可以看出’lalal后面’ 被加了转义字符,而 or 1=1;#后加了SQL语句应该加的username结束标志 ’ 。
创建数据库 account
- 创建一张表 t_ccount。有以下列
- cardId:字符串,主键
- password:字符串,非空
- username:字符串,非空
- balance:小数,非空
- phone:字符串,非空
#创建数据库
create database account character set utf8;
#使用account数据库
use account;
#创建表
create table t_account
(
card_id character(20) primary key,
password character(50) not null,
username character(20) not null,
balance double(10, 2) not null,
phone character(11) not null
) character set utf8;
创建AccountSystem类,完成下列功能
- 开户:控制台输入所有的账户信息,使用PreparedStatement添加至t_account表
- 存款:输入卡号、密码、存储金额进行修改
- 取款:输入卡号、密码、取款金额
- 转账:输入卡号、密码、对方卡号、转账金额进行修改
- 查看余额: 输入卡号、密码,查询余额
- 修改密码:输入卡号、密码,再输入新密码进行修改
- 注销:输入卡号、密码,删除对应的账户信息
import java.util.Scanner;
/**
* 菜单
*/
public class TestAccount {
public static void main(String[] args) {
AccountSystem accountSystem = new AccountSystem();
Scanner scanner = new Scanner(System.in);
System.out.println("欢迎使用中国银行ATM机");
int choice = 0;
do {
System.out.println("1.开户 2.存款 3.取款 4.转账 5.修改密码 6.查看账户余额 7.注销 0.退出");
System.out.print("请选择你的操作:");
choice = scanner.nextInt();
switch (choice) {
case 1:
accountSystem.register();
break;
case 2:
accountSystem.saveMoney();
break;
case 3:
accountSystem.takeMoney();
case 4:
accountSystem.transfers();
break;
case 5:
accountSystem.updatePassword();
break;
case 6:
accountSystem.getBalance();
break;
case 7:
accountSystem.logout();
break;
case 0:
accountSystem.closeConnection();
return;
default:
break;
}
} while (choice != 0);
}
}
import java.sql.*;
import java.util.Scanner;
/**
* 要求:
*
* 开户:输入所有的账户信息,使用PreparedStatement添加至t_account表中
* 存款:输入卡号、密码、存储金额进行修改
* 取款:输入卡号、密码、取款金额
* 转账:输入卡号、密码、需要对其转账的卡号、转账金额进行修改
* 修改密码:输入卡号、密码,再输入新密码进行修改
* 查询余额:输入卡号、密码,查询出对应的卡内余额
* 注销:输入卡号、密码,删除对应的账户信息
*/
public class AccountSystem {
Scanner scanner = new Scanner(System.in);
private static Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
static {
//重复的操作,就出发一次加载即可!
try {
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8", "root", "Mylifes1110");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭数据库连接对象Connection
*
* 注意:在程序结束时关闭,如果过早关闭的话,将与数据库断开连接
* 所以我们放在退出ATM机系统时调用关闭
*/
public void closeConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//开户
public void register() {
System.out.print("请输入卡号:");
String card_id = scanner.next();
System.out.print("请输入密码:");
String password = scanner.next();
System.out.print("请输入用户名:");
String username = scanner.next();
System.out.print("请输入存储金额:");
double balance = scanner.nextDouble();
System.out.print("请输入预留手机号:");
String phone = scanner.next();
//3.创建PreparedStatement,预编译
String sql = "insert into t_account(card_id, password, username, balance, phone) values (?, ?, ?, ?, ?)";
try {
preparedStatement = connection.prepareStatement(sql);
//4.为占位符赋值
preparedStatement.setString(1, card_id);
preparedStatement.setString(2, password);
preparedStatement.setString(3, username);
preparedStatement.setDouble(4, balance);
preparedStatement.setString(5, phone);
//5.执行SQL语句
int result = preparedStatement.executeUpdate();
//6.处理结果
if (result > 0) {
System.out.println("开户成功!");
} else {
System.out.println("开户失败!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//存款
public void saveMoney() {
System.out.print("请输入卡号:");
String card_id = scanner.next();
System.out.print("请输入密码:");
String password = scanner.next();
System.out.print("请输入存储金额:");
double money = scanner.nextDouble();
if (money > 0) {
String sql = "update t_account set balance = balance + ? where card_id = ? and password = ?";
//预编译
try {
preparedStatement = connection.prepareStatement(sql);
//为占位符赋值
preparedStatement.setDouble(1, money);
preparedStatement.setString(2, card_id);
preparedStatement.setString(3, password);
//执行SQL语句
int result = preparedStatement.executeUpdate();
//处理结果
if (result > 0) {
System.out.println("存款成功!");
} else {
System.out.println("卡号或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
} else {
System.out.println("输入存储金额错误!");
}
}
//取款
public void takeMoney() {
System.out.print("请输入卡号:");
String card_id = scanner.next();
System.out.print("请输入密码:");
String password = scanner.next();
System.out.print("请输入取出金额:");
double money = scanner.nextDouble();
if (money > 0) {
String sql = "update t_account set balance = balance - ? where card_id = ? and password = ?";
//预编译
try {
preparedStatement = connection.prepareStatement(sql);
//为占位符赋值
preparedStatement.setDouble(1, money);
preparedStatement.setString(2, card_id);
preparedStatement.setString(3, password);
//执行SQL语句
int result = preparedStatement.executeUpdate();
//处理结果
if (result > 0) {
System.out.println("取款成功!");
} else {
System.out.println("卡号或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//查看余额
public void getBalance() {
System.out.print("请输入卡号:");
String card_id = scanner.next();
System.out.print("请输入密码:");
String password = scanner.next();
String sql = "select balance from t_account where card_id = ? and password = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, card_id);
preparedStatement.setString(2, password);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next(