JDBC相关知识
总结黑马视频JDBC原理视频地址
1. JDBC简介
1.1 JDBC概念
- JDBC就是使用Java语言操作关系型数据库的一套API(接口)
- Java DataBase Connectivity(Java 数据库连接)
JDBC就是java用来操作关系型数据库的接口:关系型数据库有很多,如:mysql,oracle,dB2等,当同一套java代码去操作不同的关系型数据库时,如没有JDBC则无法操作,因为每个关系型数据库对应的Java代码不同,因此提出了==JDBC:规则,可保证同一套Java代码可操作不同的关系型数据库==。如图所示
1.2 JDBC本质
- 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
1.3 JDBC的优点
- 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
- 可随时替换底层数据库,访问数据库的java代码基本不变
2. JDBC快速入门
步骤:
-
创建工程,导入jar包
-
注册驱动
Class.forName("com.mysql.jdbc.Driver");
-
获取连接
Connection conn=DriverManager.getConnection(url,username,password);
-
定义SQL语句
String sql="update..."
-
获取执行SQL对象
Statement stmt=conn.createStatement();
-
执行SQL
stmt.executeUpdate(sql);
-
处理返回结果
-
释放资源
//代码实例 package com.itheima.jdbc; //JDBC API import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; /** * JDBC快速入门 */ public class jdbc { public static void main(String[] args) throws Exception { //1、注册驱动,将代码用的哪个实现类加载进来 Class.forName("com.mysql.jdbc.Driver"); //2、获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称 String username="root";//用户名 String password="123456";//密码 Connection conn=DriverManager.getConnection(url,username,password); //3、定义sql语句,更新account表中id=1的对应的money的数值为2000 String sql="update account set money=2000 where id=1"; //4、获取执行sql的对象Statement Statement stmt=conn.createStatement(); //5、执行sql int count=stmt.executeUpdate(sql); //6、处理结果:返回受影响的行数来确定执行是否成功 System.out.println(count); //7、释放资源,按照顺序,最先生成的最后释放 stmt.close(); conn.close(); } }
3. JDBC的API详解
3.1 DriverManager:驱动管理类
-
注册驱动:DriverManager.registerDriver()
//1、注册驱动,将代码用的哪个实现类加载进来 Class.forName("com.mysql.jdbc.Driver"); //查看Driver源码: static{ try{ DriverManager.registerDriver(new Driver()); }catch(SQLException var1){ throw new RuntimeException("Can't register driver!") } }
提示:
- MySQL5之后的驱动包,可以省略注册驱动的步骤
- 自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
-
获取连接:
DriverManager.getConnection(String url,String user,String password); //参数说明 /** 1、url:连接路径 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2... 示例:jdbc:mysql://127.0.0.1:3306/db1 细节: ①如果连接的是本机sql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对 ②配置useSSL=false参数,禁用安全连接方式,解决警告提示 2、user:用户名 3、password:密码 */
3.2 Connection:数据库连接对象
-
获取执行SQL的对象
- 普通执行SQL对象
Statement creatStatement()
- 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement preparedStatement(sql)
- 执行存储过程的对象
CallableStatement prepareCall(sql)
-
事务管理
- MySQL事务管理
开启事务:BEGIN;/START TRANSACTION;
提交事务:COMMIT
回滚事务:ROLLBACK
- JDBC事务管理:Connection接口中定义了3个对应的方法
开启事务:setAutoCommit(bollean sutoCommit):true为自动提交事务;false为手动提交事务,即为开启事务
提交事务:commit()
回滚事务:rollback()
package com.itheima.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; /** * JDBC的Connection API:事务管理详解 */ public class Jdbc_Connection { public static void main(String[] args) throws Exception { //1、注册驱动,将代码用的哪个实现类加载进来 Class.forName("com.mysql.jdbc.Driver"); //2、获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称 String username="root";//用户名 String password="123456";//密码 Connection conn=DriverManager.getConnection(url,username,password); //3、定义sql语句,更新account表中id=1的对应的money的数值为2000 String sql1="update account set money=2000 where id=1"; String sql2="update account set money=2000 where id=2"; //4、获取执行sql的对象Statement Statement stmt=conn.createStatement(); try { //开启事务 conn.setAutoCommit(false); //5、执行sql int count1 = stmt.executeUpdate(sql1); //6、处理结果:返回受影响的行数来确定执行是否成功 System.out.println(count1); //设置异常 int a = 1 / 0; int count2 = stmt.executeUpdate(sql2); System.out.println(count2); //提交事务 conn.commit(); } catch (Exception throwables) { //回滚事务 conn.rollback(); throwables.printStackTrace(); } //7、释放资源 stmt.close(); conn.close(); } }
3.3 Statement
-
执行SQL语句
int executeUpdate(sql):执行DML,DDL语句
- 返回值:
- DML语句影响的行数
- DDL语句执行后,执行成功也可能返回0
package com.itheima.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class Jdbc_Statement { /** * 执行DML语句:修改,添加,删除 * @throws Exception */ @Test public void testDML() throws Exception { //1、注册驱动,将代码用的哪个实现类加载进来 Class.forName("com.mysql.jdbc.Driver"); //2、获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称 String username="root";//用户名 String password="123456";//密码 Connection conn=DriverManager.getConnection(url,username,password); //3、定义sql语句,更新account表中id=1的对应的money的数值为2000 String sql="update account set money=2000 where id=1"; //4、获取执行sql的对象Statement Statement stmt=conn.createStatement(); //5、执行sql int count=stmt.executeUpdate(sql); //6、处理结果:执行DML语句后返回受影响的行数来确定执行是否成功 //System.out.println(count); if(count>0){ System.out.println("修改成功"); }else{ System.out.println("修改失败"); } //7、释放资源 stmt.close(); conn.close(); } /** * 执行DDL语句:数据库定义语言:创建、删除、修改:数据库、表等 * @throws Exception */ @Test public void testDML() throws Exception { //1、注册驱动,将代码用的哪个实现类加载进来 Class.forName("com.mysql.jdbc.Driver"); //2、获取连接 String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称 String username="root";//用户名 String password="123456";//密码 Connection conn=DriverManager.getConnection(url,username,password); //3、定义sql语句,更新account表中id=1的对应的money的数值为2000 String sql="create database db2"; //4、获取执行sql的对象Statement Statement stmt=conn.createStatement(); //5、执行sql int count=stmt.executeUpdate(sql); //6、处理结果:执行DML语句后返回受影响的行数来确定执行是否成功 //System.out.println(count); if(count>0){ System.out.println("修改成功"); }else{ System.out.println("修改失败"); } //7、释放资源 stmt.close(); conn.close(); } }
- ResultSet executQuery(sql):执行DQL语句
- 返回值:ResultSet 结果集对象
- 返回值:
3.4 ResultSet
-
ResultSet(结果集对象)作用:
-
封装了DQL查询语句的结果
ResultSet stmt.executeQuery(sql):执行DQL语句,返回ResultSet对象
-
-
获取查询结果
boolean next():(1)将光标从当前位置向前移动一行(2)判断当前行是否为有效行 返回值: 1. true:有效行,当前行有数据 2. false:无效行,当前行没有数据
xxx getXxx(参数):获取数据 xxx:数据类型;如:int getInt(参数);String getString(参数) 参数:int:列的编号,从1开始;String:列的名称
-
使用步骤
/** 1.游标向下移动一行,并判断该行是否有数据:next() 2.获取数据:getXxx(参数) */ //循环判断游标是否是最后一行末尾 while(rs.next()){ //获取数据 rs.getXxx(参数); }
//实例1:
public class Jdbc_ResultSet {
@Test
public void testResultSet() throws Exception {
//1、注册驱动,将代码用的哪个实现类加载进来
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称
String username="root";//用户名
String password="123456";//密码
Connection conn=DriverManager.getConnection(url,username,password);
//3、定义sql
String sql="select * from account";
//4、获取执行对象
Statement stmt=conn.createStatement();
//5、执行sql
ResultSet rs=stmt.executeQuery(sql);
//6、处理结果,遍历rs中的所有数据
//6、1光标向下移动
/* while(rs.next()){
int id=rs.getInt(1);
String name=rs.getString(2);
int money=rs.getInt(3);
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("==========");
}*/
while(rs.next()){
int id=rs.getInt("id");
String name=rs.getString("name");
int money=rs.getInt("money");
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("==========");
}
//7、释放资源
rs.close();
stmt.close();
conn.close();
}
//实例2:需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
package com.itheima.jdbc;
import com.itheima.pojo.Account;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class Jdbc_ResultSet {
/**
* :需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
* 1、定义实体类Account
* 2、查询数据,封装到Account对象中
* 3、将Account对象存入ArrayList集合中
* @throws Exception
*/
@Test
public void testResultSet2() throws Exception {
//1、注册驱动,将代码用的哪个实现类加载进来
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
String url="jdbc:mysql://127.0.0.1:3306/db1";//固定写法,最后是数据库的名称
String username="root";//用户名
String password="123456";//密码
Connection conn=DriverManager.getConnection(url,username,password);
//3、定义sql
String sql="select * from account";
//4、获取执行对象
Statement stmt=conn.createStatement();
//5、执行sql
ResultSet rs=stmt.executeQuery(sql);
//创建一个集合
List<Account> list=new ArrayList<>();
//6、处理结果,遍历rs中的所有数据
while(rs.next()){
//对象的创建
Account account=new Account();
int id=rs.getInt("id");
String name=rs.getString("name");
double money=rs.getInt("money");
account.setId(id);
account.setName(name);
account.setMoney(money);
//存入集合
list.add(account);
}
//7、释放资源
rs.close();
stmt.close();
conn.close();
}
}
实例2需求图:
4. 数据库连接池
4.1 什么是连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
4.2 为什么要使用连接池
数据库连接-问题:数据库连接是一种关键、有限、昂贵的资源。一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样会造成系统的性能低下。
数据库连接池-解决方案:在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(即在一个“池”中存放好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。当出现并发请求时(即连接对象数大于连接池中的连接数时),应该在请求队列中排队等待。此外,应用程序可以根据池中连接的使用率,动态地增加或减少池中的连接数。
连接池技术-优点:尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,可通过其自身的管理机制来监视数据库连接的数量、使用情况等。
4.3传统的连接机制与数据库连接池的运行机制区别
不使用连接池流程
以访问MySQL为例,执行一个sql命令,若不使用连接池,需要经过哪些流程。
步骤:
- TCP建立连接的三次握手(服务端和客户端建立连接)
- MySQL认证的三次握手
- 真正的SQL执行
- MySQL的关闭
- TCP的四次握手关闭
优点:
- 实现简单
缺点:
- 网络IO较多
- 数据库的负载较高
- 响应时间(RT,Response Time,表示处理请求快慢的指标)较长且QPS(Query per Second,直接反应应用吞吐能力的指标)较低。
- 应用频繁的创建链接和关闭连接,导致临时对象较多,GC(Garbage collection,垃圾收集)频繁
- 在关闭连接后,会出现大量TIME_WAIT(等待时间)的TCP状态(在2各MSL之后关闭)
使用连接池流程
步骤:第一次访问的时候,需要建立连接。但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。
优点:
- 减少了网络开销
- 系统的性能会有一个实质的提升
- 不存在TIME_WAIT状态
4.4数据库连接池工作原理
-
连接池的建立
- 一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,一遍使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector,Stack等。
-
连接池中连接的使用管理(核心)
- 客户请求数据库连接时:查看连接池中是否有空闲连接
- 若存在空间连接,则将连接分配给客户使用
- 若无空闲连接,则查看当前所开的连接数是否已达到最大连接数
- 若没达到,重新创建一个连接给请求的客户
- 若达到,按照设定的最大等待时间进行等待,若超出最大等待时间,则抛出异常给客户。
- 客户释放数据库连接时:判断该连接的引用次数是否超过了规定值
- 若超过,则直接从连接池删除该连接
- 若没超过,则保留后续为客户服务
该管理策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
- 客户请求数据库连接时:查看连接池中是否有空闲连接
-
连接池的关闭
- 当应用程序退出时,关闭连接池中所有连接,释放连接池相关的资源,该过程与创建相反。
4.5 连接池的主要参数
- 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费
- 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这回影响以后的数据库操作
- 最大空闲时间
- 获取连接超时时间
- 超时重试连接次数
4.6 连接池需要注意的点
1. 并发问题
问题:为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。
解决:各自语言自身提供了对并发管理的支持,利用synchronized(java),lock(C#)关键字即可确保线程使同步的。
2. 事务处理
事务具有原子性:对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池来说很麻烦。原因为:即使Connection类提供了相应的事务支持,可是我们仍然不确定那个数据库操作是对应那个事务的,这是优于我们有2个线程都在进行事务操作而引起的。因此,我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务的复杂性。
3. 连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的服用都,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给它,如果没有就抛出一个异常给用户。
4. 连接池的配置与维护
连接池中放置多少连接,才能使系统的性能最佳?
可采取设置最小连接数和最大连接数等参数来空间。说明:最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来确慢。这样,在开发时,设置较小的最小连接数,开发起来会很快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。
- 最大连接数:连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
- 最小连接数:动态和静态两种策略
- 动态:每隔一段时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。
- 静态:发现空闲连接不够时再去检查。