你真的会JDBC吗?

市面上很多人建议都是简单的学习jdbc,因为后面都会使用mybatis相关框架进行封装。虽然确实在实际开发中很少小关于jdbc的代码。但是对于一个代码热爱者来说,我们还是有必要进一步学习jdbc,首先在学习mybatis、spring等框架源码时,我们必不可少地会接触到jdbc的源码;再者,我们可以在jdbc中学习到很多知识和思维:工厂模式、装饰者模式、反射、集合框架等。

首先我们来看看本文章的思维导图:

JDBC
JDBC概述
JDBC 环境依赖相关以及快速案例
依赖引入
数据库搭建
JDBC里面各种API详解
Driver
DriverManger
Connection
PreparedStatment and Statement
ResultSet
ResultSetMetaData
JDBC 自定义封装工具类及方法
getConnction
close
update
select
JDBC事务相关
JDBC连接池
DBUtils工具类介绍及源码分析

一.JDBC概述

  • JDBC是java中的一个接口,各种数据库如mysql、sqlserver等数据都需要实现这个接口,才能够使得java能够使用相关产品的数据库,如果哪一个数据库不提供相关的JDBC实现类,就会被“踢出”java生态。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
  • 目前我们使用的框架如mybatis其底层就是使用的JDBC写的,所以我们现在学习JDBC也是为了以后学习mybatis源码打好基础。(当然像反射中的动态代理、设计模式等同样很重要)

能看到本文章的可能都会对JDBC有一定的了解,接下来我们直接进入正题吧!

二.JDBC 环境依赖相关以及快速案例

1.依赖引入:

这里我们使用maven来管理项目:

<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.25</version>
</dependency>

2.数据库环境介绍:

这里我们使用mysql数据库,并建立一张表供我们后续操作持续使用:
我们建立一个学生表
在这里插入图片描述

下面是建立表和插入的测试数据,

/*Table structure for table `student` */

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(225) NOT NULL,
  `age` int DEFAULT NULL,
  `sex` varchar(225) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Data for the table `student` */

insert  into `student`(`id`,`username`,`age`,`sex`) values (1,'以某',22,'男'),(2,'小二',19,'男'),(3,'张三',12,'男'),(4,'李四',12,'男'),(5,'王五',22,'男');

3.快速使用JDBC

 @Test
    public  void demo1() throws SQLException {
        Driver driver = new Driver();    //获取Driver驱动

        //填写数据源相关信息
        String url="jdbc:mysql://localhost:3306/zjdata";
        String user="root";
        String password="12345678";
        DriverManager.registerDriver(driver);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        Statement statement = connection.createStatement();
        String sql="delete from student where id="+5;
        statement.execute("delete from student where id=5");

        //关闭连接
        connection.close();

    }

以上通过JDBC实现了数据库中id为5的数据的删除,当然这只是初级的使用,后面还有更高级的用法。

三.JDBC相关API详解

1.Driver类

一个是java.sql包下的Driver抽象类,里面给出了Driver的各种规定方法,各种数据库厂商需要根据此抽象类编写相关的数据库驱动,我们程序员不需要面向该接口进行编程。
另一个则是相关的数据库厂商编写出来的Driver实现类,就拿mysql的驱动包举例:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

这个类就是mysql方法给我们提供的驱动类,平时我们在写jdbc时,就必须把这个类加载到内存中并注册。在我的快速案例中写的代码是:

 Driver driver = new Driver();
 DriverManager.registerDriver(driver);

首先这个 DriverManager.registerDriver(driver); 我们可以省略不写,同样可以成功,因为在Driver中有static代码块:

static {
        try {
        //static代码块中实现驱动的注册,即当Driver类加载到内存的过程中自动实现了驱动的注册
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

该静态代码块在加载时就会为我们执行Driver的注册操作。

另外对于new Driver() 其实写其的主要目的是将Driver这个驱动类加载到内存当中,我们在学习反射时,知道常用的三种方式将类加载到内存中:

Driver driver = new Driver();    //获取Driver驱动

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

ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");

上面这三种加载方式中,我们最常用的就是 Class.forName(“com.mysql.jdbc.Driver”); 以后我们加载驱动时就用该代码。

2.DriverManager类

该类主要用于管理驱动的类,我们通过该类调用getConnection()方法来获得相关的数据库连接。当然在我们注册驱动(加载Driver时自动注册)之后、获取连接之前需要获取连接mysql的相关信息,这里的getConnection参数就提示了我们要填写的信息:

@CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }

这里需要我们填写的分别是:

  • url:
    填写要连接的数据库地址 jdbc:mysql://localhost:3306/zjdata
    其中jdbc:mysql代表协议字段,localhost:3306代表自己数据库连接地址,这里指的是本机数据库,zjdata是要连接的数据库名称
  • user:数据库登录名
  • password:数据库登录密码

我们将这些参数设置过后,就能正常获得我们的连接对象connection了!!

另外提一下:我们现在写的Driver路径、url、user、password都高度耦合在代码当中,这样其实是非常不好的,我们如果要修改就会很麻烦,所以建议通过properties文件来写这些需要手动输入的东西。
在resource目录下建立jdbc.xml:

url=jdbc:mysql://localhost:3306/zjdata
driverClassName=com.mysql.jdbc.Driver
username=root
password=12345678

在java代码中,我们通过properties类来读取properties文件来加载相关信息:

InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);
		//从properties文件中获取信息
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        String driverClassName = properties.getProperty("driverClassName");
        String url = properties.getProperty("url");
        
        Class.forName(driverClassName);
        Connection connection = DriverManager.getConnection(url, username, password);

