Java-JDBC技术(概念及应用)

一、JDBC简介


1.JDBC的工作原理


JDBC的工作原理如下图所示:

Java-JDBC技术(概念及应用)_第1张图片

从图中可以看到JDBC的几个重要组成要素。最顶层是我们自己编写的Java应用程序(Java Application),Java应用程序可以使用集成在JDK中的java.sqljavax.sql包中的JDBC API来连接和操作数据库。下面我们从上到下的顺序依次讲解JDBC的组成要素:

1.JDBC API
JDBC API由Sun公司提供,提供了Java应用程序与各种不同数据库交互的标准接口,如Connection(连接)接口,Statement接口,ResultSet(结果集)接口,PreparedStatement接口等。开发者使用这些JDBC接口进行各种数据库操作。

2.JDBC Driver Manager
JDBC Driver Manager(驱动程序管理器)由Sun公司提供,它是JDBC体系结构的支柱,负责管理各种不同的JDBC驱动,把Java应用程序连接到相应的JDBC驱动程序上,位于JDK的java.sql包中。

3.JDBC驱动
JDBC驱动由各个数据库厂商或第三方中间件厂商提供,负责连接各种不同的数据库。例如,上图中,访问MySQLOracle时需要不同的JDBC驱动,这些JDBC驱动都实现了JDBC API中定义的各种接口。
在开发Java应用程序时,我们只需正确加载JDBC驱动,正确调用JDBC API,就可以进行数据库访问了。


2.JDBC API介绍


JDBC API主要做三件事:与数据库建立连接、发送SQL语句、处理结果。
如下图所示:
Java-JDBC技术(概念及应用)_第2张图片

上图为我们介绍JDBC的工作过程,同时也展示了JDBC API的作用。

  • DriverManager类:装载驱动程序,并为创建新的数据库连接提供支持。
  • Connection接口:负责连接数据库并担任传送数据的任务。
  • Statement接口:由Connection产生,负责执行SQL语句。
  • ResultSet接口:负责保存和处理Statement执行后所产生的查询结果。
  • PreparedStatement接口:Statement的子接口,也由Connection产生,同样负责执行SQL语句。与Statement接口相比,PreparedStatement接口具有高安全性、高性能、高可读性和高可维护性的优点。

3.JDBC访问数据库的步骤


1.加载JDBC驱动
使用Class.forName()方法将给定的JDBC驱动类加载到Java虚拟机中。若系统中不存在给定的类,则会引发异常,异常类型为ClassNotFoundException。代码示例:

Class.forName("JDBC驱动类的名称");

2.与数据库建立连接

DriverManager类是JDBC的管理层,作用于用户和驱动程序之间。 DriverManager类跟踪可用的驱动程序,并在数据库和相应的驱动程序之间建立连接。当调用getConnection()方法时, DriverManager类首先从已加载的驱动程序列表中找到一个可以接收该数据库URL的驱动程序,然后请求该驱动程序使用相关的URL、用户名和密码连接到数据库中,于是就建立了与数据库的连接,创建连接对象并返回引用。代码示例:

Connection conn = DriverManager.getConnection(数据连接字符串,数据库用户名,密码);

3.发送SQL语句,并得到返回结果
一旦建立连接,就使用该连接创建Statement接口的对象,并将SQL语句传递给它所连接的数据库。如果是查询操作,将返回类型为ResultSet的结果集,它包含执行SQL查询的结果。如果是其他操作,将根据调用方法的不同返回布尔值或操作影响的记录数目。代码示例:

Statement stmt =  conn.createStatement();

ResultSet rs = stmt.executeQuery("SELECT `id`, `name` FROM `master`");

4.处理返回结果
处理返回结果主要是针对查询操作的结果集,通过循环取出结果集中每条记录并做相应处理。
处理解雇的代码示例:

while(rs.next()){
	int id = rs.getInt("id");
	String name = rs.getString("name");
	System.out.println(id + "    " + name);
}

一定要明确使用JDBC的四个基本步骤,接下来是使用这四个步骤实现对数据库的各种访问。


二、连接数据库


1.两种常用的驱动方式

1.JDBC-ODBC桥连方式
2.纯Java驱动方式
推荐使用第2个:纯Java驱动方式!


2.JDBC-ODBC桥连方式连接数据库

