JDBC 万字长文总结回炉重造

Posted ZSYL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC 万字长文总结回炉重造相关的知识,希望对你有一定的参考价值。

1. 概述

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

  • JDBC直接访问数据库
  • JDO技术(Java Data Object)
  • 第三方O/R工具,如Hibernate, Mybatis 等

JDBC是java访问数据库的基石,JDO, Hibernate等只是更好的封装了JDBC。

1.1 什么是JDBC

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

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

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

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


JDBC是SUN公司提供一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可。

不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。

1.2 JDBC API

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

1.3 JDBC 可以做什么


我们可以做以下事情:增删改查

①查询女神们的基本信息
②查询女神们的详细信息
③查询心仪女神的电话号码 ,噢耶
④添加心仪的女神
⑤删除不符合要求的女神
⑥修改女神的信息
⑦查询管理员的所有信息

1.4 JDBC程序编写步骤

  1. 注册驱动
  2. 获取连接
  3. 执行增删改查
  4. 释放资源

2. 演示完整步骤

2.1 引入JDBC驱动程序

准备工作:引入JDBC驱动程序

驱动程序由数据库提供商提供下载。 mysql的驱动下载地址:http://dev.mysql.com/downloads/

如何在Java Project项目应用中添加数据库驱动jar:


1.把上图.jar包拷贝到项目中一个目录中:

2.添加到项目的类路径下

在驱动jar上右键–>Build Path-->Add to Build Path

注意:如果是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发工具叫WebRoot)目录中的WEB-INF目录中的lib目录下即可

2.2 加载并注册驱动

加载并注册驱动:
加载驱动,把驱动类加载到内存
注册驱动,把驱动类的对象交给DriverManager管理,用于后面创建连接等使用。

2.2.1 Class.forName( )

因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例,所以可以换一种方式来加载驱动。(即只要想办法让驱动类的这段静态代码块执行即可注册驱动类,而要让这段静态代码块执行,只要让该类被类加载器加载即可)

调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名:

//通过反射,加载与注册驱动类,解耦合(不直接依赖)
Class.forName("com.mysql.jdbc.Driver");

2.2.2 服务提供者框架

服务提供者框架(例如:JDBC的驱动程序)自动注册(有版本要求)

符合JDBC 4.0规范的驱动程序包含了一个文件META-INF/services/java.sql.Driver,在这个文件中提供了JDBC驱动实现的类名。

例如:mysql-connector-java-5.1.40-bin.jar文件中就可以找到java.sql.Driver文件,用文本编辑器打开文件就可以看到:com.mysql.jdbc.Driver类。

JVM的服务提供者框架在启动应用时就会注册服务,例如:MySQL的JDBC驱动就会被注册,而原代码中的Class.forName("com.mysql.jdbc.Driver")仍然可以存在,但是不会起作用。

但是注意mysql-connector-java-5.0.8-bin.jar版本的jar中没有,如下:

2.3 获取数据库链接

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

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)其中Properties info通常至少应该包括 “user” 和 “password” 属性

JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。JDBC URL的标准由三部分组成,各部分间用冒号分隔。 jdbc:<子协议>:<子名称>

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

例如:

MySQL的连接URL编写方式:

  • jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
  • jdbc:mysql://localhost:3306/testdb
  • jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
  • jdbc:mysql://localhost:3306/testdb?user=root&password=123456
//1、加载与注册驱动
Class.forName("com.mysql.jdbc.Driver");

//2、获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
Connection conn = DriverManager.getConnection(url, "root", "root");

2.4 操作或访问数据库

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

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

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

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

2.4.1 Statement

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

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

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

  • int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
  • ResultSet excuteQuery(String sql):执行查询操作SELECT

2.4.2 ResultSet

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

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

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

ResultSet 接口的常用方法:

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

增删改查示例代码:

