一、概述

在Java中,数据库存取技术可分为如下几类:

JDBC直接访问数据库
JDO技术(Java Data Object)
第三方O/R工具,如Hibernate, ibatis 等
JDBC是java访问数据库的基石,JDO, Hibernate等只是更好的封装了JDBC。

1、什么是JDBC

JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统(DBMS)、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法、方便地访问数据库资源

JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。

JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

如果没有JDBC,那么Java程序访问数据库时是这样的:

java jcs java jc数据库_SQL

  改装:

java jcs java jc数据库_sql_02

实际上:

java jcs java jc数据库_数据库_03

  用于操作数据库

2、JDBC API

JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并到得到返回结果
等各类操作。声明在java.sql与javax.sql包中。

 

java jcs java jc数据库_SQL_04

3、JDBC程序编写步骤

java jcs java jc数据库_数据库_05

编写加载与注册驱动的代码

(一)加载并注册驱动

驱动程序由数据库提供商提供下载。 
MySQL的驱动下载地址:http://dev.mysql.com/downloads/
项目导入数据连接操作的jar包

1、Driver接口 

Java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。

MySQL:com.mysql.jdbc.Driver
SQLServer:com.microsoft.sqlserver.jdbc.SQLServerDriver
Oracle:oracle.jdbc.driver.OracleDriver
例如:
  Driver driver = new com.mysql.jdbc.Driver();
//因为与mysql驱动包中的类发生直接依赖,所以这样可移植性不够好

2、DriverManager 类 

在实际开发中,程序中不直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)
去调用这些Driver实现。
DriverManager 类是驱动程序管理器类,负责管理驱动程序。
通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例。
//DriverManager.registerDriver()方式注册驱动,还是依赖
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 
方法来注册自身的一个实例,所以可以换一种方式来加载驱动。(即只要想办法让驱动类的这段静态代码块执行即可注册驱动类,
而要让这段静态代码块执行,只要让该类被类加载器加载即可)

java jcs java jc数据库_java jcs_06

调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名
//1、通过反射,加载与注册驱动类,解耦合(不直接依赖)
Class.forName("com.mysql.jdbc.Driver");

(二)获取数据库链接

可以通过 DriverManager 类建立到数据库的连接Connection:

如下方法试图建立到给定数据库 URL 的连接。DriverManager 试图从已注册的 JDBC 驱动程序集中选择一个适当的驱动程序。

public static Connection getConnection(String url)
public static Connection getConnection(String url,String user, String password)
public static Connection getConnection(String url,Properties info)

1、JDBC URL

JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,
从而建立到数据库的连接。

JDBC URL的标准由三部分组成,各部分间用冒号分隔。

jdbc:<子协议>:<子名称>

协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提
供足够的信息
例如:

java jcs java jc数据库_数据库_07

//1、加载与注册驱动
Class.forName("com.mysql.jdbc.Driver");
		
//2、获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
Connection conn = DriverManager.getConnection(url, "root", "root");

//硬编码

2、jdbc.properties

在src下建立文件jdbc.properties,当然也可以是其他名字,只不过这里为了见名知意取这个名

通常至少应该包括 "user" 和 "password" 属性
#key=value
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
password=root
	public static void main(String[] args) throws Exception {
		//预先加载配置文件jdbc.properties,把配置信息封装到Properties对象中
		Properties pro = new Properties();

		//ClassLoader只能加载类路径下的资源文件
        //如果不是类路径下,只能使用FileInputStream
		pro.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
		
		//1、加载与注册驱动
		Class.forName(pro.getProperty("driver"));
		
		//2、获取数据库连接
//		Connection conn = DriverManager.getConnection(pro.getProperty("url"), pro);
		Connection conn = DriverManager.getConnection(pro.getProperty("url"), pro.getProperty("user"),pro.getProperty("password"));
		System.out.println(conn);
	}

//解决了硬编码的问题

(三)操作或访问数据库

数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。

其实一个数据库连接就是一个Socket连接。

在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
PrepatedStatement:SQL 语句被预编译并存储在此对象中,然后可以使用此对象多次高效地执行该语句。
CallableStatement:用于执行 SQL 存储过程

 

Statement

通过调用 Connection 对象的 createStatement() 方法创建该对象

该对象用于执行静态的 SQL 语句,并且返回执行结果

