jdbc基本架构的实现

备注:本文是学习高新强的jdbc课程后,根据老师讲课的思路总结而成。

Java控制mysql的基本架构

1 .java连接数据库的基本流程

1.1.JDBC

        JDBC(Java Data Base Connectivity,java数据库连接),它是由一组用Java语言编写的类和接口组成。java.sql包就是JDBC的API。java连接数据库时,将调用JDBC相关的类以及其中的方法,这些具体的实现方法是由被连接的数据库方提供的。如在连接mysql数据库时,要先往java项目中导入由mysql官方提供的mysql-connector-java-8.0.jar(下载地址:https://dev.mysql.com/downloads/connector/j/)包,里面包含了JDBC具体的实现(如图1)。

        先将该包复制到lib文件夹中,然后点击Build Path。

jdbc基本架构的实现

图1

1.2连接mysql的基本流程

1.2.1加载驱动

Class.forName(“com.mysql.cj.jdbc.Driver”);

        加载驱动:通过加载字节码的方式,将java和数据库进行了连接,源码如图2。jdbc基本架构的实现

图2

个人理解:

        上述字节码被加载后,直接执行了静态代码块中的代码,即首先new一个新的驱动driver,然后将该驱动register登记到驱动管理器DriverManager中。

        显然这里的DriverManager是一个普通类,而registerDriver是这个类中的静态方法。1.2.2连接数据库

Connection conn = DriverManage.getConnection(url, user, pwd);

url="jdbc:mysql://localhost:3306/jdbc_db?&serverTimezone=UTC"; 

1.2.3 sql语句的建立与执行
        1Statement st = conn.createStatement();

        createStatement的文档解释:

        Creates a Statement object for sending SQL statements to the database. SQL statements without parameters are normally executed using Statement objects. If the same SQL statement is executed many times, it may be more efficient to use a PreparedStatement object.

        创建一个Statement 对象用于向数据库发送并执行SQL命令句。没有参数的SQL命令句常用Statement 对象来执行。如果一个SQL命令句经常被执行,那么采用PreparedStatement 对象来操作可能更有效率。

2String sql = "insert into stu values(2, 'ls', 18)";

3int row = st.executeUpdate(sql);

executeUpdate的文档解释:

   Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.

        一般执行insert、update、delete命令句,也可以执行那些不返回任何东西的命令句,如sql的DDL命令句。DDL命令句即数据库模式的定义语言,常见的数据库定义语言有:create database;创建表create table;

1.2.4 资源的关闭

        1st.close();

close的文档解释:

Releases this Statement object's database and JDBC resources immediately instead of waiting for this to happen when it is automatically closed. It is generally good practice to release resources as soon as you are finished with them to avoid tying up database resources.

立即释放Statement对象的数据库以及JDBC资源,而不是等它被自动释放。通常,最好在完成相应命令句后立即释放资源,以避免占用数据库资源。

2conn.close();

close的文档解释:

Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be automatically released.

立即释放这个连接对象的数据库以及JDBC资源,而不是等待它们被自动释放。

1.2.4 java通过jdbc向数据库插入一条数据的基本操作流程

        上述代码会抛出很多异常,完整的基本连接代码如图3

jdbc基本架构的实现

图3

2 .代码的重构

2.1基本架构

        jdbc.util包:该包存储的代码与数据库的连接相关

        jdbc.dao包:定义一个操作数据库的方法接口

        jdbc.dao.impl包:操作数据库方法接口的实现

        jdbc.domin包:与被操作表相同的属性类

2.2 insert、delete、update操作的代码重构

2.2.1创建一个数据类

        数据类中的实例是与数据库中要操作表的属性对应,并实现了geter、seter方法。

如图4建立一个雇员类:

jdbc基本架构的实现

图4

2.2.2定义一个dao接口

        定义一个IEmployeeDao接口(如图5),其中包含了常用的insert、update、delete三种操作数据库的方法。

jdbc基本架构的实现

图5

2.2.3建立一个通用的方法

如图6jdbc基本架构的实现

三种方法的封装

如图7

jdbc基本架构的实现

2.3增加新的操作数据库方法

        数据库的查询工作也是最常用的方法,这里给出两个方法get、getAll,前者得到数据库的特定一条数据,后者查询数据库中的所有数据。

2.3.1查询操作的基本流程

1)加载驱动:Class.forName(“com.mysql.cj.jdbc.Driver”);

        2)连接数据库:

