JDBC的概念
英文全称:Java DataBase Connectivity
中文名:java数据库连接
故名思义,作用:与数据建立起连接,使java能实现对数据库的操作。
JDBC的特点
1.JDBC是一组用java语言编写的类和接口组成的程序API;
2.JDBC可以用统一的语法对多种关系数据库进行访问和操作(比如既可以访问oracle,也可以访问mysql);
3.JDBC结构: Diver Interface(驱动程序管理接口) + API(应用程序编程接口)
4.优点:
(1). 简化程序员的操作;
(2). 支持不同的关系数据库,可移植性强;
(3). JDBC API是封装的,可以一直备用,提高效率。
5.缺点:
(1). 访问数据记录的速度会受到影响;
(2). 包含多种不同产品,更改数据源麻烦。
JDBC的类
1.DriverManager
概念: 属于java.sql包,用于管理数据库驱动程序的类
作用: 处理驱动程序的加载和建立新数据库连接
常用静态方法:
getConnection(String url,String userName,String password) ,该方法返回一个Connection类对象
DriverManager类在实际编程中的应用:
(1) 加载并注册驱动:
A. 引进驱动类,驱动类名(diver class name)一般是固定的,不同数据库的驱动类名不同
//一般情况,把驱动参数声明为私有的、静态的、不可变。(封装、代码复用、数据保护)
//oracle数据库的驱动类名
private static final String DRIVER = “oracle.jdbc.driver.OracleDriver”;
//mysql数据库的驱动类名
private static final String DRIVER = “com.mysql.jdbc.Driver”;
//Sql Server数据库的驱动类名
private static final String DRIVER = “com.microsoft.jdbc.sqlserver.SQLServerDriver”;```
B. 加载并注册驱动
使用Class.forName()方法,该方法返回的是一个类
作用:指定JVM加载名字为指定参数的驱动类
//指定JVM加载DRIVER类 -- 创建数据库连接的第一步
Class.forName(DRIVER);
(2) 建立数据库连接
A. DriverManager类的getConnection()方法返回的是一个Connection类对象
Connection类的概念:
属于java.sql包,用于处理与特定数据库连接的类;
Connection类对象:表示数据库连接的对象; – – 连接失败时值为null
Connection类对象包含多种方法,供Java程序对数据库进行操作。
B. getConnection(String url,String userName,String password)
该方法需要三个参数:
url: 表示连接数据库的地址
userName: 表示登录数据库的用户名
password: 表示登录数据库的密码
//一般情况,把三个参数声明为私有的、静态的、不可变。(封装、代码复用、数据保护)
//定义连接oracle数据库地址的参数
private static final String URL = “jdbc:oracle:thin://localhost:1521/orcl”;
// 定义数据库登录用户名的参数
private static final String USER = “scott”;
// 定义数据库登录密码的参数
private static final String PASSWORD = “123456”;
//定义Connection类对象
Connection conn = null;
//调用DriverManager类的getConnection()方法 -- 创建数据库连接的第二步,建立数据库连接完成
conn = DriverManager.getConnection(URL,USER,PASSWORD);
建立数据库连接后,conn即表示数据库的当前连接状态。
在测试的时候可以system.out.print(conn);检测数据库连接是否正常:
如果连接正常,返回当前的数据库连接地址;
如果连接异常,返回null,报出空指针异常:Exception in thread “main” java.lang.NullPointerException
2. SQLException类
概念:继承Exception类,提供数据库异常处理
作用:当运行的有关数据库的操作出现错误或警告,就会触发异常处理
JDBC的接口:
1. Connectiond接口:
Connection接口的实现类即上文的Connection类,概念及作用等同。
作用:
(1)Connection对象表示数据库的连接状态;- - 保存DriverManager()方法的返回值。
(2) Connection接口提供了多种数据库操作的方法:
常用的几种方法:
1.处理sql语句(常用的三种如下): - - 处理的过程 :编译/预编译sql语句,还没执行
(1) stamement(String sql) - - 返回的是一个Statement对象
(2) preparedStatement(String sql) - - 返回的是一个PreparedStatement对象
(3)callabStatement(String sql) - - 返回的是一个CallabStatement对象2.控制事务提交
(1) setAutoCommit(true/false) - - true为打开自动提交,false为关闭自动提交。(默认自动提交)3.控制事务回滚
rollback()4.关闭数据库连接
close()
2.Statement/PreparedStatement/CallabStatement接口
三个接口分别对应Connnection接口的三种方法。
概念:
Statement/PreparedStatement/CallabStatement接口是 Java 执行数据库操作的一类重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。
作用:
1.用于执行SQL 语句 - - 执行的过程:把sql发送到数据库中,由数据库执行
2.并返回它所生成结果的对象。
Statement/PreparedStatement/CallabStatement接口提供了多种执行sql语句的方法
常用的几种方法:
1.executeQuery(): 用于执行查询型的sql语句,返回单个结果集对象;
2.executeUpdate(): 用于执行更新、添加、删除、修改等sql语句,返回一个整数,指数据库中受影响的行数(即更新的行数);
3.execute(): 用于执行多个更新计数或二者结合的sql语句,返回多个结果集,比较少用。
结果集:一个ResultSet接口对象。
(1)Statement对象:
用于执行不带参数的简单SQL语句
特点:
a. 只执行单条的sql语句;
b. 只能执行不带参数的sql语句;
c.运行原理的角度,数据库接收到sql语句后需要对该条sql语句进行编译后才执行;
d.与其它接口对比,适合执行单条且不带参数的sql语句,这种情况执行效率相对较高。
(2)PreparedStatement对象
执行带或不带 IN 参数的预编译 SQL 语句
特点:
a. 继承自Statement接口(意味着功能相对更加全面);
b. 带有预编译的特性。
c. 批量处理sql语句
d. 处理带未知参数的sql语句
e. 具有安全性,即可以防止恶意的sql语句注入攻击
f. 在处理单条语句上,执行效率没有Statement快
g. 提高程序的可读性和可维护性
特点解释:
1.何为 预编译:
原理: 编译过的语句能被重复调用
- 程序每执行一条sql语句,都会被被数据库编译器编译后缓存下来;
- 缓存的语句包括了:整个数据库曾经编译过的语句(一般不会保存重复的语句);
- 以后在执行结构相同的sql语句时,会直接在缓存池中匹配相对应的sql语句,直接执行,节省了重复编译的时间。
- 结构相同指的是sql语法相同,参数可能不同。预编译会先对当前sql语句进行预处理,判断出sql语句的结构,然后找到匹配的sql语句,传进参数。
相对比Statement,就算是同一sql语句,也会存在由于参数不同,需要重新编译才能被执行。
2. 批量处理的实现:
Statement 和 Preparement对象都可以实现批量处理操作;
方法: addBatch(String sql)
A . PrepraedStatement对象对sql语句有预编译过程,和Statement对象逐条编译的过程对比,显著提高了程序的运行效率。
前提: 因为其预编译的特性, 只能应用在SQL语句相同,但参数不同的批处理中。
一般用于在同一个表中批量插入数据,或者批量更新数据。(基本满足需求了)
B. Statement对象也可以实现批处理操作,且可实现一批不同sql语句的添加或更新。
但是,因为需要逐条编译,降低了程序的运行效率,如果没有特殊需求,实际开发中不推荐使用statement对象进行批处理。
具体的实现以后之后涉及。
3. 处理带未知参数的sql语句:
PreparedStatement对象提供了setString(int index, String value)、setInt(int index,Int value)……给未知参数设置值的方法。
原理:在预处理sql语句之后再设置参数的值,很大程度的提高了开发的便捷性。
便捷性主要体现:
1.可读性: 编写sql语句的时候,不用变量代入; 先设置?参数,再在预编译后,通过set方法设置参数值; 实现分开处理。2.维护性: 一目了然,参数和设置的值有存在对应键值对的关系,相互对应。 修改的时候,根据?参数的顺序,找到对应的set语句中的index,即可马上找到。
4. 具有安全性防止sql注入攻击:
恶意语句:
绕过输入验证,误导数据库识别为正常的sql语句,使数据库执行后导致数据库数据被破坏。
恶意注入成功的原理:
针对Statement对象的编译过程,投机取巧的方式。
一般情况,通过Statement对象操作数据库的整个过程:
先从前端获取用户的输入值;
输入值保存到sql语句中的变量;
Statement对象对sql语句进行处理(编译);
数据库执行sql语句
投机取巧的关键点:
Statement对象对sql语句的编译过程,直接把保存了前端输入的变量值,以字符串的形式拼接到sql语句后就进行编译。
即用户输入什么,数据库都会执行。
这样的后果就是,用户可以通过在输入的内容上拼接sql语句从而误导数据库。
比如:
在学生成绩管理系统中,学生根据学号和密码查询个人成绩。
此时,在JDBC中对应的sql语句:
//stu_id 是number类型 , stuPassword 是varChar2()类型
String sql = "select grade from students where stu_id =" + stuId + " and password ="+"'"+stuPassword+"'";
查询功能正常的情况:
假设有学生Tom,输入了学号10000,密码123456。
如果输入的数据能成功匹配数据库表中该同学的学号字段和密码字段,返回查询成果;
如果输入的数据不能成功匹配数据库表中该同学的学号字段和密码字段,返回查询失败。
sql注入攻击的情况:
假设成功的情况:学号10000,密码123456
此时输入学号:10000, 密码:xx’ or ‘1’=’1;
JDBC定义的sql语句:
String sql = "select grade from students where stu_id =" + stuId + " and password ="+"'"+xx' or '1'='1+"'";
分析此时的sql语句:
关键点密码字符串的拼接处 。
Statement将sql编译后,发送给数据库时的语句是:
select grade from students where stu_id = 10000 and password ='xx' or '1'='1'
该sql语句是没有语法错误,能被数据库识别且执行。
此时,password的值为 xx ,显然,密码不对;
但是,后面拼接了一个 or/或 关键字,or 后面的条件筛选语句 “1” = “1 ” 为true;
A or B ,当A,B都为false的时候,返回false;当只要其中的一个为true,则返回true
“1”=“1”为true - - 因此,此sql语句是恒成立的。
结论:
即使密码输入不对,也能使数据库成功返回查询的结果;
此时的数据库是不安全的,没通过验证就能成功操作数据库中的数据。
PreparedStatement对象具有安全性的原理
预编译实现的过程
一般情况,通过PreparedStatement对象操作数据库的整个过程:
先从前端获取用户的输入值;
PreparedStatement对象对sql语句进行处理(预编译);
输入值通过set方法依次赋值给?参数;
数据库执行sql语句。
关键点:先预编译,后给参数赋值。 - - 赋值的方式不再是简单的字符串拼接
预编译后,已经找到了匹配的sql语句,之后不允许再改变sql语句的结构;
如果传入的参数改变了原有的sql语句结构,发生匹配失败,数据库执行失败,没有匹配成功的结果。
个人建议:
一般情况下,不管从什么方面考量,优先选择使用PreparedStatement对象。
(3)CallableStatement 接口
用于执行对数据库已存在的存储过程的调用。
存储过程: 一个可编程的函数,在数据库中创建并保存.
特点:
a. 继承自PreparedStatement接口,有处理一般sql语句的方法,也有处理PreparedStatement对象处理带参数的sql语句的方法
b. CallableStatement对象添加了处理 OUT 参数的方法。–OUT参数:已储存过程的返回值
用继承自preparedStatement的set方法处理IN参数(未知参数),用自身的get方法检索OUT参数(输出结果参数),并返回是否为 JDBC NULL的值。
CallableStatement对象并不常用,一般在调用已存储过程的情况下才考虑。
PreparedStatement接口在实际编程中的应用
PreparedStatement接口一般情况下,优先选择。本文也主要已学习总结prepareStatement接口为例
建立在已创建数据库连接的前提上:
//作用: 预处理(此处为预编译)sql语句,并发送sql语句到数据库中
PreparedStatement pstmt = conn.preparedStatement(sql);
//执行sql语句,并返回执行结果
//返回一个查询结果集
ResultSet rs = pstmt.executeQuery();
//返回一个数据库中受影响行数
int count = pstmt.executeUpdate();
3.ResultSet接口
概念:
数据库结果集的数据表
作用:
1.保存数据库查询后的结果集
2.提供多种对结果集进行操作的方法
提供的方法:
1.用于遍历输出结果集 – 结果集保存了多行查询数据
next方法: – – 最常用
ResultSet 对象具有指向其当前数据行的指针; 默认情况下,指针被置于第一行之前; next 方法将指针移动到下一行;
没有下一行时返回 false。可以通过 while 循环中使用next()方法来迭代结果集。(输出每一行的查询结果) 如果是单行查询(结果集只有一条数据),可以通过 if 判断中使用next()来输出查询的单行数据
2.用于定位结果集 - - 常用的是next()方法,其他不推荐使用, 部分数据库不支持这些方法。 比如: abosolute (int row)、relative(int
row)、 next()、first()、last()、previous()、isFirst()、isLast()……3.对结果集进行操作 - - 不常用,也不推荐使用 比如:
insertRow()、 updateRow()、 deleteRow()、updateXXX()……
ResultSet接口在编程中的应用:
//作用:保存执行查询的sql句后返回的数据(多条数据,是一个结果集)
ResultSet rs = pstmt.executeQuery();
//作用:迭代输出结果集
//从结果集中获取下一行(默认初始指针指向第一行数据的上一行)
while(rs.next()){ // -- 获取当前行数据(即当前的rs对象)
//按sql字段查询顺序逐一获取当前行的字段(也可以直接通过字段名获取)
String stuName = rs.getString(1);
String account = rs.getString(2);
//直接通过字段名获取
String password = rs.getString("password");
}
Connection、PreparedStatement、ResultSet 关系小结:
JDBC简单的实现
一、导入JDBC包
目前,普遍导入ojdbc6.jar包
具体操作(oracle举例):
1.下载驱动
两种方式:
(1)网上下载
(2)复制在oracle安装目录里product/11.1.0/datebase_1/jdbc/lib 的ojdbc6.jar包
2.把驱动包添加classpath类路径中
(1)把ojdbc6.jar拉到eclipse左侧项目空白处
(2)右键选择build path,再选择 add to build path
编写基础类
功能:实现对客户信息的增删查改
//创建类customer:
import java.util.Date;
public class Customer {
//编号int
private int userId;
//账号String
private String account;
//密码String
private String password;
//邮箱String
private String email;
//电话String
private String phone;
//注册时间Date
private Date regDate;
//显示空构造函数
public Customer(){
}
//定义构造函数
public Customer(int userId, String account, String password, String email, String phone, Date regDate) {
this.userId = userId;
this.account = account;
this.password = password;
this.email = email;
this.phone = phone;
this.regDate = regDate;
}
}
二、定义四大参数
一般情况,参数声明为静态的,私有的,不可变的。
//Oracke数据库举例
//数据库用户名
private static final String USER = "user";
//数据库登陆密码
private static final String PASSWORD = "123456";
//oracle驱动类名 (oracle数据库固定不变)
private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
//oracle数据库连接地址(jdbc包的版本不同,地址可能不同)
private static final String URL = "jdbc:oracle:thin://localhost:1521/orcl";
三、注册、加载驱动类
Class.forName(DRIVER);
四、打开数据库连接通道
Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
五、预处理sql语句
定义sql语句:
//查询customer表中的全部数据。(从性能上,查全部字段尽量不使用*)
String sql = "secect user_id,user_account,user_password,user_email,user_phone,register_date from customer";
发送sql语句到数据库
PreparedStatement pstmt = conn.prepareStatement(sql);
六、执行sql语句
ResultSet rs = pstmt.executeQuery();
七、处理结果集
//数据库中每一行的数据对应每一个客户的信息
//定义一个集合,保存所有客户信息
List<Customer> list = new ArrayList();
//ResultSet对象的next()方法:读取下一行数据
//没有数据返回false,有数据返回true,指针下移一行
while(rs.next()){
//从结果集中获取数据
//有两种方法:
//根据列的序号进行获取
//根据列名或列的别名进行获取(有别名的用别名,没别名的用列名)
//根据列的序号进行获取:
//下面的1,2,3,4,5,6分别对应上述编写sql语句中各个字段的序号
int userId = rs.getInt(1);
String userAccount = rs.getString(2);
String email =rs.getString(3);
String password =rs.getString(4);
String phone =rs.getString(5);
//getTimestamp()方法精确到年月日时分秒
Date regDate = rs.getTimestamp(6);
//各个字段的值保存到了相应的变量后,生成一个customer对象
Customer customer = new Customer(userId,userAccount,password,email,phone,regDate);
//每生成一条客户信息,就添加到事先准备好的集合中
list.add(customer);
}
八、关闭连接
出于节省资源开销的角度,连接的打开是需要关闭的
共有三种连接需要关闭,即三个接口:Connection,PrepraedStatement,ResultSet
关闭连接的顺序有要求:
原则:先开后关,后开先关
//关闭ResultSet接口
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭PrepraedStatement接口
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭Connection接口 (关闭数据库连接)
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
增加客户信息简单实现
//注册、加载驱动类
Class.forName(DRIVER);
//建立数据库连接
Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
//定义sql语句
String sql = "insert into customer(user_id,user_account,user_password,user_email,user_phone,register_date) values(CUSTOMER_ID.NEXTVAL,?,?,?,?,?)";
//预处理带未知参数的sql语句
PreparedStatement pstmt = conn.preparedStatement(sql);
//给未知参数逐一赋值,同获取原则,有两种赋值方式
//根据列的序号进行赋值
//根据列名或列的别名进行赋值(有别名的用别名,没别名的用列名)
//根据列的序号进行赋值:
//下面的1,2,3,4,5,6分别对应上述编写sql语句中各个?参数的序号
//要增加到数据库中的客户信息
//此处只需初始化4个参数(id是数据库序列号自动生成,注册时间是获取数据库系统当前时间)
Customer customer = new Customer("test","123456","123@qq.com","13912345678");
//给数据库字段account赋值
pstmt.setString(1, customer.getAccount());
//给数据库字段password赋值
pstmt.setString(2, customer.getPassword());
//给数据库字段email赋值
pstmt.setString(3, customer.getEmail());
//给数据库字段phone赋值
pstmt.setString(4, customer.getPhone());
//执行sql语句,返回受影响行数
int count = pstmt.excuteUpdate();
//如果有受影响行数,即count>0,说明sql语句被成功执行了
if(count > 0){
System.out.println("成功新增客户信息到数据库中!");
}
//关闭连接
//不需要关闭rs接口(没有打开过)
//关闭PrepraedStatement接口
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭Connection接口 (关闭数据库连接)
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//特别说明:如果需要插入指定时间,时间需要用Timestamp进行插入
//不使用pstmt.setDate(setDate的时间只保存年月日,不保存时分秒)
pstmt.setTimestamp(5, new Timestamp(customer.getRegDate().getTime()));
以上简单实现只是理论上的实现。
没有添加异常处理,实际编程中系统会报出异常提示,点击处理即可。
会在之后的博客中,学习总结如何拓展JDBC的应用
包括:
1.封装成工具类
2.采用sql语句拼接
3.实现事务控制
总结
1.定义四大参数
1.DRIVER :数据库驱动类名
2.URL :数据路连接地址
3.USER :数据库登陆用户名
4.PASSWORD :数据库登陆密码
2.注册、加载驱动
Class.forName(DRIVER)
3.建立数据库连接
Connection conn = DriverManager(URL,USER,PASSWORD)
4.预处理sql语句
String sql = "";
PrePraedStatement pstmt = conn.prepraedStatement(sql);
5.执行sql语句
ResultSet rs = pstmt.executeQuery()
/ int count = pstmt.executeUpdate()
6.处理结果集
while(rs.next()){
……
}/ if(count > 0){
……
}
7.关闭连接
//关闭ResultSet接口
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭PrepraedStatement接口
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭Connection接口 (关闭数据库连接)
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}