Statement 接口中定义了下列方法用于执行 SQL 语句:

int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE

ResultSet excuteQuery(String sql):执行查询操作SELECT

代码: 

package com.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;

public class TestStatement {

	public static void main(String[] args) throws Exception{
		//加载jdbc.properties资源配置文件
		Properties pro = new Properties();
		pro.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
		
		//1、加载与注册驱动
		Class.forName(pro.getProperty("driver"));
		
		//2、获取数据库连接
		Connection conn = DriverManager.getConnection(pro.getProperty("url"), pro);
		
		//3、访问数据库
		//(1)获取操作SQL语句的Statement对象
		Statement st = conn.createStatement();
		
		//(2)调用Statement的executeUpdate(String sql)执行SQL语句进行插入
//		int len = st.executeUpdate("insert into t_stu value(9,'九儿','女','H5','170101班')");
		//第一列自增,那么id列可以传null
		int len = st.executeUpdate("insert into t_stu value(null,'舒淇','女','H5','170101班')");
		if(len>0){
			System.out.println("添加成功");
		}else{
			System.out.println("添加失败");
		}
		
		//4、释放资源
		st.close();
		conn.close();
	}
}

ResultSet

通过调用 Statement 对象的 excuteQuery() 方法创建该对象

ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商实现

ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 
方法移动到下一行

ResultSet 接口的常用方法:

boolean next()
getXxx(String columnLabel):columnLabel使用 SQL AS 子句指定的列标签。如果未指定 SQL AS 子句,则标签是列名称
getXxx(int index) :索引从1开始
…

java jcs java jc数据库_SQL_08

Connection、Statement、ResultSet都是应用程序和数据库服务器的连接资源,使用后一定要关闭,需要在finally中关闭

 