以上通过文件的方式减少了代码耦合性。

3.Connection

该连接对象其实表示的是mysql客户端与mysql服务端的一个连接,通常只有我们只有我们获取了对象后才能真正的使用sql相关语句和事务相关操作。获取该对象主要通过DriverManager获取或者通过数据库连接池对象(后面介绍)来获取

4.statement与PreparedStatement的区别

当我们获取了连接对象后,我们就可以通过connection调用方法来获取statement对象:

  • statement:不支持预编译,只能通过拼串的方式来解决占位问题,例如:
public void insert delete(String id,Statement statement){
	String sql="delete from studen where id="+id;
	statement.execute(sql);
	
}

其中进行了拼串操作,也会有一定的开销。
另外statement也会出现sql注入问题:

public void insert delete(String id,Statement statement){
	String sql="select id,name,age,sex from studen where id="+id;
	statement.execute(sql);
	//本来查询出来的应该是一条数据。但是如果数据库攻击者将id的值设为“1 or id>-100”,
	//这样实际语句就是select id,name,age,sex from studen where id=1 or id>-100,查询出来的就不是一条语句,而是所有语句。其原因就是mysql误将id中的语句or等看成了关键字,从而引起了sql注入问题。
}

所以在我们平时的开发中,不会使用statement二而是使用preparedStatement,其采用了预编译的机制,可以有效解决预编译的问题:

//通过连接对象connection获取预编译对象preparedStatement对象:
PreparedStatement ps=connection.prepareStatement("delete from student where id=?");
//sql字符串语句中,占位通过?进行占位操作
ps.setObject(1,3);  //设置sql语句中?的值,最小从1开始,这里我们删除id为3的学生。

//执行:
boolean flag= ps.excute();   

上面我们看到了PreparedStatement的两个方法:

  • setObject(int parameterIndex, Object x) 用于设置占位符的值
  • excute() 执行预编译的语句,返回true则表示使用select返回了结果集,返回false则表示返回了影响行数。

接下来我们详细介绍更改数据库和查询数据库的具体操作以及相关Api:

3.1 更改操作

更改操作包括增删改:
我们调用预编译对象的excuteupdate()方法来返回影响行数。

int i=ps.excuteupde()  //返回更新的影响的行数。

3.2 查询语句

其实更改操作都是比较简单的,下面介绍一下相对较为复杂的查询语句:
调用预编译对象的excuteQuery() 方法,返回的结果集 ResultSet:

ResultSet resultSet = preparedStatement.executeQuery();

这个结果集对象可能包含了多行数据,我们怎么在java中通过ResultSet获取所有的查询信息呢?
首先我们要创建一个实体类对象来接受我们要查询的数据:

//		创建一个列表用于存储查询到的实体集
 		List<Student> list=new ArrayList<Student>();
//      创建一个元数据对象,该元数据对象可以获取查询出来结果的列的数量,列的名称,列的别名等,列的数据类型等
        ResultSetMetaData metaData = resultSet.getMetaData();
//     获取有多少列
        int len=metaData.getColumnCount();
        while(resultSet.next()){
            Student student = new Student();
            for (int i = 0; i < len; i++) {
                Object object = resultSet.getObject(i + 1);
                //获取列别名,没有别名就列原名
                String columnLabel = metaData.getColumnLabel(i + 1);
                
//                使用反射
                Field declaredField = student.getClass().getDeclaredField(columnLabel);
                declaredField.setAccessible(true);
                declaredField.set(student,object);
                list.add(student);
            }
        }

上面代码的思路就是首先由一个实体类Student来接受数据(注意该实体类必须对象表的各个字段类型和名称要相等),之后通过结果集获得元数据对象metadata,该对象主要是封装了返回数据的各个字段信息,包括列名、列数量等,我们获取了列名后,就可以结果集获取相应的列的数据,然后通过反射机制,强制修改student对象中的属性(即赋值),这样循环下去,我们就可以获取多个对象对应的多个数据!

----------------------------分割线-------------------------------------------------------------
2021/12/16 剩下文章后续继续更新,后面我们将自己建立一个工具类来封装之前的代码,增加可用性

2021/12/18 今天下午考完了六级,虽不是很理想,但是博客更新还是要继续的。😂
----------------------------分割线-------------------------------------------------------------

