Connection对象概述:
一个Connection对象表示通过jdbc驱动与数据源建立的连接,一个Connection对象可以维护多个数据源。从JDBC驱动的角度来看Connection对象可以表示客户端会话,因此它需要一些相关的状态信息,例如用户ID、sql语句、会话中使用的结果集、事务隔离级别等信息。
上一篇文章我们知道可以通过两种方式来获取Connection对象:
1)、通过JDBC API中提供的DriverManager类获取
2)、通过DataSource接口获取(推荐)
一般推荐第二种方式,因为其使得应用程序可以透明的使用连接池、分布式事务。现在几乎所有的JavaEE项目都是用DataSource的具体实现来维护应用程序和数据库的连接,目前主流的C3P0、DBCP、Druid等都是javax.sql.DataSource接口的具体实现。
一、JDBC驱动类型
JDBC的驱动程序有很多种实现,主要有下面这几种:
1)、JDBC-ODBC Bridge Driver
刚发布JDBC规范时,市场上的JDBC驱动很少,而ODBC的驱动方案几乎可以连接所有类型的数据源,所以就sun公司就发布了JDBC-ODBC的桥接驱动,将JDBC调用转为ODBC调用,不过多了一层转换,性能是比较低的
2)、Native API Driver
这类驱动会直接调用数据库提供的客户端或者链接库,虽然少了中间层性能提升了,但是驱动程序与数据库平台绑定了,无法到达JDBC跨平台的基本目的
3)、JDBC-NET Driver
这类驱动将JDBC调用转化为独立于数据库的协议,使用中间组件再将其转化为数据库通信协议,这样虽然有更好的架构灵活性,而且厂商也可以自行开发提供额外的功能,但是多了一个中间组件进行转换传递,性能还是有一定影响(微软的ADO.NET就是这种架构)
4)、Native Protocol Driver(主流)
这是最常见的驱动,基本现在的开发都是这种类型的驱动,通常由数据库厂商提供1,例如mysql的mysql-connector-java。该类驱动把JDBC调用直接转为数据库特定的通信协议,少了中间组件的协议转换和转发,该类驱动可以纯java实现,支持跨平台性能也较好。
二、java.sql.Driver接口
1、Driver接口
所有的JDBC驱动都必须实现Driver接口,而且这些类必须包含一个静态初始化代码块,我们知道静态化代码块会在类初始化时调用,驱动实现类会在这个静态代码块中像DriverManager注册一个自己的实例(即驱动类的实例),例如:
当我们加载驱动实现类时,就会调用该代码往DriverManager注册一个驱动类实例对象,这就是为什么JDBC4.0之前使用JDBC操作数据库时需要先加载驱动类:
Class.forName("com.mysql.cj.jdbc.Driver"):
为了确保驱动程序可以使用这种机制加载,Driver实现类会提供一个无参构造方法。
当我们试图使用DriverManager与数据库建立连接时,DriverManager会通过驱动实现类(Driver接口的实现类)调用重写方法connect(),还可以调用其acceptsURL方法来查看是否能成功的建立连接。Connect()方法具体如下:
Connection connect(String url, java.util.Properties info)
该方法有两个参数:一个为驱动类能识别的URL,一个是建立连接需要的额外参数(例如用户名、密码等)如果能够建立连接则会返回Connection对象,否则返回null。
注意:JDBC 4.0之后对DriverManager类的getConnection()方法做了增强,可以通过java的SPI机制加载驱动,即驱动程序需要存在一个META_INF/services/java.sql.Driver文件,且该文件中指定Driver接口的实现类。
下面我们就来看看什么是java的SPI机制:
JDBC4.0之前,使用DriverManager获取Connection对象之前都会显式的加载驱动实现类:
Class.forName("xxxxxxxxxxxxx");
JDBC4.0之后的版本对此作了改进,我们不在需要显式的加载驱动实现类,而是借用java的SPI机制进行加载驱动实现类。
2、java的SPI机制
SPI是jdk内置的一种服务提供发现机制,是一种动态替换发现的机制,例如想要在运行时给某个接口动态添加实现,此时只需要编写一个实现并进行指定,SPI机制就会在运行时发现。
当厂商提供了驱动实现类,然后在classpath下的META-INF/services目录创建一个以驱动接口命名的文件,然后在文件中指定具体的驱动实现类,此时SPI机制在运行时会自动加载这个驱动实现类。
SPI机制中查找实现类的工具类是java.util.ServiceLoader,该类有一个静态load方法,用于加载指定接口的所有实现类(即驱动实现类),调用该方法后META-INF/services目录下的java.sql.Driver文件指定的驱动实现类会被加载(必须时全限定名)
我们可以看看这个工具类是在什么时候被调用的:
当我们使用DriverManager方式获取Connection对象,我们可以先看看DriverManager类中定义了一块静态代码:
即在调用getConnection方法前,先执行了这个静态代码块,我们进入这个方法:
可以看到这里使用了ServiceLoader工具类加载了java.sql.Driver接口的所有实现类,然后迭代所有实现类,而驱动实现类会在自己的静态代码块中将自己的实例注册到DriverManager中,这样就取代了Class.forName方法加载驱动的过程。
3、java.sql.DriverAction接口
我们上面知道,DriverManager类可以通过调用registerDriver方法来注册驱动实现类,也可以调用deregisterDriver方法来解除注册,而DriverManager提供了另一个接口DriverAction用来监听deregisterDriver方法。一般开发人员不会去关注这个接口,而是由驱动开发商去使用,所以DriverAction的实现类通常会作为私有的内部类,避免被其他程序使用。要使的deregisterDriver方法能够被监听,一般需要驱动实现类的静态代码块中一起将这个接口一起注册进去,例如:
该接口开发人员只需了解即可
4、java.sql.DriverManager类
上面已经说了很多次了,DriverManager类通过Driver接口为JDBC客户端管理一些可用的驱动实现类,可以通过getConnection方法获取Connetion对象。其中最主要的是下面两个方法:
其中 getConnection方法还有两个重载的方法:
5、javax.sql.DataSource接口
上面一直在说通过DriverManager获取Connection对象,但是这种方式并不是现在的主流,现在主流的做法是使用DataSource接口,JDBC驱动程序中会对DataSource接口进行实现。
在应用程序中我们可以使用JNDI把一个逻辑名称和数据源对象建立映射关系(即一个逻辑名称绑定一个DataSource对象),如果数据库的信息发生改变,我们只需要修改DataSource对象的属性值即可,这样就不用去修改应用的任何代码。
DataSource接口主要提供下面两种功能:(DataSource接口的实现必须包含一个无参构造方法)
1)、通过连接池提高系统性能和伸缩性
2)、通过XADataSource接口支持分布式事务
JDBC API中对DataSource对象定义了一些属性来描述数据源的实现:
我们也可以自己加上一些自定义属性(增强DataSource的实现类),不过每个属性必须有get、set方法且需要在创建DataSource对象时进行初始化, 一般不建议直接访问DataSource实现类的属性,而是通过增强其实现类,然后调用增强方法来访问属性。另外客户端操作的增强类对象的属性的get、set方法一般不需要暴露给客户端,如果客户端需要访问实现类的属性,可以使用java的内省机制。
6、使用JNDI增强应用的可移植性
需要注意的是,JDK只提供了JNDI规范,具体的实现由不同的厂商来完成,例如mybatis、tomcat中就有 对JNDI的实现。
我们可以看SpringBoot+mybstis通过JNDI实现多数据源的案例:
springboot+mybatis实现多数据源_springboot+mybatis多数据源_老F的技术屋的博客-CSDN博客
7、关闭Connection对象
三、Statement接口与其子接口PreparedStatement、CallableStatement
Statement接口中定义了执行sql语句的方法,PreparedStatement接口中增加了设置sql参数的方法,CallableStatement接口继承PreParedStatement并在其基础上增加了调用存储过程以及检索存储过程调用结果的方法。
1、java.sql.Statement接口
Statement接口的具体实现是由JDBC驱动来完成的,我们可以通过调用Connection对象的createStatement方法来获取Statement对象,一个Connection对象可以创建多个Statement对象。
此外Connection接口还提供了几个重载的createConnection方法,用于通过Statement对象指定ResultSet结果集的属性,例如:
Statement接口主要是用来和数据库进行交互,该接口中定义了一些数据库操作和检索sql执行结果相关的方法:
2、java.sql.PreparedStatement接口
PreparedStatement接口继承Statement接口,在其基础上增加了参数占位符的功能(使用“?”作为占位符),PreparedStatement接口新增了一些方法可以为占位符设置值,而且PreparedStatement的实例可以预编译SQL语句,且执行一次后后续多次执行效率会较快。
获取PreparedStatement对象只需要Connection对象调用preparedStatement方法接口返回,不过该方法需要一个sql语句作为参数。
3、java.sql.CallableStatement接口
4、获取自增的键值
五、ResultSet(结果集)详解
RsultSet接口提供了检索和操作sql执行结果的相关方法
1、ResultSet的类型
ResultSet对象主要有三种类型,主要体现在游标可操作、ResultSet对象的修改对数据库影响等方面。
1)、Type_Forward_Only
这种类型的ResultSet不可滚动,游标只能向前移动(从第一行到最后一行,不允许向后移动),即只能使用ResultSet的next方法,不能使用previous方法否则会报错。
2)、Type_Scroll_Inseneitive
这种类型的ResultSet是可以滚动的,即游标可以向前向后移动,也可以移动到指定位置,当ResultSet没有关闭时,修改ResultSet对象不会影响到数据库中的记录,数据库的修改也不会影响到ResultSet对象
3)、Type_Scroll_Sensitive
这种类型是可滚动、可移动到绝对位置,但是如果ResultSet未关闭,修改ResultSet对象会影响到数据库中的记录,数据库数据修改也会影响到ResultSet对象。
默认情况下ResultSet的类型是Type_Forward_Only,DatabaseMetaData接口中提供了一个supportsResultType方法用来判断数据库驱动是否支持某种类型的ResultSet对象。可以通过调用ResultSet对象的getType方法来确定他的类型。
2、ResultSet的并行性
1)、Concur_Read_Only:当ResultSet设置这个属性时,只能从ResultSet中读取数据,不能修改数据。
2)、Concur_Updatable:可以从ResultSet读取数据,也能更新ResultSet的数据然后更新到数据库中去。
3、ResultSet的可保持性
4、ResultSet属性的设置(即设置ResultSet的类型、并发性、可保持性等属性)
5、ResultSet游标
6、修改ResultSet对象(修改新增删除行)
并行性为Concur_Updatable的ResultSet对象可以使用ResultSet接口提供的方法对其进行修改并更新到数据库中去:
例如上面的代码,对ResultSet对象进行修改主要分两步
1)、为某一行的数据设置新的值(完成后此时只是修改了ResultSet的数据,还没同步到数据库中)
2)、如果ResultSet未关闭,则将ResultSet的数据同步到数据库中去。
如上面的代码,先设置并行级别为可修改,然后执行sql返回ResultSet对象,调用next方法定位到第一行,然后调用updateString翻翻噶更新第一行的author字段,接着调用ResultSet的updateRow方法将ResultSet对象的修改应用到数据库。
对ResultSet进行删除行操作:
对ResultSet进行新增行操作:
7、关闭ResultSet对象
六、DatabaseMetaData接口详解
该接口的实现是由驱动程序实现的,上面就经常提到这个接口可以用来获取数据源的一些属性信息(例如数据源基本信息、数据源的限制信息、包含哪些sql对象以及这些对象的属性、数据源对事务的支持信息等等)
1、创建DatabaseMetaData对象
2、获取数据源基本信息
3、获取数据源支持特性
4、获取数据源限制
5、获取sql对象及属性
6、获取事务支持
七、JDBC事务
JDBC事务主要有下面几个概念:
1)、自动提交模式
2)、事务隔离级别
3)、保存点
1、事务边界与自动提交
2、事务的隔离级别
同一个事务中数据的修改等操作是可见的,而事务的隔离级别是用来指定不同事务间的数据可见性,解决一致性等问题。
下面就是数据并发访问是可能出现的几个问题:
JDB中有4种隔离级别+一种不支持事务,具体如下:
(不同数据库的隔离级别处理方式可能不同,例如mysql的RR级别能够处理部分场景下的幻读)
3、事务中的保存点