JDBC 万字长文总结回炉重造
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC 万字长文总结回炉重造相关的知识,希望对你有一定的参考价值。
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程序编写步骤
- 注册驱动
- 获取连接
- 执行增删改查
- 释放资源
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 万字长文总结回炉重造的主要内容,如果未能解决你的问题,请参考以下文章