目录
一、JDBC
1.1.为什么要使用JDBC?
我们所使用的编程语言是Java,而在Java中是不存在SQL语言的,那么我们如果使用Java来操作和链接数据库呢?
JDBC就是我们Java公司提出的用来规范客户端程序访问数据库的API接口,也就是说,我们想要在Java中连接使用数据库,就要使用JDBC。
1.2.JDBC的实现
所谓的JDBC可以理解成一套规范,是由Java提出,让各大数据库厂商自己实现的一套规范,当实现了这个规范的时候,其数据库就可以和Java进行连接。
从事实上讲,各大厂商有自己的运行逻辑,不同的运行逻辑是需要不同的实现方式的,Java不可能为各大厂商提供不同的规范,这是较为麻烦的;而如果我们提供一种规范出来,让数据库厂商去实现这个标准,我们只需要将其实现的jar包加载即可使用,就会方便很多。
1.3.JDBC的常用接口
1.3.1.接口路径
Java中提供的接口在java.sql.*包下。其中的常用接口为
接口名称 | 作用 |
java.sql.Connection | 连接 |
java.sql.Statement | 静态处理块 |
java.sql.PreparedStatemen | 预处理块 |
java.sql.ResultSet | 结果集 |
java.sql.ResultSetMetaData | 结果集元信息 |
1.3.2.Oracle厂商的实现接口
F:\app\Administrator\product\11.2.0\dbhome_1\jdbc\lib\ojdbc6.jar 视安装路径而定
1.4.JDBC的基本流程
准备工作[以IDEA举例]
引入驱动包
前面已经说过了,我们的JDBC的具体实现是由数据库厂商来进行实现的,所以,我们需要将数据库厂商实现的包导入到项目中。
1.下载Oracle的jar包
2.在项目中创建一个lib文件夹
3.将我们下载好的jar包复制到lib文件夹中,选中,右击,点击as a library
1.4.1.加载驱动
驱动:oracle.jdbc.driver.OracleDriver
我们所谓的加载驱动,就是将我们想要的驱动在项目中进行加载,加载就是类被使用,当我们的类被使用的时候就会自动加载。
使用类,我们很容易就想到了创建实例化对象。
硬编码:new oracle.jdbc.driver.OracleDriver();
new oracle.jdbc.driver.OracleDriver();
但是,其实我们通过new关键字创建一个实例化的方式来加载是不合理的,因为,我们本质上,只是想要将驱动加载而已,也就是说,要加载的是某个类,而new关键字则是根据这个类来创建了实例,浪费了内存空间。
那么,我们通过什么方式才可以只加载类,不创建实例对象呢?
反射!!我们反射的第一步就是得到类对象,此时的类就是一个被加载的状态。
软编码:Class.forName("oracle.jdbc.driver.OracleDriver");
Class.forName("oracle.jdbc.driver.OracleDriver");
1.4.2.建立连接
通过第一步我们将要链接数据库的驱动加载到了项目中,此时,我们就可以通过规定好的方法来与数据库建立链接了。
所谓的建立连接就是获取一个Connection类型的对象,通过DriverManager工具来连接,DriverManager是java.sql包下的一个API,Java规定了我们需要通过其来与数据库建立连接。
使用方法:
static Connection getConnection(String url)
尝试建立与给定的数据库URL的连接
static Connection getConnection(String url,String user,String password)
尝试建立与给定的数据库URL的连接
static Connection getConnection(String url,Properties info)
尝试建立与给定的数据库URL的连接
基础语法:
Connection conn = DriverManager.getConnection(参数一,参数二,参数三);
参数一:要连接的哪种数据库 "jdbc:oracle:thin:@localhost:1521:XE"
依据连接的数据库的种类不同来选择不同的字符串
参数二:用户名 "SCOTT"
在要连接的数据库中,我们使用的是哪个用户进行链接
参数三:密码 "TIGER"
用户对应的密码
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "SCOTT", "TIGER");
1.4.3.准备SQL语句
建立好连接,我们就需要通过连接将我们要执行的SQL语句传递给数据库来让其执行。但是在传输之前,我们需要先将我们想要执行的SQL语句准备好。
String sql = "select * from emp";
1.4.4.封装处理块
对于我们准备好的SQL语句,我们不能直接将其传给服务器,这是因为,我们的SQL语句在Java中是以字符串的形式存储的,如果我们直接传给服务器,那么他就仅仅是一个字符串,而不是作为一个SQL语句,我们需要将其封装到处理块中,告诉服务器,我们这个字符串是一个SQL语句。
封装处理块,就是制造一个发送SQL语句的发送器,在Java中是Statement类型的一个实例。Java中可以通过我们前面所建立的连接Connection来创建我们想要的处理块。
Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的 SQL 语句。Statement 对象,用于执行不带参数的简单 SQL 语句。
执行静态 SQL 语句并返回它所生成结果的对象。
使用方法:
Statement createStatement()
创建一个Statement对象,该对象用于将SQL语句发送到数据库
基本语法:
Statement sta = conn.createStatement();
Statement sta = conn.createStatement();
1.4.5.得到结果集
我们的发送SQL语句的准备工作已经全部完成,此时我们就需要将我们的SQL传输到服务器端,来让服务器帮助我们与数据库进行交流,得到查询结果。
发送SQL,前面我们得到了发送SQL语句的Statement对象,现在我们需要通过这个对象来将我们的SQL发送给数据库并得到返回结果。
使用方法:
DDL数据定义语言:boolean execute(String ddl语句) ----- 通常不会在代码中执行
DML数据操控语言:int executeUpdate(String dml语句) ----- 返回被影响行数
DQL查询语句:ResultSet executeQuery(String sql)
执行给定的SQL语句,该语句返回一个ResultSet对象
基本语法:
ResultSet res = sta.executeQuery(sql);
ResultSet res = sta.executeQuery(sql);
1.4.6.处理结果
上一步我们就已经拿到我们查询出来的数据了,我们可以对这些数据进行处理,这里的处理逻辑与实际需求相关,这里以输出为例。
实际查询出来的数据一般来说是多条数据,所以我们需要通过循环拿出每一条结果。
我们拿到的结果是以每条记录为基本单位的,也就是说,ResultSet中的一个元素就是我们查询的一整条记录。
但是,一般来说,我们实际操作的是一条记录中的某一列或某几列,那么我们如何获取到我们想要的列的信息呢?
我们可以通过ResultSet中的getXxx方法来获取单独某列的数据。
getXxx方法Xxx是以我们要获取的那一列的数据类型来确定,当我们无法确定,就使用Object
该方法需要一个参数,参数是以我们想要查询的是第几列的数据信息来确定的,假设,我们想要处理的是该条记录的第一列的数据,那么我们的参数就是1
while (res.next()) {
System.out.print(res.getObject(1) + "\t");
System.out.print(res.getString(2) + "\t");
System.out.print(res.getString(3) + "\t");
System.out.print(res.getInt(4) + "\t");
System.out.print(res.getString(5) + "\t");
System.out.print(res.getDouble(6) + "\t");
System.out.print(res.getDouble(7) + "\t");
System.out.print(res.getInt(8) + "\t");
System.out.println();
}
1.4.7.关闭资源
res.close();
sta.close();
conn.close();
1.4.8.特点总结
1.处理不变的静态的SQL语句
2.可以直接查看SQL,方便处理错误
3.性能低,拼接SQL语句也较为麻烦,可能存在SQL注入的现象
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
Class.forName("oracle.jdbc.driver.OracleDriver");
//2.建立连接
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","SCOTT","TIGER");
//3.准备sql:不能写分号的话,是否意味着,一个String字符串,只能写一条查询语句。
String sql = "select * from emp";
//4.获取封装处理快
Statement sta = conn.createStatement();
//5.发送sql:通过建立好的连接
ResultSet res = sta.executeQuery(sql);
//6.处理数据
while (res.next()) {
System.out.print(res.getObject(1) + "\t");
System.out.print(res.getString(2) + "\t");
System.out.print(res.getString(3) + "\t");
System.out.print(res.getInt(4) + "\t");
System.out.print(res.getString(5) + "\t");
System.out.print(res.getDouble(6) + "\t");
System.out.print(res.getDouble(7) + "\t");
System.out.print(res.getInt(8) + "\t");
System.out.println();
}
//7.关闭资源
res.close();
sta.close();
conn.close();
1.5.对JDBC的优化
1.5.1.异常的捕获
在JDBC运行的整个流程中,有一些代码可能会有异常的出现,我们可以使用try...catch...对这些异常进行捕获处理。
分析:
1.除开第三步,准备sql语句外,其余步骤都会有异常
2.第一步,加载驱动的异常与其余步骤异常不同,需要自行捕获
3.第二步到第七步的异常是同一个,我们可以选择将代码都写到同一个try中使用一个catch进行捕获;
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//2.建立连接
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","SCOTT","TIGER");
//3.准备sql:不能写分号的话,是否意味着,一个String字符串,只能写一条查询语句。
String sql = "select * from emp";
//4.获取封装处理快
Statement sta = conn.createStatement();
//5.发送sql:通过建立好的连接
ResultSet res = sta.executeQuery(sql);
//6.处理数据
while (res.next()) {
System.out.print(res.getObject(1) + "\t");
System.out.print(res.getString(2) + "\t");
System.out.print(res.getString(3) + "\t");
System.out.print(res.getInt(4) + "\t");
System.out.print(res.getString(5) + "\t");
System.out.print(res.getDouble(6) + "\t");
System.out.print(res.getDouble(7) + "\t");
System.out.print(res.getInt(8) + "\t");
System.out.println();
}
//7.关闭资源
res.close();
sta.close();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
问题一:
我们将第二步以后的所有步骤都放入了try中,那么,依据try的机制,当我们遇到异常的时候,从出现异常的语句开始,其后的语句都不会执行,也就导致了,我们的关闭资源语句不会执行,有可能会对其余执行程序造成影响。
解决:
我们可以把关闭资源的语句放入finally中,那么,不论是否出现异常,关闭资源的语句都会执行,此时,就不会再会出现上面的问题了。
注意:
在将关闭资源语句挪到finally中不要忘记异常的重新捕获
问题二:
当我们将关闭资源的语句放入到finally中之后,我们发现报红
分析:
这是因为,我们的变量的声明此时还是在try块中,导致,我们的finally发现自己的作用域中没有这些资源,父级到根作用域中都找不到要关闭的资源。
解决:
所以我们需要将资源的变量声明进行提前。
注意:
我们在声明的时候不要忘记赋值:初始值null。因为我们要保证其在被使用的时候一定被初始化。
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2.建立连接
//声明提前
Connection conn = null;
Statement sta = null;
ResultSet res = null;
try {
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","SCOTT","TIGER");
//3.准备sql:不能写分号的话,是否意味着,一个String字符串,只能写一条查询语句。
String sql = "select * from emp";
//4.获取封装处理快
sta = conn.createStatement();
//5.发送sql:通过建立好的连接
res = sta.executeQuery(sql);
//6.处理数据
while (res.next()) {
System.out.print(res.getObject(1) + "\t");
System.out.print(res.getString(2) + "\t");
System.out.print(res.getString(3) + "\t");
System.out.print(res.getInt(4) + "\t");
System.out.print(res.getString(5) + "\t");
System.out.print(res.getDouble(6) + "\t");
System.out.print(res.getDouble(7) + "\t");
System.out.print(res.getInt(8) + "\t");
System.out.println();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//7.关闭资源
try {
res.close();
sta.close();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
问题三:
此时,我们的代码在运行阶段仍会出现一个问题,当运行过程中,在第二步到第六步中出现异常,会导致后面的语句不再执行,这会使得,我们有些资源在关闭的时候仍为null,没有关闭资源的必要。
解决:
我们可以对三个资源进行判断,当资源不为null,就说明资源有了具体的值,此时,再去关闭资源。
//7.关闭资源
try {
if (res != null) {
res.close();
}
if (sta != null) {
sta.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
问题四:
虽然我们进行了判断,但是我们此时还是将三个关闭资源的语句,放在同一个try块中,如果我们前面两个资源中有一个发生异常,就会导致后面的资源也无法关闭
解决:
我们可以将三个关闭资源的语句分开编写到不同的try...catch...块中,判断也分开编写,此时,不仅会解决我们关闭资源出错的问题,更会让我们的关闭资源更清晰。
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2.建立连接
Connection conn = null;
Statement sta = null;
ResultSet res = null;
try {
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE","SCOTT","TIGER");
//3.准备sql:不能写分号的话,是否意味着,一个String字符串,只能写一条查询语句。
String sql = "select * from emp";
//4.获取封装处理快
sta = conn.createStatement();
//5.发送sql:通过建立好的连接
res = sta.executeQuery(sql);
//6.处理数据
while (res.next()) {
System.out.print(res.getObject(1) + "\t");
System.out.print(res.getString(2) + "\t");
System.out.print(res.getString(3) + "\t");
System.out.print(res.getInt(4) + "\t");
System.out.print(res.getString(5) + "\t");
System.out.print(res.getDouble(6) + "\t");
System.out.print(res.getDouble(7) + "\t");
System.out.print(res.getInt(8) + "\t");
System.out.println();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//7.关闭资源
try {
if (res != null) {
res.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (sta != null) {
sta.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
1.5.2.采用配置文件
对于我们JDBC中经常修改或改动的内容,我们可以将其定义到配置文件中,来进行使用,实现软编码。
定义在配置文件中,我们想要修改的时候就可以直接找到配置文件并在配置文件中进行修改,不再需要去代码中寻找编写的位置了。
1.分析JDBC中的经常修改的内容
1.1.要连接的数据库类型:驱动
1.2.建立的连接的三个参数
2.在项目目录下的src目录下创建一个配置文件(properties文件):
3.在配置文件中编写配置内容(键值对形式)
4.在代码中使用配置文件
4.1.获取properties对象
//properties
Properties db = new Properties();
4.2.通过properties对象来连接配置文件
使用方法:
Properties类中:
void load(InputStream inStream) 从输入字节流中读取属性列表(键和元素对)
void load(Reader reader)
以简单的面向行的格式从输入字符流中读取属性(键和元素对)
ClassLoader类中:
InputStream getResourceAsStream(Strinig name) 返回用于读取指定资源的输入流
Thread类中:
ClassLoader getContextClassLoader() 返回此线程的上下文ClassLoader
static Thread currentThread() 返回当前正在执行的线程对象
//连接配置文件
try{
db.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
4.3.将过程中所用到的地方进行替换
使用方法:
String getproperty(String key) 在此属性列表中搜索具有指定键的属性
这里的属性列表,就是我们配置文件中的信息
key则是我们配置信息中前面的键
4.3.1.加载驱动
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
try {
Class.forName(db.getProperty("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
4.3.2.建立连接
//2.建立连接
conn = DriverManager.getConnection(db.getProperty("url"), db.getProperty("username"), db.getProperty("password"));
1.5.3.采用预处理块代替普通处理块
为什么要采用预处理块?
普通处理块在运行阶段有可能造成sql注入的现象,这会导致数据的安全问题。就像是用户的登录,在普通处理块中,我们的sql语句的编写是这样的:
String sql = "select * from t_user where username='"+username+"' and password = "+password;
此时,如果我们输入的用户名username为 ' or 1 = 1 --,此时就会导致我们的sql语句最终呈现的是:
select * from t_user where username='' or 1 = 1 --' and password = password;
我们依照SQL语言的执行逻辑看就会发现,此时,我们实际传给数据库的SQL是一定会通过验证的,这就是SQL注入。
使用预处理块就可以避免这种SQL注入的出现。
什么是预处理块?
所谓的预处理块就是将SQL语句中的值用占位符替代,预先编译,最后将真正的值替换来达成我们想要的SQL拼接。
预处理块的使用
1.修改SQL语句
//2.准备sql
String sql = "select * from t_user where username=? and password =? ";
2.将封装处理块的方式改为预处理块,并赋值
//3.封装预处理块
PreparedStatement state = conn.prepareStatement(sql);
//为sql中的?赋值
state.setObject(1,username);
state.setObject(2,password);
注意:
实际上,我们也可以通过,1.检查变量的数据类型和格式;2.过滤特殊符号的方式来来避免sql注入的情况,但是这两种方式都比较麻烦,且并不一定能解决所有的sql注入。
1.6.JDBC的封装
1.6.1.分析代码
在代码中,我们可以发现,第1步加载驱动和第2步获取连接对于所有的程序都是通用的,第七步的关闭资源也是通用的,只有第3步到第6步则会根据我们查询的表的不同,以及SQL语句的不同来变得不同,并不是固定的语句。
按照封装的思想,我们可以封装两个方法,一个是建立连接的;一个是关闭资源
1.6.2.开始封装
1.在项目文件家中创建一个包module用来存放我们封装的模块
2.在module包创建我们的封装类,JDBC
3.封装建立连接方法,静态方法,方法名为receiver,方法返回值是一个Connection类型的对象,不需要参数。
public static Connection receiver() throws SQLException {
//properties
Properties db = new Properties();
db.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
return DriverManager.getConnection(db.getProperty("url"), db.getProperty("username"), db.getProperty("password"));
}
问题一:
此时,我们发现封装的receiver方法中的Properties对象每次创建连接都会创建一个新的对象,但是,实际上,我们只需要创建一个对象,每次连接的时候通过这个创建好的对象来建立连接即可。
解决:
我们可以将Proerties对象的创建封装到类的实例化成员变量中,这样的话,我们每次调用方法的时候都可以通过这个属性来建立连接。
问题二:
虽然,我们将对象变为类的属性来保存了,但是,我们对象与文件之间建立连接不可以直接写到类体中
解决:
我们可以通过定义静态块的方式,将对象与文件之间的连接通过静态块的方式来使用
static Properties db = new Properties();
static {
//连接配置文件
try {
db.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
//1.加载驱动,仅需加载,所以通过反射,创建一个类对象即可
try {
Class.forName(db.getProperty("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection receiver() throws SQLException {
return DriverManager.getConnection(db.getProperty("url"), db.getProperty("username"), db.getProperty("password"));
}
4.封装关闭资源的方法,静态方法,没有返回值,方法名为close,参数为ResultSet res, Statement sta, Connection conn
public static void close(ResultSet res, Statement sta, Connection conn) {
if (res != null) {
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (sta != null) {
try {
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5.至此,我们的JDBC就封装完成了,当我们想要使用封装的JDBC的时候,直接通过类名调用即可