5.ResultSet

ResultSet类我们前面也看见了,它是执行select语句返回出来的一个结果集,这个结果集可能只有一个数据、也可能有多个数据。ResultSet.next()这个next()方法会判断下一行是否有数据,如果有数据则返回true并将光标移向下一行。(注意:ResultSet光标最开始在第一行的前面)
在这里插入图片描述

既然ResultSet当前行有数据,那怎么把当前行的数据获取出来呢?
方法如下:

//通过当前行的列标签获取对应数据
Object getObject(String columnLabel);

//通过当前行的列索引获取数据(从0开始)
Object getObject(int columnIndex) ;

虽然这种方式可以获取数据,但是必须要我们事先知道查询结果的列别名或列的数量。所以又出现问题,如何获取结果集的当前行的列名和列的数量

6.ResultSetMetaData

该对象可以获取结果数据中行的各种结构信息(包括列名和列的数量):
获取ResultSetMetaData的方法:

//获取元数据对象
ResultSetMetaData metaData = resultSet.getMetaData();

这下我们获取了这个元数据对象,接下来我们继续讲解如何获取对应行的列名和列数据:

//获取列的数量
 int len=metaData.getColumnCount();

//通过列的下标获取相应列的列别名(没有别名就获取原始名称)
String columnLabel = metaData.getColumnLabel(i + 1);

这样我们就可以通过循环、配合反射的方式获取每列的列名字和列属性了,具体操作如下:

//循环检测resultset结果集中是否还有数据
 while(resultSet.next()){
 			//创建一个空的实例对象,后面通过反射对其注入值
            Student student = new Student();

			//该行有数据,就遍历(列的数量len)次,通过下标获取数据和列名
            for (int i = 0; i < len; i++) {
            	//获取值
                Object object = resultSet.getObject(i + 1);
                //获取列别名,没有别名就列原名
                String columnLabel = metaData.getColumnLabel(i + 1);
                
//                使用反射:将查询出来的列名注入其对应的值(这里也就间接的要求了数据库的名字必须和java中创建的实体类属性名字相同)
                Field declaredField = student.getClass().getDeclaredField(columnLabel);
                declaredField.setAccessible(true);
                declaredField.set(student,object);
//将每次查到的数据加到列表中
                list.add(student);
            }
        }

上面讲到了反射,如果对反射还不了解的,可以看看我写的反射博客

四.JDBC 自定义封装工具类及方法

其实很多框架如mybatis持久化框架就已经对jdbc进行了封装。这里我们不将mybatis,为了简化我们的代码,增加开发效率,这里我们写几个平常常用的工具类:
大家可以直接复制粘贴到idea中进行使用,不过要记得修改一些配置文件,以下讲解过程都会讲到

- JDBCUtils

  • 关于获取连接和关闭连接的功能封装
    首先是配置文件jdbc.properties
classDriverName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/zjdata
username=root
password=12345678

使用mysql时上面的除了classDriverName其他都需要根据自己的数据库进行修改。

下面创建JDBCUtils.java


//获取连接对象,前提要创建jdbc.properties文件并正确配置
public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException {
        InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("JDBC.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);

        Class.forName(properties.getProperty("classDriverName"));
        Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password"));
        return connection;

    }

//关闭连接需要传入连接对象和Statement对象
public static void close(Connection connection, Statement statement) throws SQLException {
        if(connection!=null) connection.close();
        if(statement!=null) statement.close();
    }

-JDBCMethod

这里提供jdbc中sql语句的封装。下面的实现首先要完成JDBCUtils的导入。

 //更改通用操作,为了配合后面的事务操作和连接池操作,这里方法的参数需要传入connection连接对象。sql表示要输入的sql语句,args表示sql语句中?的占位
    public static int update(Connection connection,String sql,Object...args) throws SQLException {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        int len=args.length;
        for (int i = 0; i < len; i++) {
            preparedStatement.setObject(i+1,args[i]);
        }

        int i = preparedStatement.executeUpdate();
        close(connection,preparedStatement);
        return i;

    }

    //通用查询操作BookUser
    //如果要查询自己的对象,就把下面的BookUser类改为自己的类即可。
    public static List<BookUser> select(Connection connection,String sql,Object ...args) throws SQLException, NoSuchFieldException, IllegalAccessException {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        int len=args.length;
        for (int i = 0; i < len; i++) {
            preparedStatement.setObject(i+1,args[i]);
        }

        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        ArrayList<BookUser> list= new ArrayList<BookUser>();
        while(resultSet.next()){
            BookUser bookUser = new BookUser();
            for (int i = 0; i < columnCount; i++) {
                String columnLabel = metaData.getColumnLabel(i + 1);
                Object object = resultSet.getObject(i+1);

                Field field = bookUser.getClass().getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(bookUser, object);


            }
            list.add(bookUser);

        }
        return list;

    }

五.JDBC事务

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值