public class TestConnection {
	//增删改
	@Test
	public void testUpdate() throws  Exception {
		//步骤1:注册驱动(为了让mysql的实现类加载到内存可以使用)
		
//		DriverManager.registerDriver(new Driver());
		Class.forName("com.mysql.jdbc.Driver");
	
		//步骤2:获取连接
		
		Properties properties  = new Properties();//配置文件要求里面必须有连接参数和配置参数
		
		properties.load(new FileInputStream("src\\\\druid.properties"));
		DataSource ds =	DruidDataSourceFactory.createDataSource(properties);
		
		
		Connection connection = ds.getConnection();
		
		//步骤3:执行增删改查操作
		//①获取执行sql语句的命令对象
		Statement statement = connection.createStatement();
		
		
		//②执行sql语句
//		statement.executeQuery(sql);//执行查询语句
//		statement.executeUpdate(sql)//执行增删改语法,返回受影响行数
//		statement.execute(sql)//执行任何sql语句
		
		int update = statement.executeUpdate("delete from admin where id =1");
		
		//③处理结果
		System.out.println(update>0?"成功":"失败");
		
		//步骤4:关闭连接(释放资源)
		
		statement.close();
		connection.close();
		
	}
	//查询
	@Test 
	public void testQuery() throws Exception {

		//1.注册驱动
		Class.forName("com.mysql.jdbc.Driver");		
		//2.获取连接
		Properties  pro = new Properties();
		pro.load(new FileInputStream("src\\\\druid.properties"));
		
		DataSource ds = DruidDataSourceFactory.createDataSource(pro);
		
		Connection connection = ds.getConnection();
		
		//3.执行查询
		
		//①获取执行sql的命令对象
		Statement statement = connection.createStatement();
		
		//②执行sql
		
		ResultSet set = statement.executeQuery("select name bname,sex gender,borndate born from beauty");
		while(set.next()) {
			
			String name = set.getString("bname");
			char gender = set.getString("gender").charAt(0);
			Date date = set.getDate("born");
	//		String date = set.getString("born");
			
			System.out.println(name+"\\t"+gender+"\\t"+date);
		
		}
		
		//4.关闭
		set.close();
		statement.close();
		connection.close();
		
	}
}

2.5 释放资源

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

演示未关闭后果:

package com.atguigu.conn;

import java.sql.Connection;
import java.sql.DriverManager;

public class TestConnectionClose {
	public static void main(String[] args) throws Exception{
		//1、加载与注册驱动
		Class.forName("com.mysql.jdbc.Driver");
		
		//2、获取数据库连接
		String url = "jdbc:mysql://localhost:3306/test";
		
		//my.ini中max_connections=10
		for (int i = 0; i < 15; i++) {
			Connection conn = DriverManager.getConnection(url,"root", "123456");
			System.out.println(conn);
			//没有关闭,资源一直没有释放
		}
	}
}

3. 封装JDBCUtils

封装JDBCTools

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

/*
 * 获取连接或释放连接的工具类
 */
public class JDBCTools {
	// 1、数据源,即连接池
	private static DataSource dataSource;
	
	// 2、ThreadLocal对象
	private static ThreadLocal<Connection> threadLocal;