package com.jdbc;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class TestStatement3 {

	public static void main(String[] args) {
		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			//加载配置文件
			Properties pro = new Properties();
			pro.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
			
			//1、加载与注册驱动
			Class.forName(pro.getProperty("driver"));
			
			//2、获取数据库连接
			conn = DriverManager.getConnection(pro.getProperty("url"), pro);
			
			//3、访问数据库
			st = conn.createStatement();
			rs = st.executeQuery("select sno,sname,sex,major,classes from t_stu");
			System.out.println("sno\tsname\tsex\tmajor\tclasses");
			while(rs.next()){
				System.out.println(rs.getInt("sno")+"\t"+rs.getString("sname")+"\t"+rs.getString("sex")+"\t"+rs.getString("major")+"\t"+rs.getString("classes"));
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			//4、释放资源
			try {
				if(rs!=null){
					rs.close();
				}
			} catch (SQLException e1) {
				e1.printStackTrace();
			}finally{
				try {
					if(st!=null){
						st.close();
					}
				} catch (SQLException e) {
					e.printStackTrace();
				} finally{
					try {
						if(conn!=null){
							conn.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

(五)PreparedStatement

SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 
语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。

Statement的不足

(1)SQL拼接

(2)SQL注入

PreparedStatement解决

对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。

可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 
方法来设置这些参数. setXxx() 
方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
ResultSet executeQuery()执行查询,并返回该查询生成的 ResultSet 对象。
int executeUpdate():执行更新,包括增、删、该

java jcs java jc数据库_java jcs_09

PreparedStatement处理Blob类型的数据

BLOB (binary large object),二进制大对象,BLOB常常是数据库中用来存储二进制文件的字段类型。

插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

java jcs java jc数据库_SQL_10

实际使用中根据需要存入的数据大小定义不同的BLOB类型。
需要注意的是:如果存储的文件过大,数据库的性能会下降。
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `head_picture` mediumblob,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
package com.jdbc;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Properties;

public class TestBlob {

	public static void main(String[] args) throws Exception{
		//加载jdbc.properties资源配置文件
		Properties pro = new Properties();
		pro.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
		
		//1、加载与注册驱动
		Class.forName(pro.getProperty("driver"));
		
		//2、获取数据库连接
		Connection conn = DriverManager.getConnection(pro.getProperty("url"), pro);
		
		//3、访问数据库
		//(1)准备带参数(?)的SQL
		//PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
		//PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示
		String sql ="insert into user(username,head_picture) value(?,?)";
		
		//(2)通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象
		PreparedStatement pst = conn.prepareStatement(sql);
		
		//(3)调用 PreparedStatement 对象的 setXxx(int parameterIndex,XX value) 方法来设置这些参数
		pst.setString(1, "lily");
		pst.setBlob(2, new FileInputStream("head/girl.jpg"));
		
		//(4)调用PreparedStatement的executeUpdate()执行SQL语句进行插入
		//注意此处不能在传sql,否则?就白设置了
		int len = pst.executeUpdate();
		
		//(5)处理结果
		if (len > 0) {
			System.out.println("添加成功");
		} else {
			System.out.println("添加失败");
		}
		
		//4、释放资源
		pst.close();
		conn.close();
	}
}

java jcs java jc数据库_SQL_11

PreparedStatement vs Statement

代码的可读性和可维护性. Statement的sql拼接是个难题。
PreparedStatement 可以防止 SQL 注入
PreparedStatement 可以处理Blob类型的数据
PreparedStatement 能最大可能提高性能:(Oracle和PostgreSQL8是这样,但是对于MySQL不一定比Statement高)
DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行
代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据
库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.  
(语法检查,语义检查,翻译成二进制命令,缓存)

ResultSetMetaData 类

可用于获取关于 ResultSet 对象中列的类型和属性信息的对象:

int getColumnCount()返回当前 ResultSet 对象中的列数。
String getColumnName(int column)获取指定列的名称。 数据库中的字段名
String getColumnLabel(int column)建议标题通常由 SQL AS 子句来指定。如果未指定 SQL AS,则返回列名
String getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
int getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
boolean isNullable(int column):指示指定列中的值是否可以为 null。
boolean isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
/**
	 * 执行查询操作的SQL语句,SQL可以带参数(?)
	 * @param clazz Class 查询的结果需要封装的实体的Class类型,例如:学生Student,商品Goods,订单Order
	 * @param sql String 执行查询操作的SQL语句
	 * @param args Object... 对应的每个?设置的值,顺序要与?对应
	 * @return ArrayList<T> 封装了查询结果的集合
	 * @throws Exception
	 */
	public static <T> ArrayList<T> getList(Class<T> clazz,String sql,Object... args)throws Exception{
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rs = null;
		ArrayList<T> list = null;
		
		try {
			//1、获取连接
			conn = JDBCTools.getConnection();
			//2、获取PreparedStatement对象,并预编译带参数?的SQL
			pst = conn.prepareStatement(sql);
			//3、设置参数?的值
			if (args != null && args.length > 0) {
				for (int i = 0; i < args.length; i++) {
					pst.setObject(i + 1, args[i]);
				}
			}
			//4、执行SQL
			rs = pst.executeQuery();
			//5、获取结果,封装到对象中
			//(1)获取ResultSetMetaData对象,用于获取关于 ResultSet 对象中列的类型和属性信息
			ResultSetMetaData rm = rs.getMetaData();
			//(2)获取一共有几列
			int count = rm.getColumnCount();
			while (rs.next()) {
				if (list == null) {
					list = new ArrayList<T>();
				}
				//(3)通过反射创建实体对象
				T entity = clazz.newInstance();

				for (int i = 0; i < count; i++) {
					//(4)获取每一列对应的属性
					Field f = clazz.getDeclaredField(rm.getColumnLabel(i + 1));
					f.setAccessible(true);
					//(5)获取列值为属性赋值
					f.set(entity, rs.getObject(i + 1));
				}

				//(6)把实体对象添加到集合中
				list.add(entity);
			} 
		} finally {
			//6、释放资源
			JDBCTools.free(rs, pst, conn);
		}
		
		
		return list;
	}
}

 DatabaseMetaData 类

Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC可以访问一个实现事先并不了解的数据库。

获取这些信息的方法都是在DatabaseMetaData类的对象上实现的,而DataBaseMetaData对象是在Connection对象上获得的。

DatabaseMetaData 类中提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息:

getURL():返回一个String类对象,代表数据库的URL。
getUserName():返回连接当前数据库管理系统的用户名。
isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
getDatabaseProductName():返回数据库的产品名称。
getDatabaseProductVersion():返回数据库的版本号。
getDriverName():返回驱动驱动程序的名称。
getDriverVersion():返回驱动程序的版本号