JDBC概述
客户端操作数据库的方式
- 方式一:使用第三方客户端来访问MySQL:SQLyog
- 方式二:使用命令窗口
什么是JDBC
JDBC(Java Data Base Connectivity)是Java访问数据库的标准规范,是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。是Java访问数据库的标准规范。
JDBC原理
JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库的连接,从而不能操作数据库。每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。
总结:
JDBC就是一套操作关系型数据库的规则(接口)
数据库厂商需要实现这套接口,并且提供数据库驱动jar包
我们去使用这套接口,真正执行的是对应的驱动包中的实现类
JDBC开发
API使用:1.注册驱动
- JDBC规范定义驱动接口:java.sql.Driver
- Mysql驱动包提供了实现类:com.mysql.jdbc.Driver
加载注册驱动的方式 | 描述 |
---|---|
Class.forName(数据库驱动实现类) | 加载和注册数据库驱动,数据库驱动由数据库厂商MySQL提供 “com.mysql.jdbc.Driver” |
//Driver类是由MySQL驱动包提供的一个实现类 它实现了java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
//静态代码块 随着类的加载而加载 只加载一次
static {
try {
//DriverManager类就是驱动管理类 registerDriver()方法就是用来注册驱动
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
从JDBC3开始,目前已经普遍使用的版本:可以不用注册驱动而直接使用。
API使用:2.获取连接
- Connection接口,代表一个连接对象,具体的实现类由数据库的厂商实现
- 使用DriverManager类的静态方法:getConnection可以获取数据库的连接
获取连接的静态方法 | 说明 |
---|---|
Connection getConnection(String url,String user,String password) | 通过连接字符串和用户名,密码来获取数据库连接对象 |
- getConnection方法的3个连接参数说明
连接参数 | 说明 |
---|---|
user | 登录用户名 |
password | 登录密码 |
url | mysql URL的格式 jdbc:mysql://localhost:3306/db4 |
- 对URL的详细说明
jdbc:mysql://localhost:3306/db4 ? 参数名 = 参数值
jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔。
- 第一部分是协议jdbc,这是固定的
- 第二部分是子协议,就是数据库名称,连接mysql数据库,第二部分当然就是mysql了
- 第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求,mysql的第三部分分别由数据库服务器的IP地址(localhost)、端口号(3306)以及要使用的数据库名称 组成
API使用:3.获取语句执行平台
- 通过Connection的createStatement方法获取sql语句执行对象
Connection接口中的方法 | 说明 |
---|---|
Statement createStatement() | 创建SQL语句执行对象 |
- Statement:代表一条语句对象,用于发送SQL语句给服务器,用于执行静态SQL语句并返回它所生成结果的对象
Statement类 常用方法 | 说明 |
---|---|
int executeUpdate(String sql) ; | 执行insert update delete语句,返回int类型,代表受影响的行数 |
ResultSet executeQuery(String sql) | 执行select语句,返回ResultSet结果集对象 |
代码示例:
package com;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class demo01 {
public static void main(String[] args) throws Exception {
//1.注册驱动(可以省略)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接Connection连接对象
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url, "root", "123456");
//打印连接对象 com.mysql.jdbc.JDBC4Connection@2d6e8792
System.out.println(con);
//3.获取语句执行平台statement
Statement statement = con.createStatement();
// 3.1通过statement对象的executeUpdate 方法创建一张表
String sql = "create table test(id int,name varchar(20),age int);";
int i = statement.executeUpdate(sql);//返回值是int类型,表示受影响的行数
System.out.println(i);
// 4.关闭流
statement.close();
con.close();
}
}
API使用:4.处理结果集
- 只有在进行查询操作的时候,才会处理结果集
ResultSet接口
- 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录
ResultSet接口方法 | 说明 |
---|---|
boolean next() | 1)游标向下一行 2)返回boolean类型,如果还有下一条记录,返回true,否则返回false |
xxx getXxx(String or int) | 1)通过列名,参数是String类型,返回不同的类型 2)通过列号,参数是整数。从1开始,返回不同的类型 |
xxx getXxx(String or int) 重载方法 参数可以是String 或者 int
通过列号获取,参数是整数,根据获取列的数据类型,选择对应的方法 getInt(1),getString(2)…
通过列名获取,参数是String类型的字段名称,getInt(“id”) getString(“name”)…
package com;
import com.mysql.jdbc.Driver;
import java.sql.*;
public class JDBCDemo02 {
public static void main(String[] args) throws Exception {
//1.注册驱动(可以省略)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接Connection连接对象
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url, "root", "123456");
//打印连接对象 com.mysql.jdbc.JDBC4Connection@2d6e8792
System.out.println(con);
//3.获取语句执行平台statement
Statement statement = con.createStatement();
//4.执行查询操作 使用executeQuery()
String sql = "select * from jdbc_user;" ;
//resultSet 是结果集对象
ResultSet resultSet = statement.executeQuery(sql);
//
// //处理结果集对象 resultSet
// boolean next = resultSet.next();
// System.out.println(next);
//获取id
// int id = resultSet.getInt("id");
// System.out.println(id);
// int id = resultSet.getInt(1);
// System.out.println("通过列号获取id" + id);
//通过while循环,遍历获取resultSet中的数据
while (resultSet.next()){
//获取id
int id = resultSet.getInt("id");
//获取姓名
String username = resultSet.getString("username");
//获取密码
String password = resultSet.getString("password");
//获取生日
Date birthday = resultSet.getDate("birthday");
System.out.println(id + " : " + username + " : " + password + " : " + birthday );
}
//5.关闭流
resultSet.close();
statement.close();
con.close();
}
}
API使用:5.释放资源
- 需要释放的对象:ResultSet结果集,Statement语句,Connection连接
- 释放原则:先开的后关,后开的先关。ResultSet ==> Statement ==> Connection
- 放在哪个代码块中:finally块
与IO流一样,使用后的东西都需要关闭! 关闭的顺序是先开后关,先得到的后关闭,后得到的先关闭
步骤总结
- 注册驱动(可以省略)
- 获取连接
- 获取Statement对象
- 处理结果集(只在查询时处理)
- 释放资源
JDBC实现增删改查
JDBC工具类
- 什么时候自己创建工具类
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用
“获取数据库连接”操作,将在以后的增删改查所有功能中农都存在,可以封装工具类JDBCUtils,提供获取连接对象的方法,从而达到代码的重复利用
- 工具类包含的内容
1.可以把几个字符串定义成常量:用户名,密码,URL,驱动类
2.得到数据库的连接:getConnection()
3.关闭所有打开的资源
package com.utils;
import java.sql.*;
public class JDBCUtils {
//1.将连接信息定义为 字符串常量
public static final String DRIVERNAME = "com.mysql.jdbc.Driver" ;
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8" ;
public static final String USER = "root" ;
public static final String PASSWORD = "123456" ;
static {
try {
Class.forName(DRIVERNAME) ;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.获取连接的静态方法
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL,USER,PASSWORD);
return connection ;
} catch (SQLException e) {
e.printStackTrace();
return null ;
}
}
//4.关闭资源的方法
public static void close(Connection con , Statement statement, ResultSet resultSet){
if (con != null && statement != null){
try {
statement.close();
con.close();
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
DML操作
插入记录
解决插入中文乱码问题
jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
characterEncoding=UTF-8 指定字符的编码,解码格式
package com.jdbc01;
import com.utils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBC01 {
/**
* 插入数据
*/
@Test
public void testInsert() throws SQLException {
// 1.通过JDBCUtils工具类 获取连接
Connection con = JDBCUtils.getConnection();
// 2.获取Statement对象
Statement statement = con.createStatement();
// 2.1编写SQL
String sql = "insert into jdbc_user values(null,'张百万','123','2020/11/11')" ;
// 2.2执行sql
int i = statement.executeUpdate(sql);
System.out.println(i);
// 3.关闭流
JDBCUtils.close(con,statement);
}
/**
* 更新操作 根据Id,修改用户表
*/
@Test
public void testUpdate() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "update jdbc_user set username = 'admin3' where id = '1'";
statement.executeUpdate(sql);
JDBCUtils.close(connection,statement);
}
/**
* 删除操作
* 删除id为1和2 的数据
*/
@Test
public void testDelete() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "delete from jdbc_user where id in(1,2)";
statement.executeUpdate(sql) ;
JDBCUtils.close(connection,statement);
}
}
DQL 操作
查询姓名为张百万的一条记录
package com.jdbc01;
import com.utils.JDBCUtils;
import java.sql.*;
public class JDBC02 {
// 查询姓名为张百万的一条记录
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection connection = JDBCUtils.getConnection();
// 2.创建Statement对象
Statement statement = connection.createStatement();
// 3.编写Sql
String sql = "select * from jdbc_user where username = '张百万'";
ResultSet resultSet = statement.executeQuery(sql);
// 4.处理结果集
while (resultSet.next()){
// 通过列名方式获取
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
Date birthday = resultSet.getDate("birthday");
System.out.println(id + " : " + username + " : " + password + " : " + birthday);
}
// 5.关闭流
JDBCUtils.close(connection,statement,resultSet);
}
}
SQL注入问题
SQL注入演示
- 向jdbc_user表中插入两条数据
INSERT INTO jdbc_user VALUES(null,'jack','123456','2020/2/24');
INSERT into jdbc_user VALUES(null,'tom','123456','2020/2/24');
- SQL注入演示
# SQL注入演示
----填写一个错误的密码
select * from jdbc_user where username='tom' and password='123' or '1' = '1' ;
如果这是一个登陆操作,那么用户就登录成功了。显然这不是我们想要看到的结果。
sql注入案例:用户登录
- 需求
用户在控制台上输入用户名和密码,然后使用Statement字符串拼接的方式实现用户的登录 - 步骤
1)得到用户从控制台上输入的用户名和密码来查询数据库
2)写一个登录的方法
a)通过工具类得到连接
b)创建语句对象,使用拼接字符串的方式生成SQL语句
c)查询数据库,如果有记录则表示登录成功,否则登录失败
d)释放资源
sql注入方式:'123' or '1' = '1'
代码示例:
package com.JDBC03;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class TestLogin01 {
/**
* 用户登录案例
* @param args
*/
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection connection = JDBCUtils.getConnection();
// 2.获取statement对象
Statement statement = connection.createStatement();
// 3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
// 4.拼接SQL语句
String sql = "select * from jdbc_user where username = '" + name + "'and password = '" + password + "'";
System.out.println(sql);
// 5.执行查询,获取结果集对象
ResultSet resultSet = statement.executeQuery(sql) ;
// 6.处理结果集
if (resultSet.next()){
System.out.println("登录成功!欢迎您:" + name);
}else {
System.out.println("登录失败!");
}
// 7.关闭流
JDBCUtils.close(connection,statement,resultSet);
}
}
问题分析
1)什么是SQL注入
我们让用户输入的密码和SQL语句进行字符串拼接,用户输入的内容作为SQL语法的一部门,改变了原有SQL真正的意义,以上问题成为SQL注入
2)如何实现注入
- 根据用户输入的数据,拼接处的字符串
select * from jdbc_user where username = 'abc' and password = 'abc' or '1'='1'
name = 'abc' and password = 'abc' 为假'1'='1' 真
相当于select * from jdbc_user where true = true; 查询了所有记录
预处理对象
PreparedStatement 接口介绍
- PreparedStatement 是Statement接口的子接口,继承于父接口中所有的方法。它是一个预编译的SQL语句对象。
- 预编译:是指SQL语句被预编译,并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句
PreparedStatement 特点
- 因为有预先编译的功能,提高SQL的执行效率
- 可以有效的方式SQL注入的问题,安全性更高
获取PreparedStatement 对象
- 通过Connection创建PreparedStatement对象
Connection 接口中的方法 | 说明 |
---|---|
PreparedStatement prepareStatement(String sql) | 指定预编译的SQL语句, SQL语句中使用占位符?创建一个语句对象 |
PreparedStatement 接口常用方法
常用方法 | 说明 |
---|---|
int executeUpdate(); | 执行insert update delete 语句 |
ResultSet executeQuery(); | 执行select语句,返回结果集对象Result |
package com;
import com.utils.JDBCUtils;
import java.sql.*;
import java.util.Scanner;
public class TestLogin02 {
/**
* SQL注入:
* 用户输入的用户名和密码,与我们编写的SQL进行了拼接,用户输入的内容成为了SQL语法的一部分,
* 用户会利用这个漏洞,输入一些其他的字符串,改变SQL原有的意思
*
* 如何解决:
* 要解决SQL注入 就不能让用户输入的数据和我们的SQL进行直接的拼接
*
* 预处理对象 PreparedStatement 它是Statement 接口的子接口
*
*/
public static void main(String[] args) throws SQLException {
// 1.获取连接
Connection connection = JDBCUtils.getConnection();
// 2.获取prepareStatement对象
// 使用?占位符的方式来设置参数
String sql = "select * from jdbc_user where username = ? and password = ?";
PreparedStatement ps = connection.prepareStatement(sql);
// 3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
// 4.设置参数 使用setXXX(占位符的位置(整数),要设置的值)方法设置占位符的参数
ps.setString(1,name);
ps.setString(2,password) ;
// 5.执行查询
ResultSet resultSet = ps.executeQuery() ;
// 6.处理结果集
if (resultSet.next()){
System.out.println("登录成功!欢迎您:" + name);
}else {
System.out.println("登录失败!");
}
// 7.关闭流
JDBCUtils.close(connection,ps,resultSet);
}
}
PreparedStatement 的执行原理
- 分别使用Statement对象和PreparedStatement 对象进行插入操作
代码示例:
package com.JDBC03;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class TestPS {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
// 获取Statement
Statement statement = connection.createStatement();
// 向数据库插入两条数据
statement.executeUpdate("insert into jdbc_user values (null,'张三','123456','2000/12/16')");
statement.executeUpdate("insert into jdbc_user values (null,'李四','654321','2000/12/16')");
// 获取预处理对象
PreparedStatement ps = connection.prepareStatement("insert into jdbc_user values (?,?,?,?)");
// 先插入第一条数据
ps.setObject(1,null);
ps.setString(2,"小小");
ps.setString(3,"123789");
ps.setString(4,"1999/2/4");
// 执行插入
ps.executeUpdate();
// 插入第二条数据
ps.setObject(1,null);
ps.setString(2,"小乐");
ps.setString(3,"123789");
ps.setString(4,"1999/5/4");
// 执行插入
ps.executeUpdate();
// 释放资源
statement.close();
ps.close();
connection.close();
}
}
- Statement对象每执行一条SQL就会发送给数据库 数据库要先编译再执行
- 预处理对象会将SQL发送给数据库进行一个预编译,然后将预编译的SQL保存起来,这样就只需要编译一次了
JDBC控制事务
- 之前我们是使用MySQL的命令来操作事务,接下来使用JDBC来操作银行转账的事务
数据准备
create table account(
id int PRIMARY KEY auto_increment,
name VARCHAR(20),
money DOUBLE
);
INSERT into account(name,money)
VALUES('tom',1000);
INSERT into account(name,money)
VALUES('jack',1000);
事务相关API
我们使用Connection中的方法实现事务管理
方法 | 说明 |
---|---|
void setAutoCommit(boolean autoCommit) | 参数是true或false;如果设置为false,表示关闭自动提交,相当于开启事务 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
开发步骤
1.获取连接
2.开启事务
3.获取预处理对象 执行SQL(两次修改操作)
4.提交事务(正常情况)
5.出现异常就回滚事务
6.释放资源
代码示例
package com.JDBC04;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestJDBCTransaction {
// 使用JDBC操作事务
public static void main(String[] args) {
// 1.获取连接
Connection connection = null;
PreparedStatement ps = null ;
// 2.开启事务
try {
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false);
// 3.获取预处理对象 执行SQL(两次修改操作)
// 3.1 tom 账户 -500
ps = connection.prepareStatement("update account set money = money - ? where name = ?");
ps.setDouble(1,500);
ps.setString(2,"tom");
ps.executeUpdate();
// 模拟tom转账后出现异常
// System.out.println(1 / 0);
// 3.2 jack 账户 +500
ps = connection.prepareStatement("update account set money = money + ? where name = ?");
ps.setDouble(1,500);
ps.setString(2,"jack");
ps.executeUpdate();
// 4.提交事务(正常情况)
connection.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
e.printStackTrace();
// 5.出现异常就回滚事务
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
// 6.释放资源
JDBCUtils.close(connection,ps);
}
}
}