JDBC
1、数据库编程–JDBC技术
在MySQL官网下载相关jar包
1.1、JDBC概述
Java DataBase Connectivity(Java 数据库连接技术),它是将Java与SQL结合且独立于特定的数据库系统的应用程序编程接口(API–它是一种可用于执行SQL语句的Java API,即由一组用Java语言编写的类与接口所组成)。
有了JDBC从而可以使Java程序员用Java语言来编写完整的数据库方面的应用程序。另外也可以操作保存在多种不同的数据库管理系统中的数据,而与数据库管理系统中数据存储格式无关。同时Java语言的与平台的无关性,不必在不同的系统平台下编写不同的数据库应用程序。
- ODBC
微软的ODBC是用C编写的,而且只适用于Windows平台,无法实现跨平台地操作数据库。
- SQL语言
SQL尽管包含有数据定义、数据操作、数据管理等功能,但它并不是一个完整的编程语言,而且不支持流控制,需要与其它编程语言相配合使用。
- JDBC的设计
由于Java语言具有健壮性、安全、易使用并自动下载到网络等方面的优点,因此如果采用Java语言来连接数据库,将能克服ODBC局限于某一系统平台的缺陷;将SQL语言与Java语言相互结合起来,可以实现连接不同数据库系统,即使用JDBC可以很容易地把SQL语句传送到任何关系型数据库中。
- JDBC设计的目的
它是一种规范,设计出它的最主要的目的是让各个数据库开发商为Java程序员提供标准的数据库访问类和接口,使得独立于DBMS的Java应用程序的开发成为可能(数据库改变,驱动程序跟着改变,但应用程序不变)。
1.2、JDBC的主要功能
-
创建与数据库的连接
// 加载驱动 Class.forName(驱动); //(1)创建与数据库的连接Connection Connection con = DriverManager.getConnection(url,账号,密码);
-
发送SQL语句到任何关系型数据库中
Statement stmt=con.createStatement(); ResultSet rs=stmt.executeQuery(sql);
-
处理数据并查询结果
while(rs.next()){ // rs.getString(1);// 取第1 列的内容 // System.out.println(rs.getString(2)); System.out.println(rs.getString("name")); }
1.3、JDBC与ODBC的对比
- ODBC是用
C语言
编写的,不是面向对象的;而JDBC是用Java
编写的,是面向对象的。 - ODBC难以学习,因为它把简单的功能与高级功能组合在一起,即便是简单的查询也会带有复杂的任选项;而JDBC的设计使得简单的事情用简单的做法来完成。
- ODBC是局限于某一系统平台的,而JDBC提供
Java
与平台无关的解决方案。 - 但也可以通过
Java
来操作ODBC,这可以采用JDBC-ODBC桥接方式来实现(因为Java
不能直接使用ODBC,即在Java
中使用本地C语言
的代码将带来安全缺陷)。
1.4、JDBC驱动程序的类型:
目前比较常见的JDBC驱动程序可分为以下四个种类:
-
JDBC-ODBC桥加ODBC驱动程序
JavaSoft桥产品利用ODBC驱动程序提供JDBC访问。注意,必须将ODBC二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装不是主要问题),或者是用Java编写的三层结构的应用程序服务器代码。
JDBC-ODBC 桥接方式利用微软的开放数据库互连接口(ODBC API)同数据库服务器通讯,客户端计算机首先应该安装并配置 ODBC driver 和 JDBC-ODBC bridge 两种驱动程序。
-
本地API
客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其它DBMS的调用。注意像桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户机上。
这种驱动方式将数据库厂商的特殊协议转换成Java代码及二进制类码,使Java 数据库客户方与数据库服务器方通信。例如:Oracle用SQLNet协议,DB2用 IBM 的数据库协议。数据库厂商的特殊协议也应该被安装在客户机上。
-
JDBC网络纯Java驱动程序
将JDBC转换为与DBMS无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS协议。这种网络服务器中间件能够将它的纯Java客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet用的产品。为了使这些产品也支持Internet访问,它们必须处理Web所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC驱动程序加到他们现有的数据库中间件产品中。
这种方式是纯Java driver。数据库客户以标准网络协议(如HTTP、SHTTP)同数据库访问服务器通信,数据库访问服务器然后翻译标准网络协议成为数据库厂商的专有特殊数据库访问协议(也可能用到ODBC driver)与数据库通信。对 Internet 和 Intranet 用户而言这是一个理想的解决方案。Java driver 被自动的,以透明的方式随Applets自 Web 服务器而下载并安装在用户的计算机上。
-
本地协议纯Java驱动程序
JDBC调用直接转换为DBMS所使用的网络协议。这将允许从客户机机器上直接调用DBMS服务器,是Intranet访问的一个很实用的解决方法。
这种方式也是纯Java driver。数据库厂商提供了特殊的JDBC协议使Java数据库客户与数据库服务器通信。然而,将把代理协议同数据库服务器通信改用数据库厂商的特殊JDBC driver。这对Intranet 应用是高效的,可是数据库厂商的协议可能不被防火墙支持,缺乏防火墙支持在Internet 应用中会存在潜在的安全隐患。
1.5、工作原理
JDBC的设计基于**X/Open SQL CLI(Call Level Interface)**这一模型。它通过定义出一组 API对象和方法以用于同数据库进行交互。
即JDBC API接口通过java.sql包中的java.sql.DriverManager接口来处理驱动的调入并且对产生新的数据库连接提供支持,然后通过底层的JDBC 驱动程序来驱动具体的数据库。
- JDBC驱动程序管理器是JDBC体系结构的支柱。它实际上很小,也很简单;其主要作用是把Java应用程序连接到正确的JDBC驱动程序上,然后即退出。
- JDBC-ODBC桥是一个JDBC驱动程序,通过将JDBC操作转换为ODBC操作来实现对数据库的操作能力,桥为所有对ODBC可用的数据库实现JDBC成为可能。
- DriverManager类存有已注册的Driver类的清单。当调用方法
getConnection
时,它将检查清单中的每个驱动程序,直到找到可与URL中指定的数据库进行连接的驱动程序为止。
2、JDBC编程步骤
2.1、加载驱动
加载连接数据库的驱动程序(把指定的Class装载到JVM中来)
Class.forName("com.mysql.jdbc.Driver");
2.2、创建与数据源的连接
String url="jdbc:mysql://127.0.0.1:3306/itschool?useUnicode=true&characterEncoding=utf-8";
Connection con = DriverManager.getConnection(url,"root","root");
2.3、代理执行SQL
查询数据库创建Statement对象并执行SQL语句以返回一个ResultSet对象。
Statement stmt = con.createStatement();// 创建代理
ResultSet rs = stmt.executeQuery("select * from teacher"); // 执行sql
2.4、获取内容
获得当前记录集中的某一记录的各个字段的值
String name = rs.getString("name");
int age = rs.getInt(3);
2.5、关闭资源
关闭查询语句及与数据库的连接(注意关闭的顺序先rs再stmt最后为con,一般可以在finally语句中实现关闭)
rs.close();
stmt.close();
con.close();
3、JDBC的结构
java.sql.DriveManager
:该接口主要定义了用来处理装载驱动程序并且为创建新的数据库连接提供支持。java.sql.Connection
:该接口主要定义了实现对某一种指定数据库连接的功能。java.sql.Statement
:该接口主要定义了在一个给定的连接中作为SQL语句执行声明的容器以实现对数据库的操作。它主要包含有如下的两种子类型。java.sql.PreparedStatement
:该接口主要定义了用于执行带或不带 IN 参数的预编译 SQL 语句。java.sql.CallableStatement
:该接口主要定义了用于执行数据库的存储过程的雕用。java.sql.ResultSet
:该接口主要定义了用于执行对数据库的操作所返回的结果集。
3.1、引用必要的包
import java.sql.*; // 它包含有操作数据库的各个类与接口
3.2、加载连接数据库的驱动程序类
为实现与特定的数据库相连接,JDBC必须加载相应的驱动程序类。这通常可以采用Class.forName()
。方法显式地加载一个驱动程序类,由驱动程序负责向DriverManager登记注册并在与数据库相连接时,DriverManager将使用此驱动程序。
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");// 注意:这条语句直接加载了sun公司提供的JDBC-ODBC Bridge驱动程序类。
Class.forName("com.mysql.jdbc.Driver");
3.3、创建与数据源的连接
jdbc:<subprotocal>:[database locator]
jdbc---指出要使用JDBC
subprotocal---定义驱动程序类型
database locator---提供网络数据库的位置和端口号(包括主机名、端口和数据库系统名等)
如:
jdbc:mysql://localhost:3306/itschool?useUnicode=true&characterEncoding=utf-8
3.4、查询数据库的信息
在JDBC中查询数据库中的数据的执行方法可以分为三种类型,分别对应:
- Statement (用来执行包含有静态SQL的字符串)
- PreparedStatement(预编译SQL语句)
- CallableStatement(主要用于执行存储过程)三个接口
3.4.1、创建Statement对象:
要想执行一个SQL查询语句,必须首先创建出Statement对象,它封装代表要执行的SQL语句,并执行
SQL语句以返回一个ResultSet对象,这可以通过Connection类中的createStatement()
方法来实现。
3.4.2、执行SQL语句
执行一个SQL查询语句,以查询数据库中的数据: Statement接口提供了三种执行SQL语句的方法:executeQuery()
、executeUpdate()
和 execute()
。具体使用哪一个方法由SQL语句本身来决定。
(1)方法 executeQuery :
用于产生单个结果集的语句,例如 SELECT 语句等。
ResultSet rs=stmt. executeQuery ("select * from student");
(2)方法 executeUpdate:
用于执行INSERT
、UPDATE
或DELETE
语句以及SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。INSERT
、UPDATE
或DELETE
语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于 CREATE TABLE 或DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
3.4.3、关闭Statement对象
每一个Statement对象在使用完毕后,都应该关闭。
stmt.close();
4、Statement 应用要点
(1)JDBC在编译时并不对将要执行的SQL查询语句作任何检查,只是将其作为一个String类对象,直到驱动程序执行SQL查询语句时才知道其是否正确。对于错误的SQL查询语句,在执行时将会产生 SQLException。
(2)一个Statement对象在同一时间只能打开一个结果集,对第二个结果集的打开隐含着对第一个结果集的关闭。
(3)如果想对多个结果集同时操作,必须创建出多个Statement对象,在每个Statement对象上执行SQL查询语句以获得相应的结果集。
(4)如果不需要同时处理多个结果集,则可以在一个Statement对象上顺序执行多个SQL查询语句,对获得的结果集进行顺序操作。
Statement stmt=con.createStatement();
ResultSet rs1=stmt.executeQuery(sql);
ResultSet rs2=stmt.executeQuery(sql);
while(rs2.next())
{ System.out.println(rs2.getString(1));
}
//rs1.close(); //此时rs1已经被关闭
rs2.close();
stmt.close();
5、预编译方式执行SQL语句
5.1、PreparedStatement的基本特性
-
由于Statement对象在每次执行SQL语句时都将该语句传给数据库,如果需要多次执行同一条SQL语句时,这样将导致执行效率特别低,此时可以采用PreparedStatement对象来封装SQL语句。
-
如果数据库支持预编译,它可以将SQL语句传给数据库作预编译,以后每次执行该SQL语句时,可以提高访问速度;
-
但如果数据库不支持预编译,将在语句执行时才传给数据库,其效果类同于Statement对象。
5.2、作用
-
对SQL语句做预编译
-
另外PreparedStatement对象的SQL语句还可以接收参数
5.3、PreparedStatement的实现机制
- 在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。
- 每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。
- 在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。
5.4、PreparedStatement的编程步骤
- 创建PreparedStatement对象
从一个Connection对象上可以创建一个PreparedStatement对象,在创建时可以给出预编译的SQL语句。
- 执行SQL语句
可以调用executeQuery()来实现,但与Statement方式不同的是,它没有参数因为在创建PreparedStatement对象时已经给出了要执行的SQL语句,系统并进行了预编译。
- 关闭PreparedStatement对象
PreparedStatement pstmt=con.prepareStatement("select * from student");
ResultSet rs = pstmt.executeQuery(); //该语句可以被多次执行
pstmt.close(); //其实是调用了Statement类中的close()方法
5.5、Statement与PreparedStatement的编程要点
-
使用PreparedStatement对象比使用Statement对象的速度更快,因为无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。
-
使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。
String sqlStatement="select * from student";
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sqlStatement);
sqlStatement = sqlStatement +"where ID >100";
rs = stmt.executeQuery(sqlStatement);
6、带输入参数的SQL语句
6.1、要求
要实现使用SQL语句的输入与输出参数,必须在 PreparedStatement 类的对象上进行操作;
同时由于 CallableStatement 类是 PrepareStatement 类的子类,所以在CallableStatemen对象上的操作也可以使用输入与输出参数(存储过程可以支持三种类型的参数:IN、OUT 和 IN OUT);
6.2 、编程原理
是在生成CallableStatement或PreparedStatement类的对象时,可以在SQL语句中指定输入或输出参数,在执行这个SQL语句之前,要对输入参数进行赋值。
6.3、对参数赋值
setXXX方法用于给相应的输入参数进行赋值,其中XXX是JDBC的数据类型,如:Int、String等。
setXXX方法有两个参数一个是要赋值的参数在SQL语句中的位置 ;
第二个参数是要传递的值,如100、"Peking"等,随XXX的不同而为不同的类型。
String sql = "select * from teacher where tid = ? and tname = ?";
// 创建代理
pstmt = conn.prepareStatement(sql);
pstmt.setObject(1,1);
pstmt.setObject(2,"天幕道人");
// 执行sql语句
rs = pstmt.executeQuery();
SQL 注入 ’or 1 = 1 -- and password='
public class JdbcUtil {
// 表示定义数据库的用户名
private static String USERNAME;
// 定义数据库的密码
private static String PASSWORD;
// 定义数据库的驱动信息
private static String DRIVER;
// 定义访问数据库的地址
private static String URL;
static {
//加载数据库配置信息,并给相关的属性赋值
loadConfig();
}
// 定义数据库的链接
private Connection connection;
// 定义sql语句的执行对象
private PreparedStatement pstmt;
// 定义查询返回的结果集合
private ResultSet resultSet;
public JdbcUtil() {
}
/**
* 加载数据库配置信息,并给相关的属性赋值
*/
public static void loadConfig() {
try {
InputStream inStream = JdbcUtil.class.getResourceAsStream("/jdbc.properties");
Properties prop = new Properties();
prop.load(inStream);
USERNAME = prop.getProperty("jdbc.username");
PASSWORD = prop.getProperty("jdbc.password");
DRIVER = prop.getProperty("jdbc.driver");
URL = prop.getProperty("jdbc.url");
} catch (Exception e) {
throw new RuntimeException("读取数据库配置文件异常!", e);
}
}
/**
* 获取数据库连接
* @return 数据库连接
*/
public Connection getConnection() {
try {
Class.forName(DRIVER); // 注册驱动
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); // 获取连接
} catch (Exception e) {
throw new RuntimeException("get connection error!", e);
}
return connection;
}
/**
* 执行更新操作
* @param sql sql语句
* @param params 执行参数
* @return 执行结果
* @throws SQLException
*/
public boolean updateByPreparedStatement(String sql, List<?> params)
throws SQLException {
boolean flag = false;
int result = -1;// 表示当用户执行添加删除和修改的时候所影响数据库的行数
pstmt = connection.prepareStatement(sql);
int index = 1;
// 填充sql语句中的占位符
if (params != null && !params.isEmpty()) {
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(index++, params.get(i));
}
}
result = pstmt.executeUpdate();
flag = result > 0 ? true : false;
return flag;
}
/**
* 执行查询操作
* @param sql sql语句
* @param params 执行参数
* @return
* @throws SQLException
*/
public List<Map<String, Object>> findResult(String sql, List<?> params)
throws SQLException {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
int index = 1;
pstmt = connection.prepareStatement(sql);
if (params != null && !params.isEmpty()) {
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(index++, params.get(i));
}
}
resultSet = pstmt.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int cols_len = metaData.getColumnCount();
while (resultSet.next()) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < cols_len; i++) {
String cols_name = metaData.getColumnName(i + 1);
Object cols_value = resultSet.getObject(cols_name);
if (cols_value == null) {
cols_value = "";
}
map.put(cols_name, cols_value);
}
list.add(map);
}
return list;
}
/**
* 释放资源
*/
public void releaseConn() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
JdbcUtil jdbcUtil = new JdbcUtil();
jdbcUtil.getConnection();
try {
List<Map<String, Object>> result = jdbcUtil.findResult(
"select * from article", null);
for (Map<String, Object> m : result) {
System.out.println(m);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
jdbcUtil.releaseConn();
}
}
}