主要内容:
- 能够理解JDBC的概念
- 能够使用DriverManager类
- 能够使用Connection接口
- 能够使用Statement接口
- 能够使用ResultSet接口
- 能够说出SQL注入原因和解决方案
- 能够通过PreparedStatement完成增、删、改、查
- 能够完成PreparedStatement改造登录案例
1 JDBC入门
1.1 客户端操作MySQL数据库的方式
- 使用第三方客户端来访问MySQL:SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL;

- 使用MySQL自带的命令行方式;
- 通过Java来访问MySQL数据库,今天要学习的内容。
1.2 JDBC的基本概念
-
概念:Java DataBase Connectivity(Java 数据库连接),Java语言操作数据库
-
本质:是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商提供各自的实现类去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
-
优点:
- 程序员如果要开发访问数据库的程序,只需要会调用JDBC接口中的方法即可,不用关注类是如何实现的;
- 使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库。

1.3 JDBC依赖包与核心API
- JDBC开发使用到的包:
使用到的包 说明 java.sql 所有与JDBC访问数据库相关的接口和类 javax.sql 数据库扩展包,提供数据库额外的功能。如:连接池 数据库驱动 由各大数据库厂商提供,需要额外去下载,是对JDBC接口实现的类 - JDBC的核心API:
接口或类 作用 DriverManager类 1. 管理和注册数据库驱动
2. 得到数据库连接对象Connection接口 一个连接对象,可用于创建Statement和PreparedStatement对象 Statement接口 一个SQL语句对象,用于将SQL语句发送给数据库服务器 PreparedStatement接口 一个SQL语句对象,是Statement的子接口 ResultSet接口 用于封装数据库查询的结果集,返回给客户端Java程序
2 JDBC操作步骤

2.1 导入驱动Jar包
- 复制
mysql-connector-java-5.1.37-bin.jar到项目的lib目录下:

右键→ Add As Library :