	static {
		try {
			//1、读取druip.properties文件
			Properties pro = new Properties();
			pro.load(JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties"));
			
			//2、连接连接池
			dataSource = DruidDataSourceFactory.createDataSource(pro);

			//3、创建线程池
			threadLocal = new ThreadLocal<>();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 获取连接的方法
	 * 
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() {
		// 从当前线程中获取连接
		Connection connection = threadLocal.get();
		if (connection == null) {
			// 从连接池中获取一个连接
			try {
				connection = dataSource.getConnection();
				// 将连接与当前线程绑定
				threadLocal.set(connection);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return connection;
	}

	/**
	 * 释放连接的方法
	 * 
	 * @param connection
	 */
	public static void releaseConnection() {
		// 获取当前线程中的连接
		Connection connection = threadLocal.get();
		if (connection != null) {
			try {
				connection.close();
				// 将已经关闭的连接从当前线程中移除
				threadLocal.remove();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

4. PreparedStatement

4.1 Statement的不足

(1)SQL拼接
(2)SQL注入
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。
(3)处理Blob类型的数据
BLOB (binary large object),二进制大对象,BLOB常常是数据库中用来存储二进制文件的字段类型。
插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

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

如果还是报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数:
max_allowed_packet=16M
 
注意:修改了my.ini文件,一定要重新启动服务

实际使用中根据需要存入的数据大小定义不同的BLOB类型。

需要注意的是:如果存储的文件过大,数据库的性能会下降。

4.2 PreparedStatement概述

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

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

PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

ResultSet executeQuery()执行查询,并返回该查询生成的 ResultSet 对象。

int executeUpdate():执行更新,包括增、删、该

4.3 PreparedStatement vs Statement

代码的可读性和可维护性. Statement的sql拼接是个难题。

PreparedStatement 可以防止 SQL 注入

PreparedStatement 可以处理Blob类型的数据

PreparedStatement 能最大可能提高性能:(Oracle和PostgreSQL8是这样,但是对于MySQL不一定比Statement高)

DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

4.4 JDBC 取得数据库自动生成的主键

获取自增长的键值:

(1)在创建PreparedStatement对象时
原来:

PreparedStatement pst = conn.preparedStatement(sql);
现在:
PreparedStatement pst = conn.prepareStatement(orderInsert,Statement.RETURN_GENERATED_KEYS);

(2)原来执行更新
原来:

int len = pst.executeUpdate();	
 现在:
int len = pst.executeUpdate();	
ResultSet rs = pst.getGeneratedKeys();
if(rs.next()){
	 Object key = rs.getObject(第几列);//获取自增长的键值
}	

5. 事务

JDBC程序中当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。

JDBC程序中为了让多个 SQL 语句作为一个事务执行:(重点)

调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务

在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务

在其中某个操作失败或出现异常时,调用 rollback(); 方法回滚事务

若此时 Connection 没有被关闭, 则需要恢复其自动提交状态 setAutoCommit(true);

注意:

如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下.

package com.atguigu.transaction;

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

public class TestTransaction {
	public static void main(String[] args){
		Connection conn = null;
		try {
			//1、连接数据库
			Class.forName("com.mysql.jdbc.Driver");

			String url = "jdbc:mysql://localhost:3306/test";
			String user = "root";
			String password = "123456";
			conn = DriverManager.getConnection(url, user, password);
			//设置手动提交
			conn.setAutoCommit(false);

			String sql1 = "update t_department set description = ? where did = ?";
			PreparedStatement pst1 = conn.prepareStatement(sql1);
			pst1.setObject(1, "挣大钱的");
			pst1.setObject(2, 4);
			int len1 = pst1.executeUpdate();
			System.out.println(len1>0?"更新部门信息成功":"更新部门信息失败");
			pst1.close();
			
			String sql2 = "update t_employee set salary = salary + ? where did = ?";
			PreparedStatement pst2 = conn.prepareStatement(sql2);
			pst2.setObject(1, 20000);
			pst2.setObject(2, 4);
			int len2 = pst2.executeUpdate();
			System.out.println(len2>0?"更新部门信息成功":"更新部门信息失败");
			pst2.close();
			
			conn.commit();
		}catch (Exception e) {
			try {
				if(conn!=null){
					conn.rollback();
				}
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} finally{
			try {
				if(conn!=null){
					//恢复自动提交
					conn.setAutoCommit(true);
					//释放连接
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

6. 批处理

当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。

JDBC的批量处理语句包括下面两个方法:

addBatch():添加需要批量处理的SQL语句或参数
executeBatch():执行批量处理语句;
clearBatch():清空批处理包的语句

通常我们会遇到两种批量执行SQL语句的情况:

多条SQL语句的批量处理;


一个SQL语句的批量传参;


注意

JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatements=true

PreparedStatement作批处理插入时使用values(使用value没有效果)

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

import org.junit.Test;

public class TestBatch {
	@Test
	public void noBatch()throws Exception{
		Class.forName("com.mysql.jdbc.Driver");
		
		String url = "jdbc:mysql://localhost:3306/test";
		String user = "root";
		String password = "123456";
		Connection conn = DriverManager.getConnection(url, user, password);
		
		
        String sql = "INSERT INTO t_department(dname,description) VALUES(?,?)";
        PreparedStatement st = conn.prepareStatement(sql);
        
        for(int i=0; i<1000; i++){
        	st.setString(1, "测试部门" + i);
        	st.setString(2, "测试部门描述"  + i);
     

以上是关于JDBC 万字长文总结回炉重造的主要内容,如果未能解决你的问题,请参考以下文章

回炉重造MySQL基础知识

JavaScript回炉重造

宝藏级全网最全的Pandas详细教程(2万字总结)

宝藏级全网最全的Pandas详细教程(2万字总结)

JavaScript回炉重造

强烈推荐一文带你搞定Python基础篇(回炉重造)