由于不推荐,故先省略,有时间补上

3.纯Java方式连接数据库


纯Java驱动方式由JDBC驱动直接访问数据库,驱动程序完全用Java语言编写,运行速度快,且具备了跨平台特点。但是,由于技术资料的限制,这类JDBC驱动一般只能由数据库厂商自己提供,即这类JDBC驱动只对应一种数据库,甚至只对应某个版本的数据库,如果数据库更换了或者版本升级了,一般需要更换JDBC驱动程序。纯Java驱动方式的工作原理如图1所示。

如果我们使用纯Java驱动方式进行数据库连接,首先需要下载数据库厂商提供的驱动程序jar包,并将jar包引入工程中。我的数据库版本是MySQL5.5,导入mysql-connector-java-5.1.0-bin.jar包,导入后结果如图2所示。

查看相关帮助文档后,获得驱动类的名称及数据库连接字符串。下面我们需要在MySQL中建立数据库,此处命名为epet。在epet数据库中建立表dog(狗狗)和 master(宠物主人),并插入若干记录,创建代码如下所示:

#创建数据库
CREATE DATABASE `epet`CHARACTER SET utf8 COLLATE utf8_bin; 

#创建dog表
CREATE TABLE `epet`.`dog`( 
`id` INT NOT NULL AUTO_INCREMENT COMMENT '序号', 
`name` VARCHAR(12) COMMENT '昵称', 
`health` INT COMMENT '健康值', 
`love` INT COMMENT '亲密度', 
`strain` VARCHAR(20) COMMENT '品种', 
PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_bin; 

#创建master表
CREATE TABLE `epet`.`master`( 
`id` INT NOT NULL AUTO_INCREMENT COMMENT '序号', 
`name` VARCHAR(12) COMMENT '姓名', 
`password` VARCHAR(20) COMMENT '密码', 
`money` INT COMMENT '元宝数', 
PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_bin; 

接下来就可以进行编程,与数据库建立连接。
需要连接的数据库名为epet,创建本地用户名为root,密码为admin
使用该数据库用户登录并访问epet数据库,具体实现代码如下所示:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Logger;

public class Connect {
	public static void main(String[] args) {
		Connection conn = null;
		// 1.加载驱动
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		// 2.建立连接
		try {
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			System.out.println("建立连接成功!");
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 3.关闭连接
			try {
				if (conn != null) {
					conn.close();
					System.out.println("关闭连接成功!");
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

注意:常见的错误有以下几类

  • JDBC驱动类的名称书写错误,出现ClassNotFoundException异常。
  • 数据连接字符串、数据库用户名、密码书写错误,出现SQLException异常。
  • 数据库操作结束后,没有关闭数据库连接,导致仍旧占有系统资源。
  • 关闭数据库连接语句没有放到finally语句块中,导致语句可能没有被执行。

三、Statement接口ResultSet接口

获取Connection对象后就可以进行各种数据库操作了,此时需要使用Connection对象创建Statement对象,Connection接口长荣方法如下表所示

方法名称 作用
void close() 立即释放此Connection对象的数据库和JDBC资源
Statement CreateStatement() 创建一个Statement对象来将SQL语句发送到数据库
PreparedStatement prepareStatement(String sql) 创建一个PreparedStatement对象来将参数化的SQL语句发送到数据库
boolean isClosed() 查询此Connection对象是否已经被关闭

Statement对象用于将SQL语句发送到数据库中,可以理解为执行SQL语句。Statement接口中包含很多基本数据库操作方法,下表中列出Statement接口执行SQL命令的三个常用方法:

方法名称 作用
ResultSet executeQuery(String sql) 可以执行SQL查询并获取ResultSet对象
int execute Update(String sql) 可以执行插入、删除、更新操作,返回值是执行该操作所影响的行数
boolean execute(String sql) 可以执行任意SQL语句,若结果为ResultSet对象,则返回true;若其为更新计数或者不存在任何结果,则返回false

1.使用Statement添加宠物

添加狗狗信息到epet数据库,操作很简单,只要创建Statement对象然后调用 execute (String sql)方法或者 executeupdate (String sql) 方法即可。这里关键是SQL语句的拼接,可以直接利用+运算符进行拼接,也可以利用 Stringbuffer 类的 append () 方法进行拼接。拼接时要非常小心,尤其是引号、逗号和括号的拼接,避免出错。如果拼接出错,可通过在控制台输出SQL语句的方法查看错误。

代码如下所示:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementAddDemo {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		String name = "欧欧"; // 昵称
		int health = 100; // 健康值
		int love = 0; // 亲密度
		String strain = "酷酷的雪纳瑞"; // 品种
		// 1.加载驱动
		try {
			Class.forName("com.mysql.jdbc.Driver");
			// 2.建立连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			// 3.插入狗狗信息到数据库
			stmt = conn.createStatement();
			StringBuffer sbSql = new StringBuffer(
				"insert into dog (name,health,love,strain) values('");
			sbSql.append(name + "',");
			sbSql.append(health + ",");
			sbSql.append(love + ",'");
			sbSql.append(strain + "')");
			stmt.execute(sbSql.toString());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4.关闭Statement和数据库连接
			try {
				if (null != stmt) {
					stmt.close();
				}
				if (null != conn) {
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

运行结果如下:
这里写图片描述


2.使用Statement更新宠物

更新数据库中 id = 1 的狗狗的健康值和亲密度信息,操作也很简单,只要创建 Statement 对象然后调用 execute (String sql) 方法或者 executeUpdate (String sql) 方法即可。这里关键还是SQL语句的拼接要细心不要出错。

代码如下所示:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementUpdateDemo {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;

		try {
			// 1.加载驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 2.建立连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			// 3.更新狗狗信息到数据库
			stmt = conn.createStatement();
			stmt.executeUpdate(
				"update dog set health=80,love=15 where id = 1");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4.关闭Statement和数据库连接
			try {
				if (null != stmt) {
					stmt.close();
				}
				if (null != conn) {
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

运行结果如下:
这里写图片描述


3.使用Statement和ResultSet查询所有宠物

查询并输出 dog 表中所有狗狗的信息,首先还是创建 Statement 对象,然后调用 executeQuery (String sql) 方法执行查询操作,返回值是结果集 ResultSet 对象。

ResultSet 可以理解为由查询结果组成的一个二维表,每行代表一条记录,每列代表一个字段;并且存在一个光标,光标所指行为当前行,只能对结果集的当前行数据进行操作;光标初始位置是第一行之前(而不是指向第一行)。通过 ResultSetnext() 方法可以使光标向下移动一行,然后通过一系列 getXxx () 方法实现对当前行各列数据的操作。

若执行 next() 后光标指向结果集的某一行,则返回 true;否则返回 false 。若光标已指向结果集最后一行,再次调用 next() 方法,会指向最后一行的后面,此时返回 false
getXxx () 方法提供了获取当前行中某列值的途径,列号或列名可用于标识要从中获取数据的列(Xxx代表基本数据类型名,如intFloat 等,也可以是String)。例如,如果结果集中第一列的列名为id,存储类型为整型,那么可以使用两种方法获取存储在该列中的值,如 int id =rs.getInt(1); 或者 int id =rs. getInt("id); 。采用列名来标识列可读性强,建议多采用这种方式。
代码如下所示:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * @author 天道酬勤 
 * 使用Statement的excuteQuery()方法查询并输出所有狗狗信息
 */
public class StmtAndResultSetGetAll {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			// 1.加载驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 2.建立连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			// 3.查询并输出狗狗信息
			stmt = conn.createStatement();
			rs = stmt.executeQuery(
			"SELECT id,name,health,love,strain FROM dog");
			System.out.println("\t\t狗狗信息列表");
			System.out.println("编号\t姓名\t健康值\t亲密度\t品种");
			while (rs.next()) {
				System.out.print(rs.getInt(1) + "\t");
				System.out.print(rs.getString(2) + "\t");
				System.out.print(rs.getInt("health") + "\t");
				System.out.print(rs.getInt("love") + "\t");
				System.out.println(rs.getString("strain"));
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4.关闭Statement和数据库连接
			try {
				if (null != rs) {
					rs.close();
				}
				if (null != stmt) {
					stmt.close();
				}
				if (null != conn) {
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

运行结果如下:
这里写图片描述


ResultSet 接口常用方法及作用如下表所示:

方法名称 作用
boolean next() 将光标从当前位置向下移动一行
boolean previous() 将光标从当前位置向上移动一行
void close() 关闭 ResultSet 对象
int getInt(int columnIndex) int 的形式获取结果集当前指定列号的值
int getInt(String columnLabel) int 的形式获取结果集当前指定列名的值
float getFloat(int columnIndex) float 的形式获取结果集当前指定列号的值
float getFloat(String columnLabel) float 的形式获取结果集当前指定列名的值
String getString(int columnIndex) String 的形式获取结果集当前行指定列号的值
String getString(String columnLabel) String 的形式获取结果集当前行指定列名的值
int getRow() 得到光标当前所指行的行号
boolean absolute(int row) 光标移动到 row 指定的行
  • 作为一种好的编程风格,应该在不需要 ResultSet 对象、 Statement 对象和 Connection 对象时显式地关闭它们,语法形式为 public void close() throws SQLException
  • 要按先 ResultSet 结果集,后 Statement ,最后 Connection 的顺序关闭资源,因为 ResultSet 是通过 Statemen 执行 SQL 命令得到的,而 Statement 是需要在创建连接后才可以使用的,所以三者之同存在相互依存的关系,关闭时也必须按照依存关系进行
  • 用户如果不关闭 ResultSet ,当 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该 ResultSet 将被自动关闭

五、PreparedStatement接口

PreparedStatement 接口继承自 Statement 接口, PreparedStatement 比普通 Statement 对象使用起来更加灵活,更有效率。


1.为什么要使用 PreparedStatement

我们首先通过示例来看一下使用 Statement 接口的一个缺点。要求宠物主人根据控制台提示输入用户名和密码,如果输入正确,输出登录成功,欢迎您,否则输出登录失败,请重新输入。
具体代码如下所示:
1.首先在master表中插入一条狗狗主人信息

INSERT INTO `MASTER` (`NAME`,`PASSWORD`,`money`) 
VALUES('xiaoming','123456',100);

2.Java代码如下:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

/**
 * @author 天道酬勤 
 * 使用Statement安全性差,存在SQL注入隐患
 */
public class StatementWeakness {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		// 根据控制台提示输入用户名账号和密码
		Scanner input = new Scanner(System.in);
		System.out.println("\t宠物主人登录");
		System.out.print("请输入姓名:");
		String name = input.next();
		System.out.println("请输入密码:");
		String password = input.next();
		try {
			// 1.加载驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 2.建立连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			// 3.判断宠物主人是否登录成功
			stmt = conn.createStatement();
			String sql = "SELECT * FROM master WHERE name='" 
				+ name + "' and password='" + password + "'";
			System.out.println(sql);
			rs = stmt.executeQuery(sql);
			if (rs.next()) {
				System.out.println("登录成功,欢迎您!");
			} else {
				System.out.println("登录失败,请重新输入!");
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4.关闭Statement和数据库连接
			try {
				if (null != rs) {
					rs.close();
				}
				if (null != stmt) {
					stmt.close();
				}
				if (null != conn) {
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

如果输入正确的用户名和密码,则显示登录成功,运行结果如下所示:
Java-JDBC技术(概念及应用)_第3张图片

可是如果输入了精心设计的内容,即使用户名和密码都是错误的,仍旧可以显示登录成功,如下图所示:
Java-JDBC技术(概念及应用)_第4张图片

这是典型的SQL注入攻击。原因是在使用Statement接口方法时要注意进行SQL语句的拼接,不仅拼接繁琐麻烦、容易出错,还存在安全漏洞。而使用PreparedStatement接口就不存在这个问题,PreparedStatement接口的优点还不仅如此,后面会介绍两种接口的其他区别~


2.使用PreparedStatement更新宠物信息

上面介绍了PreparedStatement接口的优点,在使用PreparedStatement之前,先掌握其常用方法,如下表所示:

方法名称 作用
boolean excute() 在此PreparedStatement对象中执行SQL语句,该语句可以是任何SQL语句。如结果是Result对象,则返回true,如果结果是更新计数或没有结果,则返回false
ResultSet excuteQuery() 在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象
int excuteUpdate() 在此PreparedStatement对象中执行SQL语句,该语句必须是一个DML语句,如INSERT UPDATE DELETE语句;或者是无返回内容的SQL语句,如DDL语句。返回值是执行该操作所影响的行数
void setInt (int index, int x) 将指定参数设置为给定Java int值。设置其他类型参数的方法与此方法类似,如setFloat (int index, float x) setDouble (int index, double x)
void setObject (int index, Object x) 使用给定对象设置指定参数的值

使用 Preparedstatement 操作数据库的基本步骤有三步:

1.创建PeparedStatement 对象
通过 Connection 接口的 prepareStatement ( String sql )方法来创建 PreparedStatement 对象,SQL语句可具有一个或多个输入参数。这些输入参数的值在SQL语句创建时未被指定,而是为每个输入参数保留一个问号"?"作为占位符。
以下的代码段(其中connConnection 对象)将创建包含带有三个输入参数的SQL语句的PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement("UPDATE dog SET health =?, love=? WHERE id=?");

2.设置每个输入参数的值
通过调用 setXxx()方法来完成,其中Xxx是与该参数相应的类型。例如,若参数是 String 类型,则使用的方法就是 setString()setXxx()方法的第一个参数是要设置参数的序数位置(从1开始计数),第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为整型值80,第二个参数设为整型值15,第三个参数设为String型值"xiaohua"

pstmt.setInt(1, 80);
pstmt.setInt(2, 15);
pstmt.setString(3, "xiaohua");

3.执行SQL语句
在设置了各个输入参数的值后,就可以调用 PreparedStatement 接口的三个执行方法 ResultSet executeQuery ()int executeUpdate ()boolean execute ()之一来执行SQL语句。

注意:这三个执行方法和 Statement 接口中三个方法名称相同、作用相同,但是不需要SQL语句做参数,SQL语句已经在创建对象 PreparedStatement 时指定了。

例如:pstmt.executeUpdate();

创建 PreparedStatement 对象时会对SQL语句进行预编译,所以执行速度要快于 Statement 对象。因此,如果在程序中存在需要多次执行SQL语句时,应使用 PreparedStatement 对象来执行数据库操作,以提高效率。

代码如下所示:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 
 * @author 天道酬勤 
 * 使用PreparedStatement添加信息和更新多条狗狗信息
 */
public class PreparedStatementUpdate {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			// 1.加载驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 2.建立连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/epet", "root", "admin");
			// 3.更新狗狗信息到数据库
			String sql1 = "INSERT INTO dog "
				+"(name,health,love,strain) VALUES(?,?,?,?)";
			pstmt = conn.prepareStatement(sql1);
			pstmt.setString(1, "花花");
			pstmt.setInt(2, 60);
			pstmt.setInt(3, 20);
			pstmt.setString(4, "阿拉斯加");
			pstmt.executeUpdate();

			String sql2 = "UPDATE dog SET health=?"
				 + ",love=? where id=?";
			pstmt = conn.prepareStatement(sql2);
			pstmt.setInt(1, 90);
			pstmt.setInt(2, 70);
			pstmt.setInt(3, 1);
			pstmt.executeUpdate();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4.关闭PreparedStatement和数据库连接
			try {
				if (null != pstmt) {
					pstmt.close();
				}
				if (null != conn) {
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

运行结果如下所示:
这里写图片描述

PreparedStatementStatement 好在哪里?

  1. 提高了代码的可读性和可维护性。
    虽然使用 PreparedStatement 来代替 Statement 会多几行代码,但避免了烦琐麻烦又容易出错的SQL语句拼接,提高了代码的可读性和可维护性
  2. 提高了SQL语句执行的性能。
    创建 Statement 对象时不使用SQL语句做参数,不会解析和编译SQL语句,每次调用方法执行SQL语句时都要进行SQL语句解析和编译操作,即操作相同仅仅是数据不同。
    创建 PreparedStatement 对象时使用SQL语句做参数,会解析和编译该SQL语句,也可以使用带占位符的SQL语句做参数,在通过 setXxx ()方法给占位符赋值后执行SQL语句时无须再解析和编译SQL语句,直接执行即可。多次执行相同操作可以大大提高性能
  3. 提高了安全性。
    PreparedStatement 使用预编译语句,传入的任何数据都不会和已经预编译的SQL语句进拼接,避免了SQL注入攻击

你可能感兴趣的:(数据库笔记,JDBC)