2.2 加载和注册驱动
-
语法:
Class.forName(数据库驱动实现类)
数据库驱动由数据库厂商提供:- MySQL驱动类名:
com.mysql.jdbc.Driver; - Oracle驱动类名:
oracle.jdbc.driver.OracleDriver; - SQL server驱动类名:
com.microsoft.jdbc.sqlserver.SQLServerDriver。
public class Demo1 { public static void main(String[] args) throws ClassNotFoundException { //抛出类找不到的异常,注册数据库驱动 Class.forName("com.mysql.jdbc.Driver"); } } - MySQL驱动类名:
-
为什么这样可以注册驱动?
查看Class.forName(String className)源码:@CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }再看
forName0方法:private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;forName0 是一个 native 方法(由非java语言实现),返回一个给定类或者接口的一个 Class 对象,如果没有给定 ClassLoader, 那么会使用根类加载器。如果 initalize 这个参数传了 true,那么给定的类如果之前没有被初始化过,则会被初始化。JDBC 传入的参数是
com.mysql.jdbc.Driver。也就是说这个类会被初始化,查看这个类的源码:public class Driver implements java.sql.Driver { static { try { DriverManager.registerDriver(new Driver()); //注册数据库驱动 } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { } }类在初始化的时候,静态代码块的内容会被执行的。也就是说我们
Class.forName和直接写DriverManager.registerDriver(new Driver)两者功能是等同的。注:从JDBC3开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。Class.forName这句话可以省略。
2.3 获取数据库连接
- DriverManager类:驱动管理对象。
-
Connection getConnection (String url, String user, String password):通过连接字符串,用户名,密码来得到数据库的连接对象;url:数据库连接路径,用于标识数据库的位置,告知JDBC程序连接哪个数据库。- 格式:
协议名:子协议://服务器名或IP地址:端口号/数据库名?参数=参数值 - MySQL格式:
jdbc:mysql://ip地址(域名):端口号/数据库名称?参数=参数值
注:如果连接的是本地服务器,并且端口号是3306,可简写为:
jdbc:mysql:///数据库名称。
- 格式:
user:登录的用户名。password:登录的密码。- 乱码处理:指定参数 ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。
jdbc:mysql://localhost:3306/数据库?characterEncoding=utf8
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class Demo2 { public static void main(String[] args) throws SQLException { //使用用户名、密码、URL得到连接对象 String url = "jdbc:mysql://localhost:3306/day24"; Connection connection = DriverManager.getConnection(url, "root", "root"); System.out.println(connection); //com.mysql.jdbc.JDBC4Connection@68de145 } } -
Connection getConnection (String url, Properties info):通过连接字符串,属性对象来得到连接对象。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class Demo3 { public static void main(String[] args) throws SQLException { //url连接字符串 String url = "jdbc:mysql://localhost:3306/day24"; //属性对象 Properties info = new Properties(); //把用户名和密码放在info对象中 info.setProperty("user", "root"); info.setProperty("password", "root"); Connection connection = DriverManager.getConnection(url, info); System.out.println(connection); //com.mysql.jdbc.JDBC4Connection@68de145 } }
-
2.4 执行sql语句
- Connection接口:数据库连接对象。
Statement createStatement():创建一条SQL语句对象。
- ResultSet接口:结果集对象,用于封装查询结果。
-
boolean next():游标向下移动1行,判断当前指向的记录是否还有下一条记录,如果还有下一条返回true;否则返回false。 -
参数数据类型 getXxx(参数):- 通过列名:
getInt(“id”),getString(“name”),getBoolean("gender "),getDate(“birthday”) - 通过列号:
getInt(1), getString(2),getBoolean(3),getDate(4)

常用数据类型转换表:SQL类型 JDBC对应方法 返回类型 BIT(1) bit(n) getBoolean() boolean TINYINT getByte() byte SMALLINT getShort() short INT getInt() int BIGINT getLong() long CHAR,VARCHAR getString() String Text(Clob) Blob getClob getBlob() Clob Blob DATE getDate() java.sql.Date 只代表日期 TIME getTime() java.sql.Time 只表示时间 TIMESTAMP getTimestamp() java.sql.Timestamp 同时有日期和时间 java.sql.Date、Time、Timestamp(时间戳),三个共同父类是:
java.util.Date。 - 通过列名:
-
- Statement接口:代表一条语句对象,用于发送SQL语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。
-
释放资源:
- 需要释放的对象:ResultSet结果集,Statement语句,Connection连接;
- 释放原则:先开的后关,后开的先关。ResultSet → Statement → Connection;
- 放在哪个代码块中:finally块。
-
Statement中的方法:
-
int executeUpdate(String sql):执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句,返回对数据库影响的行数。-
执行DDL操作:使用JDBC在MySQL的数据库中创建一张学生表。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class Demo4DDL { public static void main(String[] args) { //1. 创建连接 Connection conn = null; Statement statement = null; try { conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root"); //2. 通过连接对象得到语句对象 statement = conn.createStatement(); //3. 通过语句对象发送SQL语句给服务器 //4. 执行SQL statement.executeUpdate("create table student (id int PRIMARY key auto_increment, " + "name varchar(20) not null, gender boolean, birthday date)"); //5. 返回影响行数(DDL没有返回值) System.out.println("创建表成功"); } catch (SQLException e) { e.printStackTrace(); } //6. 释放资源 finally { //关闭之前要先判断 if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
-
执行DML操作:使用JDBC向学生表中添加4条记录,主键是自动增长。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class Demo5DML { public static void main(String[] args) throws SQLException { // 1) 创建连接对象 Connection connection = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root"); // 2) 创建Statement语句对象 Statement statement = connection.createStatement(); // 3) 执行SQL语句:executeUpdate(sql) int count = 0; // 4) 返回影响的行数 count += statement.executeUpdate("insert into student values(null, '孙悟空', 1, '1993-03-24')"); count += statement.executeUpdate("insert into student values(null, '白骨精', 0, '1995-03-24')"); count += statement.executeUpdate("insert into student values(null, '猪八戒', 1, '1903-03-24 ')"); count += statement.executeUpdate("insert into student values(null, '嫦娥', 0, '1993-03-11')"); System.out.println("插入了" + count + "条记录"); // 5) 释放资源 statement.close(); connection.close(); } }
-
-
ResultSet executeQuery(String sql):执行DQL(select)语句,返回结果集对象。-
执行DQL操作:确保数据库中有3条以上的记录,查询所有的学员信息。
import java.sql.*; public class Demo6DQL { public static void main(String[] args) throws SQLException { //1) 得到连接对象 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root"); //2) 得到语句对象 Statement statement = connection.createStatement(); //3) 执行SQL语句得到结果集ResultSet对象 ResultSet rs = statement.executeQuery("select * from student"); //4) 循环遍历取出每一条记录 while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); boolean gender = rs.getBoolean("gender"); Date birthday = rs.getDate("birthday"); //5) 输出的控制台上 System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday); } //6) 释放资源 rs.close(); statement.close(); connection.close(); } }结果:

注意:
1)如果光标在第一行之前,使用rs.getXX()获取列值,报错:Before start of result set;
2)如果光标在最后一行之后,使用rs.getXX()获取列值,报错:After end of result set;
3)使用完毕以后要关闭结果集ResultSet,再关闭Statement,再关闭Connection。
-
-
-
3 数据库工具类JDBCUtils
3.1 建立JDBCUtils
-
什么时候自己创建工具类?
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。 -
创建类JDBCUtils包含3个方法:
- 可以把几个字符串定义成常量:
用户名,密码,URL,驱动类 - 得到数据库的连接:
getConnection() - 关闭所有打开的资源:
close(Connection conn, Statement stmt)
close(Connection conn, Statement stmt, ResultSet rs)
- 可以把几个字符串定义成常量:
-
代码:
import java.sql.*; /** * 访问数据库的工具类 */ public class JDBCUtils { //可以把几个字符串定义成常量:用户名,密码,URL,驱动类 private static final String USER = "root"; private static final String PWD = "root"; private static final String URL = "jdbc:mysql://localhost:3306/day24"; private static final String DRIVER = "com.mysql.jdbc.Driver"; /** * 注册驱动 */ static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 得到数据库的连接 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } /** * 关闭所有打开的资源 */ public static void close(Connection conn, Statement stmt) { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 关闭所有打开的资源 */ public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } close(conn, stmt); } }
3.2 案例:用户登录
-
有一张用户表,添加几条用户记录:
CREATE TABLE USER ( id INT PRIMARY KEY auto_increment, NAME VARCHAR ( 20 ), PASSWORD VARCHAR ( 20 ) ) INSERT INTO USER VALUES (NULL,'jack','123'),(NULL,'rose','456'); -- 登录, SQL中大小写不敏感 SELECT * FROM USER WHERE NAME='JACK' AND PASSWORD='123'; -- 登录失败 SELECT * FROM USER WHERE NAME='JACK' AND PASSWORD='333'; -
根据用户输入的用户名和密码,使用JDBC来查询数据库:
import com.itheima.utils.JDBCUtils; import javax.xml.transform.Result; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class Demo7Login { //从控制台上输入的用户名和密码 public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } /** * 登录的方法 */ public static void login(String name, String password) { //a) 通过工具类得到连接 Connection connection = null; Statement statement = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); //b) 创建语句对象,使用拼接字符串的方式生成SQL语句 statement = connection.createStatement(); //c) 查询数据库,如果有记录则表示登录成功,否则登录失败 String sql = "select * from user where name='" + name + "' and password='" + password + "'"; System.out.println(sql); rs = statement.executeQuery(sql); if (rs.next()) { System.out.println("登录成功,欢迎您:" + name); } else { System.out.println("登录失败"); } } catch (SQLException e) { e.printStackTrace(); } finally { //d) 释放资源 JDBCUtils.close(connection, statement, rs); } } }
3.3 SQL注入问题
当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了:
请输入用户名:
newboy
请输入密码:
a' or '1'='1
select * from user where name='newboy' and password='a' or '1'='1'
登录成功,欢迎您:newboy
分析:
select * from user where name='newboy' and password='a' or '1'='1'
name='newboy' and password='a' 为假;
'1'='1' 为真;
相当于 select * from user where true; 查询了所有记录。
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
3.4 PreparedStatement接口
-
概念:是Statement接口的子接口,继承于父接口中所有的方法,可预编译SQL语句。有效防止SQL注入的问题,安全性更高。
-
执行原理:

-
Connection创建PreparedStatement对象:
PreparedStatement prepareStatement(String sql):指定预编译的SQL语句,SQL语句中使用占位符?创建一个语句对象。
-
PreparedStatement接口中的方法:
int executeUpdate():执行DML,增删改的操作,返回影响的行数。ResultSet executeQuery():执行DQL,查询的操作,返回结果集。
-
PreparedSatement的好处:
- prepareStatement(String)会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率;
- 安全性更高,没有SQL注入的隐患;
- 提高了程序的可读性。
-
使用PreparedStatement的步骤:
-
编写SQL语句,未知内容使用
?占位:SELECT * FROM user WHERE name=? AND password=?; -
获得PreparedStatement对象;
-
设置实际参数
setXxx(占位符的位置, 真实的值):PreparedStatement中设置参数的方法 描述 void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值 void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值 void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值 void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值 void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值 void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值 -
执行参数化SQL语句;
-
关闭资源。
-
-
使用PreparedStatement改写上面的登录程序,看有没有SQL注入的情况:
import com.itheima.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Scanner; /** * 使用PreparedStatement */ public class Demo8Login { //从控制台上输入的用户名和密码 public static void main(String[] args) throws SQLException { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } /** * 登录的方法 * * @param name * @param password */ private static void login(String name, String password) throws SQLException { Connection connection = JDBCUtils.getConnection(); //写成登录SQL语句,没有单引号 String sql = "select * from user where name=? and password=?"; //得到语句对象 PreparedStatement ps = connection.prepareStatement(sql); //设置参数 ps.setString(1, name); ps.setString(2, password); ResultSet resultSet = ps.executeQuery(); if (resultSet.next()) { System.out.println("登录成功:" + name); } else { System.out.println("登录失败"); } //释放资源,子接口直接给父接口 JDBCUtils.close(connection, ps, resultSet); } }
3.5 表与类的关系

-
使用PreparedStatement查询一条数据,封装成一个学生Student对象:
import com.itheima.entity.Student; import com.itheima.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class Demo9Student { public static void main(String[] args) throws SQLException { //创建学生对象 Student student = new Student(); Connection connection = JDBCUtils.getConnection(); PreparedStatement ps = connection.prepareStatement("select * from student where id=?"); //设置参数 ps.setInt(1, 2); ResultSet resultSet = ps.executeQuery(); if (resultSet.next()) { //封装成一个学生对象 student.setId(resultSet.getInt("id")); student.setName(resultSet.getString("name")); student.setGender(resultSet.getBoolean("gender")); student.setBirthday(resultSet.getDate("birthday")); } //释放资源 JDBCUtils.close(connection, ps, resultSet); //可以数据 System.out.println(student); } } -
将多条记录封装成集合List,集合中每个元素是一个JavaBean实体类:
import com.itheima.entity.Student; import com.itheima.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Demo10List { public static void main(String[] args) throws SQLException { //创建一个集合 List<Student> students = new ArrayList<>(); Connection connection = JDBCUtils.getConnection(); PreparedStatement ps = connection.prepareStatement("select * from student"); //没有参数替换 ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { //每次循环是一个学生对象 Student student = new Student(); //封装成一个学生对象 student.setId(resultSet.getInt("id")); student.setName(resultSet.getString("name")); student.setGender(resultSet.getBoolean("gender")); student.setBirthday(resultSet.getDate("birthday")); //把数据放到集合中 students.add(student); } //关闭连接 JDBCUtils.close(connection, ps, resultSet); //使用数据 for (Student stu : students) { System.out.println(stu); } } } -
PreparedStatement执行DML操作:
import com.itheima.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Demo11DML { public static void main(String[] args) throws SQLException { //insert(); //update(); delete(); } //插入记录 private static void insert() throws SQLException { Connection connection = JDBCUtils.getConnection(); PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)"); ps.setString(1, "小白龙"); ps.setBoolean(2, true); ps.setDate(3, java.sql.Date.valueOf("1999-11-11")); int row = ps.executeUpdate(); System.out.println("插入了" + row + "条记录"); JDBCUtils.close(connection, ps); } //更新记录: 换名字和生日 private static void update() throws SQLException { Connection connection = JDBCUtils.getConnection(); PreparedStatement ps = connection.prepareStatement("update student set name=?, birthday=? where id=?"); ps.setString(1, "黑熊怪"); ps.setDate(2, java.sql.Date.valueOf("1999-03-23")); ps.setInt(3, 5); int row = ps.executeUpdate(); System.out.println("更新" + row + "条记录"); JDBCUtils.close(connection, ps); } //删除记录: 删除第5条记录 private static void delete() throws SQLException { Connection connection = JDBCUtils.getConnection(); PreparedStatement ps = connection.prepareStatement("delete from student where id=?"); ps.setInt(1, 5); int row = ps.executeUpdate(); System.out.println("删除了" + row + "条记录"); JDBCUtils.close(connection, ps); } }
3.6 JDBC事务的处理
之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。
-
准备数据:
CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR ( 10 ), balance DOUBLE ); -- 添加数据 INSERT INTO account (NAME,balance) VALUES ('Jack',1000),('Rose',1000); -
Connection接口中与事务有关的方法:
void setAutoCommit(boolean autoCommit):参数是true或false。如果设置为false,表示关闭自动提交,相当于开启事务void commit():提交事务;void rollback():回滚事务。
-
具体步骤:
- 获取连接;
- 开启事务;
- 获取到PreparedStatement;
- 使用PreparedStatement执行两次更新操作;
- 正常情况下提交事务;
- 出现异常回滚事务;
- 最后关闭资源。
import com.itheima.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Demo12Transaction { //没有异常,提交事务,出现异常回滚事务 public static void main(String[] args) { //1) 注册驱动 Connection connection = null; PreparedStatement ps = null; try { //2) 获取连接 connection = JDBCUtils.getConnection(); //3) 开启事务 connection.setAutoCommit(false); //4) 获取到PreparedStatement //从Jack扣钱 ps = connection.prepareStatement("update account set balance = balance - ? where name=?"); ps.setInt(1, 500); ps.setString(2, "Jack"); ps.executeUpdate(); //出现异常 System.out.println(100 / 0); //给Rose加钱 ps = connection.prepareStatement("update account set balance = balance + ? where name=?"); ps.setInt(1, 500); ps.setString(2, "Rose"); ps.executeUpdate(); //提交事务 connection.commit(); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); try { //事务的回滚 connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } System.out.println("转账失败"); } finally { //7) 关闭资源 JDBCUtils.close(connection, ps); } } }
本文详细介绍了JDBC的概念、依赖包及核心API,包括如何加载和注册驱动、获取数据库连接以及执行SQL语句。通过实例演示了JDBCUtils工具类的创建,用户登录案例以及PreparedStatement接口的使用,强调了SQL注入问题和事务处理的重要性。
1742

被折叠的 条评论
为什么被折叠?



