JDBC(上)
JDBC(上)
一、JDBC简介
(一)JDBC定义
简单总结:
JDBC是Java语言与数据库连接的中间件(一座桥梁), 它是一个规范而不是一个实现, 我们可以通过Java代码去实现对数据库中的数据进行增删改查;
JDBC的工作原理:由SUN(Oracle)提供一套访问数据库的规范(即一组接口),并提供连接数据库的协议标准,这组协议标准称之为JDBC API;然后各个数据库厂商会遵循SUN(Oracle)的规范提供一套访问自己公司数据库服务器的程序,称之为数据库驱动。
JDBC API是接口,而JDBC驱动才是接口的具体实现,没有驱动是无法完成数据库连接操作的。所以每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
JDBC驱动:就是JDBC API的实现类。不同类型的数据库有自己的驱动程序,为了方便使用,已经将这个驱动程序打成jar文件,可以直接导入到项目中。
无JDBC驱动和有JDBC驱动的区别:
无JDBC规范:
有JDBC规范:
(二)JDBC常见接口和类
DriverManager类 驱动管理类 作用: 注册JDBC驱动
Connection接口 连接对象 作用: 建立与数据库的连接
Statement接口 SQL编译器 作用: 向数据库发送并执行sql语句
ResultSet接口 查询的结果集对象 作用: 执行查询操作时, 对返回数据的结果进行的处理
二、JDBC快速入门
(一)下载JDBC驱动
本门课我们以MySQL数据库为例,所以我们需要下载MySQL的JDBC驱动程序。
下载地址:https://dev.mysql.com/downloads/connector/j/5.1.html
目前常用的MySQL驱动有5.* 版本和 8.*版本,二者在使用时配置上略有不同,大家可以选择其中任意一个版本使用。
驱动程序下载后解压得到jar文件:
(二)JDBC入门案例
需求: 查询用户表中数据,将用户信息显示在控制台上
2.1、流程分析
2.2、案例准备
创建数据库和数据表
创建项目, 导入环境(数据库驱动)
- 创建folder文件, 将驱动包导入
- 创建folder文件后, 命名为lib
- 将数据库驱动拷贝到该文件下
- 右键点击jar包, 点击小奶瓶
当出现证明导入成功;
2.3、案例实现
package com.ujiuye.jdbc;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement;
public class JDBCDemo { //程序入口 //需求: 查询用户表中数据,将用户信息显示在控制台上 public static void main(String[] args) throws Exception { /** * JDBC的固定思路实现需要遵循六大步:(高频面试题) * 1. 注册驱动 * 2. 获取连接 * 3. 创建执行sql语句的连接对象(并定义sql语句) * 4. 执行sql语句 * 5. 处理结果集 * 6. 释放资源 */ //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 /** * url: 连接地址 * user: 数据库用户名 * password: 数据库密码 */ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day08_jdbc", "root", "root"); //3. 创建执行sql语句的连接对象(并定义sql语句) Statement st = conn.createStatement(); //定义sql String sql = "select * from user"; //4. 执行sql语句(返回结果集) ResultSet rs = st.executeQuery(sql); //5. 处理结果集 while(rs.next()) { int uid = rs.getInt("uid"); String username = rs.getString("username"); String password = rs.getString("password"); System.out.println(uid + "\t" + username + "\t" + password); } //6. 释放资源 rs.close(); st.close(); conn.close(); } } |
三、JDBC的详解
(一)注册驱动
注册驱动实际上就是把Driver类加载JVM, 并且完成类的初始化工作;
(二)创建连接对象
参数解析:
DrvierManager 驱动管理器。 注册驱动,数据库链接等
getConnection(url,username,password) |
获取连接 Url: 主协议名:子协议名://主机名:端口号/数据库名 jdbc: mysql:/ /localhost:3306/ day08_jdbc 简写: jdbc:mysql:///day08_jdbc 如果在连接字符串后拼接参数, 则需要使用?隔开, 多个参数之间使用&隔开; 该方法会尝试和数据库进行连接操作。 测试连接对象是否能够打印出控制台 能够成功连接数据库,获取Connection对象。连接对象 |
(三)创建SQL编译器
(四)执行SQL语句
Statement对象
executeQuery(String sql) |
执行查询的SQL语句。 参数:SQL语句 会把SQL语句传递给mysql数据库去执行 返回结果:ResultSet对象---一张二维表格 |
executeUpdate(String sql) |
执行更新的SQL语句。 参数:SQL语句 会把SQL语句传递给mysql数据库去执行 返回结果:int SQL语句执行后更新了几行数据 |
execute(String sql)(了解) |
执行任意语句。 参数:SQL语句 Select,insert into,update,delete from 执行select 返回true 执行insert into,update,delete from 返回false |
(五)处理结果集
(六)释放资源
释放资源: 建议倒叙关闭
四、单元测试
JUnit单元测试是为了能够在一个类中创建多个可执行的方法,每个方法都可以独立执行,我们可以将`每个方法理解为一个主函数,一个类里面可以写多个@Test 并且互相不影响,是测试人员和开发者必须掌握的一个小技术。
关键的注解: @Test 注意: 不要将自己创建的类名设置为Test, 否则会冲突
使用@Test注释的导入步骤:
测试:(一个类中创建多个单元测试方法)
使用Junit单元测试需要注意事项:
- 单元测试的修饰符必须是public
- 方法不能有返回值
- 方法无参数
- 千万千万不要忘记添加@Test注解
五、JDBC实现CRUD操作
5.1、从数据库查询所有数据
详细见入门案例
5.2、向数据库增加数据
5.3、向数据库修改数据
5.4、从数据库根据ID删除数据
5.5、从数据库根据ID查询某条数据
5.6 定义一个方法,查询表中的数据将其封装为对象,然后装载集合,返回
六、JDBC工具类封装
6.1 完成工具类抽取
package com.ujiuye.utils;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class JDBCUtils {
//注册驱动 static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
//获取连接 public static Connection getConnection() { try { Connection conn = DriverManager.getConnection("jdbc:mysql:///day08_jdbc", "root", "root"); return conn; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }
//释放资源 public static void closeAll(ResultSet rs, Statement st, Connection conn) { //判断 if(rs != null) { try { rs.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(st != null) { try { st.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
6.2 使用工具类改写以上CRUD案例
七、PreparedStatement
(一)使用工具类完成用户登录操作(模拟)
需求:模拟用户输入账号、密码登录网站
实现效果:
package com.ujiuye.login;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.Scanner;
import com.ujiuye.utils.JDBCUtils;
public class LoginDemo {
public static void main(String[] args) throws Exception { /** * 需求:模拟用户输入账号、密码登录网站 * 模拟用户登录步骤: * 1. 在控制台输入用户名和密码 * 2. 根据用户名和密码查询数据库, 比对用户名和密码是否正确 * 3. 如果正确, 证明登录成功, 提示信息: 欢迎您, XXX; * 4. 如果错误, 登录失败, 提示信息: 用户名或者密码错误... */ //1. 在控制台输入用户名和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入账号:"); String username = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); //System.out.println("用户名为" + username + " ==> 密码为: " + password); //2. 根据用户名和密码查询数据库, 比对用户名和密码是否正确 //注册驱动和获取连接 Connection conn = JDBCUtils.getConnection(); //System.out.println(conn); Statement st = conn.createStatement(); //定义sql String sql = "select * from user where username = '"+username+"' and password = '"+password+"'"; //执行sql语句 ResultSet rs = st.executeQuery(sql); //处理结果集 if(rs.next()) { //如果正确, 证明登录成功, 提示信息: 欢迎您, XXX; String name = rs.getString("username"); System.out.println("恭喜您登录成功, 欢迎您: " + name); }else { System.out.println("用户名或者密码错误..."); }
}
} |
成功效果:
通过以上效果, 发现也会登录成功, 这种登录成功会出现sql注入问题;
为什么会出现sql注入问题?
当我们使用statement接口时, 会将控制台输入的字符串与sql语句进行一个拼接, 拼接时把字符串中的or当成了逻辑运算符进行判断, 此时 or后边跟的1=1, 恒为真(true), 所以就失去了验证用户名和密码的功能了;
解决sql注入的问题:
使用预处理对象(PreparedStatement )解决sql注入问题;
Statement与PreparedStatement 区别:(面试题)
- 使用PreparedStatement 对象虽然比Statement代码多写几行, 但是代码的可读性和维护性更好;
- PreparedStatement 对象比Statement对象执行效率要高;
- PreparedStatement 对象能够解决SQL注入问题, 安全性较高;
(二)PreparedStatement 解决SQL注入问题
PreparedStatement是位于java.sql包中的接口,是Statement接口的子接口。PreparedStatement对象会将SQL语句进行预编译,通过?占位符的方式进行SQL语句参数的拼接,这样就避免将字符中的内容当成SQL关键字参与编译执行,从而解决SQL注入问题。
PreparedSatement的执行原理
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。
1、Statement 对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。
上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。
如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了
2、 prepareStatement() 会先将SQL语句发送给数据库预编译。 PreparedStatement 会引用着预编译后的结果。
可以多次传入不同的参数给 PreparedStatement 对象并执行。相当于调用方法多次传入不同的参数。
以上?代表 占位符
上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。
PreparedStatement常见方法的使用:
setInt(int index,int value) |
为?占位符,赋予int值 |
setString(int index,String value) |
为?占位符,赋予String值 |
executeQuery() |
执行查询的SQL语句。 会把SQL语句传递给mysql数据库去执行 返回结果:ResultSet对象---一张二维表格 |
executeUpdate() |
执行更新的SQL语句。 会把SQL语句传递给mysql数据库去执行 返回结果:int SQL语句执行后更新了几行数据 |
(三)PreparedStatement的CRUD操作