Connection conn = DriverManage.getConnection(url, user, pwd);

        3)建立SQL的查询语句:

        String sql = “selection * from employee where id = ?”;

        4)建立预编译执行语句:

        PreparedStatement ps = conn.prepareStatement(sql);

   prepareStatement的文档解释:

        Creates a PreparedStatement object for sending parameterized SQL statements to the database.

        创建一个将含参SQL命令句发送到数据库的PreparedStatement对象

        A SQL statement with or without IN parameters can be pre-compiled and stored in a PreparedStatement object. This object can then be used to efficiently execute this statement multiple times.

        带参或者无参SQL命令句可以被预编译并储存在一个PreparedStatement对象内。然后这个对象可以多次高效第执行此语句。

   ps.setInt(1, id);

   ResultSet rs = ps.executeQuery();

   ResultSet的文档解释:

        A table of data representing a database result set, which is usually generated by executing a statement that queries the database.

        表示数据库结果集的数据表,它通常是由执行查询数据库的语句生成的。

A ResultSet object maintains a cursor pointing to its current row of data. Initially the cursor is positioned before the first row. The next method moves the cursor to the next row, and because it returns false when there are no more rows in the ResultSet object, it can be used in a while loop to iterate through the result set.

cursor:光标

结果集对象维护一个指向当前数据行的光标。最初光标指向第一行数据之前。next命令

移动光标指向下一行,因为当result set对象中没有更多行时,它返回false,所以可以在while循环中使用它来迭代结果集。

The ResultSet interface provides getter methods (getBoolean, getLong and so on) for retrieving column values from the current row. Values can be retrieved using either the index number of the column or the name of the column. In general, using the column index will be more efficient. Columns are numbered from 1. For maximum portability, result set columns within each row should be read in left-to-right order, and each column should be read only once.

Retrieving:检索portability:可移植性

ResultSet接口提供getter方法(如getBoolean、getLong等)用于检索当前行中的列值。

可以用列的索引号或者列的名称来检索值。一般来说,使用列的索引号会更有效率。列从1开始编号。为了实现最大的可移植性,每行中的结果集列应该从左向右的顺序读取,并且每列应该只读取一次。

   5)输出结果:

   if(rs.next())

{

   Employee e = new Employee();

   e.setId(rs.getInt(“id”));

e.setName(rs.getString(“name”));

e.setage(rs.getInt(“age”));

e.setSalary(rs.getSalary(“salary”));

system.out.println(e);

}

   6)关闭资源

   rs.close();

   ps.close();

   conn.close();

2.3.2查询操作基本流程代码

如图8

jdbc基本架构的实现

jdbc基本架构的实现

2.3.3查询操作代码的重构

        新增加的查询操作代码与insert、delete、update的代码略有不同,根据之前代码重构的思想可以同样将查询操作的代码进行抽取。并在IEmployeeDao接口中定义新增的操作。

如图9:

jdbc基本架构的实现

jdbc基本架构的实现

        此时的IEmployeeDao以及IEmployeeDaoImp类如图10、11

jdbc基本架构的实现

图10

jdbc基本架构的实现

图11

3代码存在的问题以及重构

3.1代码存在的问题

        上述代码存在的两个典型问题:

        1)CRUDTemplate中的executeUpdate与excuteQuery方法存在着大量代码的重复,连接数据库的代码与关闭资源的代码重复

        2)每进行一次数据库操作都要进行数据库的连接和资源的关闭,这在频繁使用数据库操作时造成巨大的资源浪费。

3.2代码的重构

3.2.1代码重复的解决

        两种方法中以下代码重复:

        1)加载驱动与数据库连接代码重复

        2)关闭资源的代码重复

        在com.it666.jdbc.test中新增一个CRUDUTIL类用于编写java直接与数据库连接的代码(如图12)。

jdbc基本架构的实现

图 12

仔细体会这个类中代码的编写,以及CRUDTemplate中对这些代码的调取(如图13、14)。jdbc基本架构的实现 图13

jdbc基本架构的实现

jdbc基本架构的实现

图14

3.2.2资源浪费的解决

3.2.2.1数据库连接池

        数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”,预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,从“缓冲池”中取出一个,使用完毕后再放回去。

        可以通过设定连接池最大连接数来放置系统无尽的与数据库相连接,可以通过连接池的管理机制监视数据库的连接的数量,使用情况,为系统开发、测试及性能调整提供依据。

        Old         :       Application  -------->      Mysql

        New        :       Application  -------->      连接池    -------->     Mysql

3.2.2.2使用数据库连接池的基本流程

1)new一个连接池对象:

BasicDataSource ds = new BasicDataSource();

https://crunchify.com/java-what-is-basicdatasource-how-to-create-basicdatasource-object/

2)连接池对象与数据库相连

