世界杯预选赛中国队赛程_世界杯多少年一次 - fybstd.com


文章目录

JBDC封装(超详细)1.前言2.导入第三方jar包2.1普通java项目2.2Maven项目

3.JDBC连接核心参数4.JDBC连接数据库5.资源管理工具类6.增删改封装(BaseDao)7.查询操作的封装7.1返回值类型7.2接口设计7.3统一查询query7.4接口实现类7.4.1MapHandler7.4.2MapListHandler7.4.3BeanListHandler7.4.4BeanHandler7.4.5ArrayListHandler7.4.6ArrayHandler

8.总结

JBDC封装(超详细)

1.前言

​ JDBC(Java Database Connectivity)是什么?它是一种规范,SUN公司要求数据库公司必须按照JDBC规范实现对应数据库Java代码,提供数据库的相关资源给java程序。在JDBC出来之前,各种数据库提供给java程序数据的方法各式各样,可以说,JDBC极大地方便了我们java开发程序员,目前和java配合最好的数据库是Oracle和MySQL数据库。

​ JDBC只是一种规范,数据库厂商根据这个标准实现自家的驱动Dirver,例如MySQL驱动com.mysql.cj.jdbc.Driver,Oracle驱动oracle.jdbc.OracleDriver,加载过驱动,我们就可以对数据库进行操作了。

2.导入第三方jar包

2.1普通java项目

如果创建的是普通的java项目,需要手动导入第三方jar包,可以自行去官网下载。

​ jar包导入项目的方法我这里就不过多赘述了。

2.2Maven项目

Maven是java项目统一管理工具,可以解决后期项目频繁导入jar包繁琐操作的问题,同时,如果是传统项目,jar包的更新迭代,我们还需要对整个项目的jar包进行替换,这里也可以用Maven项目来解决。

方法:

在pom.xml文件中写入依赖:

如下代码:

mysql

mysql-connector-java

5.1.47

​ 注释里面是这个网址,可以在里面直接复制此代码。

3.JDBC连接核心参数

1.明确连接的是哪一个数据库,提供对应驱动

导入第三方jar包

2.url:统一资源定位符,可以明确数据库在哪

需要数据库对应的主机名,域名和IP地址以及【端口号】

3.连接数据库的用户名和密码

可以新建一个jdbc.properties文件,将连接参数都写入进去。

driverClass=com.mysql.jdbc.Driver

jdbcUrl=jdbc:mysql://localhost:3306/gp_01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false

username=root

password=root

4.JDBC连接数据库

​ 我们用一个小Demo来看一下简单流程:

@Test

public void testInsert() {

//1.准备数据库连接必要参数

String jdbcUrl = "jdbc:mysql://localhost:3306/gp_01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false";

String username = "root";

String password = "root";

Connection connection = null;

Statement statement = null;

try {

//2.加载驱动

Class.forName("com.mysql.jdbc.Driver");

//3.获取数据库连接对象

connection = DriverManager.getConnection(jdbcUrl, username, password);

//4.获取数据库搬运工连接对象

statement = connection.createStatement();

/*

5.准备执行sql语句

*/

String sql = "insert into gp_01.student( name, age, gender) value ('伍宇龙',16,false)";

//6.执行sql语句

int affectRows = statement.executeUpdate(sql);

System.out.println("sql影像行数:" + affectRows);

} catch (ClassNotFoundException | SQLException e) {

e.printStackTrace();

} finally {

//7.关闭数据库资源

try {

if (statement != null) {

statement.close();

}

if (connection != null) {

connection.close();

}

} catch (SQLException e) {

e.printStackTrace();

}

}

}

​ 上面执行的插入操作,向数据库中插入数据,由此,我们可以总结一下JDBC操作数据库执行增删查改的操作流程,如下:

1.首先,明确数据库连接的必要参数

jdbcurl 统一资源定位符

driverClass 驱动类

username 用户名

password 密码

2.加载驱动

3.获取数据库连接对象 java.sql.Connection

4.准备目标SQL语句

5.通过Connection获得数据库搬运工对象,这里有两种对象,分别是Statement PreparedStatement

