一.JDBC概述
1.JDBC简介
JDBC(Java DataBase Connectivity)即java数据库连接是Java连接不同数据库的类库,它有三个核心功能:1.连接数据库,2.向数据库发送SQL语句,3.操作SQL语句的返回结果
2.JDBC概念
JDBC是SUN公司提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,各个数据库厂商遵循SUN的规范提供一套访问自己公司的数据库服务器的API。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的可以访问不同型号的数据库的API被称之为数据库驱动
二.JDBC核心类与接口
2.1 DriverManager类
DriverManger是java.sql包下的一个类,它的作用是:
1) 注册驱动:
使用DriverManager类中的,让JDBC知道要使用的是哪个驱动
DriverManager类中registerDriver方法的源码:
//一个静态同步方法,参数是一个实现了java.sql.Driver接口的类(不同的数据库驱动有java.sql.Driver接口的不同实现,所以JDBC可以操作不同的数据库)
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
MySQL驱动包中实现了java.sql.Driver接口的类com.mysql.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
//静态代码块,加载此类后即执行此代码块中的代码
static {
try {
//调用DriverManager类的registerDriver方法注册MySQL驱动
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
由以上代码可以看出在MySQL驱动中,Driver类一经加载即可自动注册MySQL驱动,如果使用DriverManager类的registerDriver方法注册MySQL驱动会注册两次MySQL驱动
/**
* 注册了两次驱动
* 1.new com.mysql.jdbc.Driver()加载Driver类时
* 2.DriverManager调用registerDriver方法时
*/
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
所以,实际开发中,为了节省CPU资源,避免重复加载驱动,使用反射加载驱动类的方式:
Class.forName("com.mysql.jdbc.Driver");
2)获取Connection:
DriverManager类的第二个功能是获取Connection数据库连接,成功获取与数据库的连接后才能操作数据库。
DriverManager类获取数据库连接的方法:
public static Connection getConnection(String url,String user,String password)
该方法作用试图建立到给定数据库 URL 的连接,并返回一个数据库连接对象connection
getConnection方法有三个参数:
参数 | 功能 |
---|---|
url | URL用于标识数据库的位置信息(好比访问网站时的网址,提供服务端信息) |
user | 数据库用户名 |
password | 对应用户的密码 |
url参数详解:
通过URL地址基本格式为:
1协议,2子协议,3主机,4端口,5数据库
例如: jdbc:mysql://localhost:3306/shop,如果连接的是本机并且端口号是3306,以上代码也可以简写成jdbc:mysql:///shop
url部分 | 含义 | 规定来源 |
---|---|---|
1协议 | jdbc | JDBC规范,固定的 |
2子协议 | 数据库类型名称,这里使用MySQL | JDBC规范,根据数据库种类填写 |
3主机 | 数据库所在的主机IP | 数据库厂商(MySQL)的要求 |
4端口号 | MySQL默认端口号是3306 | 数据库厂商(MySQL)的要求 |
5数据库 | DATABASE名称 | 数据库厂商(MySQL)的要求 |
url后面也可以携带参数例如:
jdbc:mysql://localhost:3306/day06?useUnicode=true&characterEncoding=UTF-8
useUnicode参数指定连接数据库的过程中,使用的字节集是Unicode字节集;characherEncoding参数指定连接数据库的过程中,使用的字节集编码为UTF-8编码。
注:mysql中指定UTF-8编码是给出的是UTF8,而不是UTF-8
2.2 Connection类
Connection代表的是一个与数据库连接的对象,常用方法:
方法 | 功能 |
---|---|
Statement createStatement( ) | 创建一个 Statement 对象来将 SQL 语句发送到数据库 |
PreparedStatement prepareStatement(String sql) | 创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库 |
CallableStatement prepareCall(String sql) | 创建一个 CallableStatement 对象来调用数据库存储过程 |
Statement createStatement(int resultSetType, int resultSetConcurrency) | 设置结果集(ResultSet)的属性,通过两个参数resultSetType和resultSetConcurrency进行设置 |
resultSetType的可选值:
值 | 含义 |
---|---|
ResultSet.TYPE_FORWARD_ONLY | 不滚动结果集 |
ResultSet.TYPE_SCROLL_INSENSITIVE | 滚动结果集,结果集数据不会跟随数据库而变化 |
ResultSet.TYPE_SCROLL_SENSITIVE(不常用) | 滚动结果集,结果集数据会跟随数据库而变化 |
resultSetConcurrency的可选值:
值 | 含义 |
---|---|
CONCUR_READ_ONLY | 结果集是只读的,不能通过修改结果集而反向影响数据库 |
CONCUR_UPDATABLE(不常用) | 结果集是可更新的,对结果集的更新可以反向影响数据库 |
2.3 Statement类
Statement对象用于执行静态 SQL 语句并返回它所生成结果的对象,常用方法:
方法 | 功能 |
---|---|
ResultSet executeQuery(String sql) | 执行给定的sql语句,通常是查询语句,返回单个的ResultSet结果集对象 |
int executeUpdate(String sql) | 执行给定的sql语句,该语句可能为update,delete,insert,返回的是行计数 |
boolean execute(String sql) | 执行给定的sql语句,返回值boolean表示sql语句是否正常执行 |
int getUpdateCount( ) | 前提:使用execute方法进行了增删改操作,则可用此方法接收影响的行数 |
ResultSet getUpdateCount( ) | 前提:使用execute方法进行了查询操作,则可用此方法接收返回的结果集 |
2.4 ResultSet类
ResultSet对象表示数据库结果集的数据表,是一个二维数据表,可以通过索引操作其中的数据,通常通过执行查询数据库的语句生成。ResultSet封装执行结果时,类似迭代器。
ResultSet滚动结果集(纵向遍历)
ResultSet内部有一个用来标记当前操作位置的游标,ResultSet提供了一系列的方法来移动游标。这些移动游标的方法的使用是有前提的,即在Connection调用方法createStatement时设置方法的resultSetType参数为结果集可滚动。若resultSetType参数设置为结果集不可滚动,则所有操作游标的方法中只有next( )方法能够使用
滚动结果集的操作是以行为单位进行的,也就是纵向的遍历数据库表中的数据
方法 | 功能 |
---|---|
void beforeFirst( ) | 把光标放到第一行的前面,这也是光标默认的位置 |
void afterLast( ) | 把光标放到最后一行的后面 |
boolean first( ) | 把光标放到第一行的位置上,返回值表示调控光标是否成功 |
boolean last( ) | 把光标放到最后一行的位置上 |
boolean isBeforeFirst( ) | 当前光标位置是否在第一行前面 |
boolean isAfterLast( ) | 当前光标位置是否在最后一行的后面 |
boolean isFirst( ) | 当前光标位置是否在第一行上 |
boolean isLast( ) | 当前光标位置是否在最后一行上 |
boolean previous( ) | 把光标向上挪一行 |
boolean next( ) | 把光标向下挪一行 |
boolean relative(int row) | 相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行 |
boolean absolute(int row) | 绝对位移,把光标移动到指定的行上 |
int getRow( ) | 返回当前光标所有行 |
ResultSet获取数据(横向获取)
1,获取指定字段(通过字段索引)中某种类型的数据
参数columnIndex表示字段(列)的索引,字段(列)索引从1开始,而不是0,这第一点与数组不同。如果已经明确当前字段(列)的数据类型,使用getXXX( )方法来获取确定类型的数据,如果不明确当前字段(列)的数据类型,使用getObject( )方法获取
方法 | 功能 |
---|---|
Object getObject(int columnIndex) | 获取任意类型数据,参数columnIndex代表的是列的序号,第一列序号为1,第二列是2,以此类推 |
int getInt(int columnIndex) | 获取指定字段(列)的int类型的数据 |
Date getDate(int columnIndex) | 获取指定字段(列)的日期类型的数据 |
String getString(int columnIndex) | 获取指定字段(列)的String类型的数据 |
double getDouble(int columnIndex) | 获取指定字段(列)的int类型数据 |
boolean getBoolean(int columnIndex) | 获取指定字段(列)的boolean类型数据 |
2,通过字段名(列名)获取数据
方法 | 功能 |
---|---|
String getString(String columnName) | 获取名称为columnName的列的String类型的数据 |
int getInt(String columnName) | 获取名称为columnName的列的int类型的数据 |
double getDouble(String columnName) | 获取名称为columnName的列的double类型的数据 |
boolean getBoolean(String columnName) | 获取名称为columnName的列的boolean类型的数据 |
Object getObject(String columnName) | 获取名称为columnName的列的Object类型的数据 |
三.JDBC操作数据库
JDBC连接数据库是一个固定步骤,是常规Java项目的基本操作。
- 通过DriverManager注册驱动
- 通过DriverManager获取连接对象Connection
- 通过Connection获取一个可以向数据库发送sql语句的对象Statement
- 通过Statement对象执行sql语句(select) 得到一个结果集ResultSet
- 遍历结果集ResultSet,得到数据表中的数据 释放资源
- 释放资源
package com.itheima.utils;
import org.junit.Test;
import java.sql.*;
public class JDBCUtils {
@Test
public void testJDBC() throws ClassNotFoundException, SQLException {
/**
* 1.常用反射的方式注册驱动
* 使用registerDriver()方法注册驱动最终会组测两次驱动,因为Driver对象中有一个静态代码块用来注册驱动
*
*/
Class.forName("com.mysql.jdbc.Driver");
/**2.通过DriverManager获取连接对象*/
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/shop", "root", "123");
/**3.通过Connection获取一个操作sql语句的对象Statement*/
Statement statement = connection.createStatement();
/**
* 4.执行sql语句(select) 得到一个ResultSet
* 使用executeQuery方法执行查询会有结果集,使用executeUpdate方法执行更新则没有结果集
*/
String sql = "select * from USER ";
ResultSet resultSet = statement.executeQuery(sql);
/**
* 5.操作结果集,得到数据(使用类似迭代器)
* ResultSet就是一张二位表格,它内部有一个"行光标",光标默认的位置在"第一行上方",我们可以调用ResultSet对象的next()方法把"行光标"向下移动一行,当第一次调用next()方法之后,"行光标"就到了第一行记录的位置,这时就可以使用ResultSet提供的getXXX(int col)方法来获取指定列的数据了
* resultSet.next();//光标移动到第一行
* resultSet.getInt(1);//获取第一行第一列的数据
*
*/
while (resultSet.next()) {
System.out.println("ID:" + resultSet.getString("uid") + "\tNAME:"+ resultSet.getString("name"));
}
/**
* 6.释放资源
* 包括结果集 resultSet
* 负责向数据库发送sql语句的对象 statement
* 数据库连接对象 connection
* 关闭是有顺序的,先得到的后关闭,后得到的先关闭
*
*/
resultSet.close();
statement.close();
connection.close();
}
}
四.PreparedStatement类
4.1 SQL注入攻击
假设有登录案例SQL语句如下:
SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码
此时,当用户输入正确的账号密码后,查询到了信息则让用户登录。
但是当用户输入的账号为XXX 密码为:XXX’ OR 1=1时
则真正执行的代码变为:
SELECT * FROM 用户表 WHERE NAME='XXX' AND PASSWORD='XXX' OR 1=1'
此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。
4.2 解决方案:
1)过滤用户输入的数据,检查其中是否包含非法字符;
2)分步校验,先使用用户名来查询用户,如果查找到了,再比较密码;
3)使用PreparedStatement代替Statement传递sql语句(最优)
4.3 PreparedStatement类
PreparedStatement即预编译声明,是Statement的子接口,PreparedStatement支持对sql语句的参数进行封装,可以有效的防止SQL攻击。同时还可以提高代码的可读性和维护性。
使用PreparedStatement类开发:
1)获取对象:
使用数据库连接返回PreparedStatement对象代替Statement对象
2)操作占位符数据
使用PreparedStatement对象传递sql语句时,sql语句中比较重要的信息比如用户名,密码,等不会直接写在sql语句中,而是用占位符”?”代替
对预编译的sql语句中”?”占位符赋值操作需要使用到的方法:
方法 | 功能 |
---|---|
void setInt(int parameterIndex,int x) | 设置int |
void setString(int parameterIndex,String x) | 设置字符串 |
void setObject(int parameterIndex,Object x) | 设置其他类型 |
3)参数的含义:
参数 | 含义 |
---|---|
parameterIndex | 表示占位符位置(从1开始计数) |
x | 传入参数的值 |
4)执行sql语句
PreparedStatement ppState = conn.prepareStatement("INSERT INTO aaa(id,NAME) VALUES(null,?)");
ppState.setString(1, "baby");
ppState.executeUpdate();
注:建议在开发中尽可能使用PreparedStatement代替Statement