ds.setDriverClassName(“com.mysql.cj.jdbc.Driver”);

ds.setUserName(“root”);

ds.setPassword(“8925750”);

ds.setUrl(“jdbc:mysql://localhost:3306/jdbc_db ?&serverTimezone=UTC”);

3)从连接池中获取Connection对象

Connection conn = ds.getConnection();

后续的操作与以前的操作相同。只需更改JDBCUtil.java文件中的内容即可,更改如图15

jdbc基本架构的实现

jdbc基本架构的实现

图15

 

 

3.2.3 配置文件(补充)

        连接数据库时,要输入的url、username、password在不同的用户环境下,均会改变,将它们放入一个单独的配置文件中是比较好的管理方法。

3.2.3.1 java读取properties文件的一般流程

        1)new一个properties对象:

Properties p = new Properties();

2)new一个FileInputStream对象,加载指定路径文件

FileInputStream in = new FileInputStream(“resource/db.properties”);

3)调取load方法

p.load(in);

一般操作(如图17):在项目中新增一个java Folder文件夹,在文件夹中新建file文件,并将配置信息写入并保存(文件写入格式如图16)。:

jdbc基本架构的实现

图16

jdbc基本架构的实现

图17

      阿里巴巴公司开源的druid.jar包是专门用来管理数据库连接池的有效工具,通过调用:

DataSource ds = DruidDataSourceFactory.createDataSource(p);

建立一个ds资源池,然后ds.getConnection()可以创建一个Connection对象进行后续操作,这样JDBCUtil方法的最终代码如图18.

jdbc基本架构的实现

图18

4 结果集的处理以及泛型

        上述代码进行重构,主要针对代码重复问题抽取了insert、update、delete的公共模块以及query的公共模块,然后两个模块的公共部分即数据库的连接以及资源的关闭公共部分再进行抽取,最后引入连接池对java连接数据库的性能进行了优化。

        但是上述代码依然存在问题,一是代码只能针对Employee类进行操作,如果对其它类进行操作要修改具体的代码细节,代码不具备泛化和扩展能力;二是针对查询操作结果集的处理也比较死板,本节针对这两个问题继续进行代码的重构。

4.1结果集处理器

        定义一个结果集处理器的接口,碰到具体的类时,再具体实现具体的结果集处理器。定义接口时最好不指定特定的返回类型,而在具体接口实现时指定特定返回类型,这是定义接口时的一般原则(如图19)。

­­­jdbc基本架构的实现

图19

        将以前直接写入CRUDTemplate.executeQuery中具体处理结果集的代码放入IRSHandlerImp_Employee的接口实现文件中(如图20)

jdbc基本架构的实现

图20

        然后CRUDTemplate.executeQuery()方法调整为泛型方法:

public static <T>T executeQuery(String sql, IRSHandler<T> rh, Object...params)具体改写如图21。

jdbc基本架构的实现

21

至此,jdbc的框架基本实现,若想将上述代码更加智能,依然是对结果集的处理代码的改进,通过内省可以自动识别domin类中的属性。

5 结果集的智能处理

5.1内省

BeanInfo bf = Introspector.getBeanInfo(this.classType, Object.class)

BeanInfo的文档解释:

Use the {@code BeanInfo} interface to create a {@code BeanInfo} class and provide explicit information about the methods, properties, events, and other features of your beans.

使用 beaninfo接口创建beaninfo类,并提供有关bean的方法、属性、事件和其他特性的显式信息。

Introspector的文档解释:

The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.

内省类提供了标准的方法工具来学习目标java Bean所支持的属性,事件和方法。

For each of those three kinds of information, the Introspector will separately analyze the bean's class and superclasses looking for either explicit or implicit information and use that information to build a BeanInfo object that comprehensively describes the target bean.

对于这三种信息中的每一种,内省器将分别分析bean的类和超类,寻找显式或隐式信息,并使用这些信息构建一个全面描述目标bean的beaninfo对象。

getBeanInfo方法的文档解释:

Introspect on a Java Bean and learn about all its properties, exposed methods and events, below a given {@code stopClass} point subject to some control {@code flags}.

对Java bean进行内省,并在给定的“停止”点下了解其属性、暴露方法。

源码如下图:

jdbc基本架构的实现

        结果集智能处理后给出最终的代码架构:

        1、结果集处理接口:

jdbc基本架构的实现

        2、结果集处理接口的两种实现,分别是返回单值、返回列表,单值常用来目标查找,列表用来范围查找。

jdbc基本架构的实现

jdbc基本架构的实现

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3、具体方法

jdbc基本架构的实现

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4、数据库底层连接

jdbc基本架构的实现