6.执行SQL语句,得到ResultSet结果集对象

7.解析ResultSet结果集

8.关闭资源

​ 为什么进行JDBC的封装,就是因为,我们每次对数据库操作如果都像上面的案例一样,每操作一次都进行一次数据库的连接,驱动加载,资源关闭会显得非常麻烦,所以,我们封装一个工具类,帮助我们更方便快捷的进行这些资源的管理。

5.资源管理工具类

我们创建资源管理工具类的目的:

让资源自动加载(驱动)获取数据库连接对象数据库相关操作资源的关闭方法

package com.renlon.utils;

import java.io.IOException;

import java.io.InputStream;

import java.sql.*;

import java.util.Arrays;

import java.util.Properties;

/**

* @author: renlon 2023/03/14 16:56

*/

public class JdbcUtils {

private static String jdbcUrl;

private static String username;

private static String password;

static {

//1.找到资源目录下的配置文件

InputStream input = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");

//2.实例化 Properties 对象

Properties properties = new Properties();

try {

//加载配置文件,完成配置文件的解析

properties.load(input);

//获取参数

jdbcUrl = properties.getProperty("jdbcUrl");

username = properties.getProperty("username");

password = properties.getProperty("password");

//加载驱动

Class.forName(properties.getProperty("driverClass"));

} catch (IOException | ClassNotFoundException e) {

e.printStackTrace();

} finally {

try {

if (input != null) {

input.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

/**

* 获取连接对象

*

* @return java.sql.Connection 数据库连接对象

*/

public static Connection getConnection() {

Connection connection = null;

try {

connection = DriverManager.getConnection(jdbcUrl, username, password);

} catch (SQLException e) {

e.printStackTrace();

}

return connection;

}

/**

* 关闭资源方法

*

* @param connection 数据库连接对象资源

* @param statement 数据库 SQL 语句搬运工资源

*/

public static void close(Connection connection, Statement statement) {

close(statement, connection);

}

/**

* 关闭资源方法

*

* @param connection 数据库连接对象资源

* @param statement 数据库 SQL 语句搬运工资源

* @param resultSet 数据库查询结果集对象

*/

public static void close(Connection connection, Statement statement, ResultSet resultSet) {

close(resultSet, statement, connection);

}

/**

* 内部私有化方法,统一处理数据库相关资源

*

* @param resources 不定长参数,统一关闭对应资源

*/

private static void close(AutoCloseable... resources) {

if (resources != null && resources.length > 0) {

Arrays.stream(resources).forEach(source -> {

try {

if (source != null) {

source.close();

}

} catch (Exception e) {

e.printStackTrace();

}

});

}

}

}

在static静态代码块中,静态代码块随着类的加载而加载,而且只加载一次。我们通过加载配置文件,获得了必要的数据库参数,同时加载了驱动。

获取数据库连接对象,通过DriverManager.getConnection(jdbcUrl, username, password)获得connection对象。

关闭连接资源对象,这里使用了AutoCloseable 不定长参数来传入资源对象进行关闭,重载了两个close方法,分别对应了两种情况:

​ 第一种:关闭connection,statement对象(增删查操作)

​ 第二种:关闭 connection,statement,resultSet对象(查询操作)

封装增删查改之前,我先介绍一下Statement和PreparedStatement的区别:

PreparedStatement:

预处理SQL得到PreparedStatement,支持占位参数,需要进行sql语句参数的赋值操作,可以防止sql注入的问题

Statement 是直接通过 Connection 数据库连接对象获取,只是 SQL 语句搬运工,有可能会导致 SQL 注入问题

​ 这里建议使用PreparedStatement。

6.增删改封装(BaseDao)

增删改的操作具有一定的相似性,他们操作操作数据库的的核心步骤为:

1.获取数据库连接对象connection

2.准备sql语句

3.预处理sql得到statement对象

4.对sql语句参数进行赋值操作

5.执行sql语句,并获得数据库表受影响的行数

6.关闭资源

​ 他们的核心第三,第四步都是相同的,可以封装一个query方法来进行统一预处理sql,并进行sql参数的赋值操作。

预处理sql,并赋值参数得封装

/**

* 预处理SQL,获取PreparedStatement对象,并对SQL参数进行赋值操作

*

* @param connection 数据库连接对象

* @param sql sql语句

* @param parameters 实际参数

* @return 返回PreparedStatement对象

* @throws SQLException SQL异常

*/

private static PreparedStatement handlePreparedStatement(Connection connection, String sql, Object[] parameters) throws SQLException {

//预处理 SQL 语句,得到PreparedStatement对象

PreparedStatement statement = connection.prepareStatement(sql);

//获取数据库元数据,并通过元数据获取SQL语句参数个数

int parameterCount = statement.getParameterMetaData().getParameterCount();

//条件约束,判断是否需要进行参数赋值操作

if (parameterCount != 0 && parameters != null && parameters.length == parameterCount) {

for (int i = 0; i < parameterCount; i++) {

statement.setObject(i + 1, parameters[i]);

}

}

return statement;

}

​ 这个方法同样适用于查询操作。

增删查的方法封装

/**

* 更新方法,支持 insert delete update 操作

*

* @param sql 目标执行的 SQL 语句

* @param parameters 对应 SQL 语句的参数,数据类型为 Object 不定长参数

* @return SQL 语句执行对应数据表的影响行数。

*/

public int update(String sql, Object... parameters) {

// 1. 准备必要的变量

int affectedRows = 0;

Connection connection = null;

PreparedStatement statement = null;

// 2. 利用 util.JdbcUtils 工具类获取数据库连接对象

connection = JdbcUtils.getConnection();

try {

statement = handlePreparedStatement(connection, sql, parameters);

// 5. 执行 SQL 语句,得到受影响的行数

affectedRows = statement.executeUpdate();

} catch (SQLException e) {

throw new RuntimeException(e);

} finally {

JdbcUtils.close(connection, statement);

}

return affectedRows;

}

7.查询操作的封装

7.1返回值类型

​ 查询是一个重点,我们平常用到最多的就是查询,而且查询的结果也是多种多样,这里查询返回的结果可以是:

1.查询一行数据,返回目标执行SQL符合JavaBean规范的对象(对象类型多种多样,可以用泛型T表示)

2.查询多行,返回符合JavaBean规范的对象,并存储到List集合(List)

3.查询单行数据,执行SQL查询到的数据存储到Map,字段名存储到key,value中存储字段对应的数据

4.查询多行数据,执行SQL查询到的数据存储到List>,将上面单行Map数据,存储到List集合

5.查询单行数据,执行SQL查询到的数据存储到Object[]数组。

6.查询多行数据,将上面每单行Object[]数据存储到List集合List

​ 常见的形式大概有以上六种,接着就该思考该怎样封装?如果针对每一个返回值类型都去分别实现一个方法,那么可想而知,冗余代码会有很多。那么,哪部分代码是重复的?这是我们该思考的问题,很简单,我们之所以会查询到各式各样类型的返回值数据,是因为我们对结果集的处理不同,前面的步骤都是一样的。

​ 那么我们就可以考虑将ResultSet结果集处理封装成一个接口,要实现类完成接口规定的方法,从而得到不同的数据反馈。

7.2接口设计

​ 接口中方法分析:

1.参数:

定义接口,是为了以不同方式处理结果集,所以参数为RestSet结果集对象

2.返回值类型:

返回值类型多种多样,可以用泛型T表示

@FunctionalInterface

public interface ResultSetHandler {

/**

* 结果集处理方法,处理ResultSet结果集

*

* @param res 结果集对象

* @return 泛型约束,由实现类决定最终的返回数据

*/

T handle(ResultSet res);

}

7.3统一查询query

​ 上面说到查询操作除了结果集的处理不同,其他的步骤都是一样的,所以这里可以将他们封装成一个统一的方法query

/**

* 通用 query 查询方法

*

* @param sql 要执行的SQL语句

* @param rsh 结果集处理器接口实现类对象

* @param parameters SQL语句对应参数

* @param 自定义声明泛型

* @return 接口实现类约束的返回值类型结果

* @throws SQLException SQL 异常

*/

public T query(String sql, ResultSetHandler rsh, Object... parameters) throws SQLException {

//准备必要变量

Connection connection = null;

PreparedStatement statement = null;

ResultSet resultSet = null;

// 获取数据库连接对象

connection = JdbcUtils.getConnection();

// 泛型变量,具体数据类型由接口实现类决定

T t = null;

try {

//上面封装的handlePreparedStatement方法,可以完成 SQL 语句预处理和参数赋值操作

statement = handlePreparedStatement(connection, sql, parameters);

// 执行SQL语句,得到结果集对象

resultSet = statement.executeQuery();

// 处理结果集对象,返回值类型由实现类决定

t = rsh.handle(resultSet);

} catch (SQLException e) {

throw e;

} finally {

JdbcUtils.close(connection, statement, resultSet);

}

return t;

}

​ 方法的参数有三个,待执行的SQL语句,遵从接口实现类对象,还有SQL语句对应参数。

t = rsh.handle(resultSet);这里的泛型由接口具体的实现类来约束类型。

7.4接口实现类

我们总共需要封装6个实现类,分别是:

BeanHandler

BeanListHandler

MapHandler

MapListHandler

ArrayHandler

ArrayListHandler

是不是感觉也很麻烦,但是你要想他的复用度很高,而且还可以进一步简化代码,只需要实现三个就行。

7.4.1MapHandler

​ 首先,我们先来进行一个MapHandler的实现,这个比较简单易懂,我们可以先来了解一下思路:

实现类

/**

* @author: renlon 2023/03/18 9:55

*/

public class MapHandler implements ResultSetHandler> {

@Override

public Map handle(ResultSet res) throws SQLException {

Map map;

//获取结果集元数据

ResultSetMetaData metaData = res.getMetaData();

//获取查询到的结果集对应字段个数

int columnCount = metaData.getColumnCount();

//根据字段个数赋值容量,节省空间

map = new HashMap<>(columnCount);

//添加数据

for (int i = 1; i <= columnCount; i++) {

map.put(metaData.getColumnName(i),res.getObject(i));

}

return map;

}

​ 这里比较重要的点是我们需要明确Map里面放的是什么,key中是数据库表的字段名称,value是对应的数据。这是比较重要的两点,那么怎么获得?

1.先获取结果集元数据res.getMetaData()

2.metaData.getColumnName(int column) 获取指定索引的字段名,注意:数据库下标一般从1开始,除了limit。

3.获取对应的值res.getObject(String columnLable)

​ 查询方法实现:

public Map queryMap(String sql, Object... parameters) throws SQLException {

//调用统一query查询方法,传入接口实现类对象

return query(sql, new MapHandler(), parameters);

}

7.4.2MapListHandler

​ 封装完MapHandler,这个就比较简单了,无非就是将多个map放入List集合中,多嵌套了一层,而且封装完MapListHandler,MapHandler的代码还可以进一步简化。他们的思路都是一样的。

public class MapListHandler implements ResultSetHandler>> {

@Override

public List> handle(ResultSet res) throws SQLException {

List> mapList = new ArrayList<>();

//获取结果集元数据对象

ResultSetMetaData metaData = res.getMetaData();

//获取字段个数

int columnCount = metaData.getColumnCount();

while (res.next()) {

//实例化 Map 双边队列,根据字段个数赋值容量,节省空间

HashMap map = new HashMap<>(columnCount);

for (int i = 1; i <= columnCount; i++) {

//添加数据

map.put(metaData.getColumnName(i), res.getObject(i));

}

//添加map到list集合

mapList.add(map);

}

return mapList.isEmpty() ? null : mapList;

}

}

​ 简化MapHandler

public class MapHandler implements ResultSetHandler> {

@Override

public Map handle(ResultSet res) throws SQLException {

//创建MapListHandler()实例处理res(结果只有一行数据)

List> mapList = new MapListHandler().handle(res);

return mapList != null ? mapList.get(0) : null;

}

}

​ 查询方法实现:

public List> queryMapList(String sql, Object... parameters) throws SQLException {

return query(sql, new MapListHandler(), parameters);

}

7.4.3BeanListHandler

​ 这是六个实现类里面最难的一个,考虑到JavaBean类型的多样性,需要用户指定类型来约束泛型,需要用到反射,接下来请看实现代码:

实现类

/**

* @author: renlon 2023/03/18 10:51

*/

public class BeanListHandler implements ResultSetHandler> {

//考虑到用户类型需要JavaBean类型的多样,需要由用户传入类型来约束泛型,需要用到反射

private final Class cls;

public BeanListHandler(Class cls) {

this.cls = cls;

}

@Override

public List handle(ResultSet res) throws SQLException {

ArrayList list = new ArrayList<>();

T t = null;

//获取结果集元数据

ResultSetMetaData metaData = res.getMetaData();

int columnCount = metaData.getColumnCount();

try {

while (res.next()) {

//通过反射获取实例对象

t = cls.getConstructor().newInstance();

for (int i = 1; i <= columnCount; i++) {

//添加值到JavaBean对象中

BeanUtils.setProperty(i, metaData.getColumnName(i), res.getObject(i));

}

list.add(t);

}

} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {

e.printStackTrace();

}

return list;

}

}

​ 思路:因为接口传入参数已经确定,那么我们该怎么获得实例对象呢?可以利用成员变量来提供反射类型变量。成员变量使用final约束,那么实例化此类必须给其赋值,这里通过有参数的构造方法来给其赋值。

​ 最终查询方法实现:

public List queryBeanList(String sql, Class cls, Object... parameters) throws SQLException {

/调用query查询方法,通过传入类对象来约束泛型

return query(sql, new BeanListHandler<>(cls), parameters);

}

7.4.4BeanHandler

​ 查询单行数据,跟上面的方法一致,这里就不过多赘述,直接上代码:

实现类

public class BeanHandler implements ResultSetHandler {

private final Class cls;

public BeanHandler(Class cls) {

this.cls = cls;

}

@Override

public T handle(ResultSet res) throws SQLException {

List list = new BeanListHandler<>(cls).handle(res);

return list != null ? list.get(0) : null;

}

}

​ 最终查询方法实现:

public T queryBean(String sql, Class cls, Object... parameters) throws SQLException {

//调用query查询方法,通过传入类对象来约束泛型

return query(sql, new BeanHandler<>(cls), parameters);

}

7.4.5ArrayListHandler

​ 这里可能会有人问:为什么要用数组来存储查询到的数据库表中数据,唯一的一点优点就是:节省空间,用其他例如Map,还要在key中存储字段名称,用数组直接存储数据即可。只要和前端规定好数据的顺序即可。

实现类

public class ArrayListHandler implements ResultSetHandler> {

@Override

public List handle(ResultSet res) throws SQLException {

//实例化List集合

List list = new ArrayList<>();

//结果集获取字段个数

int columnCount = res.getMetaData().getColumnCount();

while (res.next()) {

//每一次循环对应一行数据,数组的容量为字段个数

Object[] arr = new Object[columnCount];

for (int i = 0; i < arr.length; i++) {

arr[i] = res.getObject(i + 1);

}

list.add(arr);

}

return list.isEmpty() ? null : list;

}

}

​ 这里的思路和List>一致,而且这里更简单,只需要知道字段的个数即可,接着向数组中添加数据。

​ 最终查询方法实现:

public List queryArrayList(String sql, Object... parameters) throws SQLException {

return query(sql, new ArrayListHandler(), parameters);

}

7.4.6ArrayHandler

​ 单行数据存储到数组中,思路和ArrayListHandler,简化代码实现类如下:

实现类

public class ArrayHandler implements ResultSetHandler {

@Override

public Object[] handle(ResultSet res) throws SQLException {

//实例化ArrayListHandler类处理结果集对象,只有单行数据

List handle = new ArrayListHandler().handle(res);

return handle != null ? handle.get(0) : null;

}

}

​ 最终查询方法实现:

public Object[] queryArray(String sql, Object... parameters) throws SQLException {

return query(sql, new ArrayHandler(), parameters);

}

8.总结

​ 当遇到重复的代码时,我们可以考虑封装,简化代码操作。