【JavaWeb技术】·一篇搞定JDBC

🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀系统学javaWeb开发_十二月的猫的博客-CSDN博客

💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 

目录

1. 前言

1.1 数据的存储 

1.2 数据的操作

2. JDBC 

2.1 JDBC是什么

2.2 JDBC的本质

2.3 JDBC的核心

3. JDBC快速入门

3.1 环境搭建 

3.2 编写代码(架构)

3.2.1 创建数据库

3.2.2 为表建立实体类

3.2.3 构建API核心代码

4. 总结


1. 前言

进行JavaWeb的学习不可避免地需要和数据库打交道,而一个java程序想要和数据库连接就需要——JDBC技术

1.1 数据的存储 

我们在开发Java程序时,数据都是存储在内存中,属于临时存储,当程序停止或重启时,内存中的数据就丢失了!我们为了解决数据的长期存储问题,有如下解决方案:

  1. 数据通过I/O流技术,存储在本地磁盘中,解决了持久化问题,但是没有结构和逻辑,不方便管理和维护。
  2. 通过关系型数据库,将数据按照特定的格式交由数据库管理系统维护。关系型数据库是通过库和表分隔不同的数据,表中数据存储的方式是行和列,区分相同格式不同值的数据。

因此,数据存储一定需要依赖数据库以及数据库管理系统 

1.2 数据的操作

数据存储在数据库,仅仅解决了我们数据存储的问题,但当我们程序运行时,需要读取数据,以及对数据做增删改的操作,那么我们如何通过Java程序对数据库中的数据做增删改查呢?

答案是:通过Java程序控制数据库管理系统,从而完成对数据库中数据的操作

2. JDBC 

2.1 JDBC是什么

  • JDBC:Java Database Connectivity,意为Java数据库连接。

  • JDBC是Java提供的一组独立于任何数据库管理系统API

  • Java提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成jar文件,也就是我们俗称的数据库驱动jar包。

  • 学习JDBC,充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。

2.2 JDBC的本质

  • 一组规范的接口
  • 面向接口编程的体现
  • 通过JDBC+驱动控制不同的数据库管理系统,从而控制数据库

2.3 JDBC的核心

  • 接口:

    • 为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

    • 接口存储在java.sql和javax.sql包下。

  • 实现:

    • 因为各个数据库厂商的DBMS软件各有不同,那么各自的内部如何通过SQL实现增、删、改、查等操作管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

    • 厂商将实现内容和过程封装成jar文件,我们程序员只需要将jar文件引入到项目中集成即可,就可以开发调用实现过程操作数据库了。

本质就是:接口规范化+实现自由化

3. JDBC快速入门

3.1 环境搭建 

  1. 准备数据库。(对于MySQL数据库安装不了解的友友可以看我的这篇文章:【一篇搞定】MySQL安装与配置_5.2 配置mysql-CSDN博客 

  2. 官网下载数据库连接驱动jar包。https://downloads.mysql.com/archives/c-j/

  3. 创建Java项目,在项目下创建lib文件夹,将下载的驱动jar包复制到文件夹里。  

  4. 选中lib文件夹右键->Add as Library,与项目集成。  

  5. 编写代码

3.2 编写代码(架构)

3.2.1 创建数据库

create table t_fruit
(
    f_id     int auto_increment comment '水果编号' primary key,
    f_name   varchar(100)       not null comment '水果名字',
    price    double(6,2)        not null comment '水果价格',
    f_count  int                not null comment '水果库存',
    remark   VARCHAR(100)       comment '水果备注'
);

insert into t_fruit (f_name,price,f_count,remark)
values ('apple',100.00,500,'China'),
			 ('banana',56.00,600,'China'),
			 ('orange',121.00,300,'China'),
			 ('watermelon',23.00,900,'China')

3.2.2 为表建立实体类

  • 在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类!

  • ORM(Object Relational Mapping)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!

  • 当下JDBC中这种过程我们称其为手动ORM。后续我们也会学习ORM框架,比如MyBatis、JPA等。

1、首先在Idea中建立如下文件夹:

fruit:对应数据库中的表的名字叫做fruit

dao:dao层,建立在fruit下面,表示对fruit表格的增删改查操作

pojo:pojo层,建立在fruit下面,表示fruit表格在java程序中的实体对象

2、在pojo文件夹在建立fruit对象:

package com.javaWebEx.fruit.pojo;

public class fruit {
    //数据库中列名用下划线,这里用驼峰
    private Integer fId;//f_id
    private String fName;//f_name
    private Double price;//price
    private Integer fCount;//f_count
    private String remark;//remark

    public fruit(Integer fId, String fName, Double price, Integer fCount, String remark) {
        this.fId = fId;
        this.fName = fName;
        this.price = price;
        this.fCount = fCount;
        this.remark = remark;
    }
    public fruit() {}

    public Integer getfId() {
        return fId;
    }

    public void setfId(Integer fId) {
        this.fId = fId;
    }

    public String getfName() {
        return fName;
    }

    public void setfName(String fName) {
        this.fName = fName;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getfCount() {
        return fCount;
    }

    public void setfCount(Integer fCount) {
        this.fCount = fCount;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "Fruit{" +
                "fId=" + fId +
                ", fName='" + fName + '\'' +
                ", price=" + price +
                ", fCount=" + fCount +
                ", remark=" + remark +
                '}';
    }
}

关键点:

1、这里的构造函数、set、get函数都可以利用idea自动生成

2、toString方法:当print(fruit)执行时会自动调用toString函数

3.2.3 构建API核心代码

java程序实现和数据库管理系统交互代码的核心步骤有六步,如下:

  1. 注册驱动【依赖的驱动类,进行安装】
  2. 获取连接【Connection建立连接】
  3. 创建发送SQL语句对象【Connection创建发送SQL语句的Statement】
  4. 发送SQL语句,并获取返回结果【Statement 发送sql语句到数据库并且取得返回结果】
  5. 结果集解析【结果集解析,将查询结果解析出来】
  6. 资源关闭【释放ResultSet、Statement 、Connection】

每次与DBMS打交道时我们都要遵守这六步操作 ,因此:

  • 将所有对数据库表的操作都封装到DAO层中
  • DAO层包括接口实现
  • 接口中包括对表的:增删改查各个操作
  • 每个增删改查操作的实现都要包括上面的六步操作
  • 增删改查可能包括:selectAll、selectById、insert、delete等操作。在这些操作中只要是select类型(查询类)的则有很大部分是相同的代码;同样,如果是insert、delete(更新类)也有很大部分相同代码
  • 因此可以将查询类抽象出一个query函数(接口),将更新类抽象出一个update函数(接口)。并且将这两个函数放在baseDAO中,作为上面增删改查操作的基础

传统方法:一步步撰写代码(这里就不写六步的传统代码了)

我们现在来看传统六步法中有没有可以优化的地方

  1. 现在的Idea也可以自动注册驱动——》不写注册驱动
  2. 获取连接存在:连接数量无法把控问题——》连接池处理
  3. Statement存在SQL注入问题——》preparedStatement代替Statement
  4. 每一个JDBC在1、2、6的操作都是相同的——》封装为工具类JDBCUtil
  5. 多线程来访问数据库时存在事务前后不一致问题——》ThreadLocal技术对连接限制

进一步分析这五个优化在代码中的关系:

  1. 五步优化中1、2、5体现在第四步的封装工具类JDBCUtil中
  2. 五步优化中3体现在BaseDAO中
  3. baseDAO要完成DAO操作,需要借助JDBCUtil的1、2、6步操作。自己完成3、4、5步

因此,代码编写顺序就很好确定了:

  1. 编写db.properties为druid连接池做配置
  2. 封装工具类JDBCUtil中
  3. BaseDAO
  4. FruitDAO
  5. FruitDAOImpl

一、编写db.properties为druid连接池做配置

  1. 建立resources文件夹
  2. 在resources中建立db.properties
  3. 将resources文件夹标记为资源根目录

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///atguigu
username=root
password=1557304
initialSize=10
maxActive=20

1、完整url设定如下:

//url:连接的数据库对象:jdbc:mysql://本机(阿里服务器ip号):端口号/数据库名字
String url="jdbc:mysql://localhost:3306/atguigu";

这里的username、password是所连接的数据库的账号密码(我这里连接的数据库名字是atguigu)

2、

initialSize:最小连接池中的连接数量 
maxActive:最大连接池中的连接数量

二、封装工具类JDBCUtil中

package com.javaWebEx.util;
/*
 * JDBC工具类
 *   1、维护一个连接池对象,维护一个线程绑定变量的threadlocal对象
 *   2、对外提供在TreadLocal中获取连接的方法
 *   3、对外提供回收连接的方法,将要回收的连接从threadlocal中移除
 * */

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtil {
    private static DataSource dataSource;
    private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();//connection设定为TheadLocal使得线程中connection是唯一的
    static {
        Properties properties=new Properties();//配置对象
        InputStream inputStream=JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");//加载db.properties到类中(以数据流形式)
        try {
            properties.load(inputStream);//设置配置对象
            dataSource= DruidDataSourceFactory.createDataSource(properties);//利用配置对象创建连接池
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static Connection getConnection(){
        try {
            //从threadlocal中获取连接
            Connection connection=threadLocal.get();//从TheadLocal中获取连接
            //threadlocal中没有连接(第一次获取),则从连接池中加载连接到TheadLocal中
            if(connection==null){
                connection= dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public static void release(){
        try {
            //归还连接对象是以线程为单位的(因为一个线程只有一个连接对象)
            Connection connection=threadLocal.get();
            //线程中
            if(connection!=null){
                //从threadLocal中移除当前已经存储的Connection对象
                threadLocal.remove();
                //归还connection后要修改回自动提交
                connection.setAutoCommit(true);
                //将Connection对象归还连接池
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

这里的工具类不同于传统的工具类,用了两个技术:

  • TheadLocal技术:保证每个线程连接的唯一性,使得事务原子性的实现存在可能
  • Druid连接池:能够有效限制连接资源

三、BaseDAO

package com.javaWebEx.fruit.dao;

import com.javaWebEx.util.JDBCUtil;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class BaseDAO {
    /**
     * 通用的增删改方法
     * @param sql 请求者要执行的SQL语句
     * @param params SQL语句中的占位符要赋值的参数
     * @return 受影响的行数
     */
    public int executeUpdate(String sql,Object...params) throws SQLException {
        //1、获取驱动+获取连接
        Connection connection= JDBCUtil.getConnection();
        //2、预编译SQL语句
        PreparedStatement preparedStatement=connection.prepareStatement(sql);
        //3、确定SQL语句并处理
        if(params!=null&&params.length>0){
            for(int i=0;i<params.length;i++){
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        //4、处理结果
        int row=preparedStatement.executeUpdate();
        //5、释放资源
        preparedStatement.close();
        //如果开启了自动提交,才自动释放连接;否则,手动提交情况下不释放连接
        if(connection.getAutoCommit()){
            JDBCUtil.release();
        }
        return row;
    }

    /**
     * 通用的查询方法
     * @param clazz 查询结果返回的对象
     * @param sql 查询的预编译sql语句
     * @param params 确定sql语句的占位符
     * @return 单行单列、单行多列、多行多列都有可能
     * @param <T> 与Class<T>合起来共同表示一个通用类
     * @throws SQLException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     */
    /*
    通用查询结果:单行单列、单行多列、多行多列
        单行单列:一个int结果
        单行多列:一个对象(例如employee)
        多行多列:list(object)
    封装过程
        1、返回的类型:泛型。类型不确定,使用时将结果类型提供给baseDAO
        2、返回的结果:通用list。可以存储多个结果,也可以存储单个结果
        3、结果的封装:反射!!要求调用者告诉baseDAO要封装对象的类对象
     */
    //Class<T>:类的类型
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object...params) throws Exception{
        //1:获取连接
        Connection connection=JDBCUtil.getConnection();
        //2、获取预编译sql
        PreparedStatement preparedStatement=connection.prepareStatement(sql);
        //3、确定sql
        if(params!=null && params.length>0){
            for(int i=0;i<params.length;i++){
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        //4、获取并处理结果(只有得到对象的属性,才能对对象的属性赋值)
        ResultSet resultSet=preparedStatement.executeQuery();
            //处理结果:获取数据库表的元数据(元数据中有:列数、列名)
        ResultSetMetaData resultSetMetaData=resultSet.getMetaData();
            //处理结果:根据数据库表的元数据得到数据库表的列的数量
        int columCount=resultSetMetaData.getColumnCount();
        List<T> list=new ArrayList<>();
            //处理结果(while循环一次处理完一个对象,也就是一条记录)
        while(resultSet.next()){
            //循环一次,代表有一个记录,创建一个对象
            T t=clazz.newInstance();
            for(int i=1;i<=columCount;i++){
                //通过getObject操作获得列的值
                Object value = resultSet.getObject(i);
                //获得到的列的value值,就是t对象中的一个属性值
                //获取t对象中的一个属性名
                String fieldName=resultSetMetaData.getColumnLabel(i);
                //通过对象的属性名来得到对象的属性
                Field field=clazz.getDeclaredField(fieldName);
                //为属性突破封装的private
                field.setAccessible(true);
                //为属性赋值
                field.set(t,value);
            }
            list.add(t);
        }
        //5、释放资源
        resultSet.close();
        preparedStatement.close();
        if(connection.getAutoCommit()){
            JDBCUtil.release();
        }
        return list;
    }

    /**
     * 通用的查询方法————简化仅用于单行数据的查询
     * @param clazz 查询结果返回的对象
     * @param sql 查询的预编译sql语句
     * @param params 确定sql语句的占位符
     * @return
     * @param <T>
     * @throws Exception
     */
    public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
        List<T> list=this.executeQuery(clazz,sql,params);
        if(list==null || list.size()==0)
            return null;
        return list.get(0);
    }
}

基本已经对每一行都做了注释,大家一定要仔细去琢磨!!!

最后一个方法仅仅是为了优化单行数据查询,不要也可以

四、FruitDAO

package com.javaWebEx.fruit.dao;

import com.javaWebEx.fruit.pojo.Fruit;

import java.sql.SQLException;
import java.util.List;

public interface FruitDAO {
    /**
     * 查询数据库中水果的所有数据
     * @return 表中所有的数据
     */
    List<Fruit> selectAll() throws Exception;
    /**
     * 根据水果id查询一个水果对象
     * @param fId
     * @return 一个水果对象
     */
    Fruit selectByFId(Integer fId) throws Exception;
    /**
     * 新增数据库对应的新增一条水果数据
     * @param fruit
     * @return 受影响行数
     */
    int insert(Fruit fruit) throws SQLException;

    /**
     * 数据库对应的修改一条水果数据
     * @param fruit 数据库中想要修改的水果对象
     * @return 受影响的行数
     */
    int update(Fruit fruit) throws SQLException;

    /**
     * 数据库对应的删除一条员工数据
     * @param fruit 数据库中想要删除的水果对象
     * @return 受影响的行数
     */
    int delete(Fruit fruit) throws SQLException;
}

FruitDAO:一个接口,里面定义了对fruit数据库表的操作。仅仅定义,并不实现

五、FruitDAOImpl

FruitDAOImpl:对FruitDAO接口的实现,实现了接口中的方法。使用baseDAO实现

package com.javaWebEx.fruit.dao;

import com.javaWebEx.fruit.pojo.Fruit;

import java.sql.SQLException;
import java.util.List;

public class FruitDaoImpl extends BaseDAO implements FruitDAO{

    @Override
    public List<Fruit> selectAll() throws Exception {
        String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit";
        return executeQuery(Fruit.class,sql,null);
    }

    @Override
    public Fruit selectByFId(Integer fId) throws Exception {
        String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit where f_id=?";
        return executeQueryBean(Fruit.class,sql,1);
    }

    @Override
    public int insert(Fruit fruit) throws SQLException {
        String sql="insert into t_fruit(f_name,price,f_count,remark) values(?,?,?,?)";
        return executeUpdate(sql,fruit.getfName(),fruit.getPrice(),fruit.getfCount(),fruit.getRemark());
    }

    @Override
    public int update(Fruit fruit) throws SQLException {
        String sql="UPDATE t_fruit set price=?,f_count=? ,remark=? WHERE f_name=?";
        return executeUpdate(sql,fruit.getPrice(),fruit.getfCount(),fruit.getRemark(),fruit.getfName());
    }

    @Override
    public int delete(Fruit fruit) throws SQLException {
        String sql="delete from t_fruit where f_id=?";
        return executeUpdate(sql,fruit.getfId());
    }
}

六、构建API核心代码总结

JDBC代码实现中最复杂的部分是baseDAO的实现、JDBCUtil的实现

baseDAO实现:难点在于如何将DAO的实现抽象化,写出通用的函数

JDBCUtil的实现:难点在于如何使用TheadLocal技术、连接池技术

七、前端测试使用代码 

add.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>fruit</title>
</head>
<body>
<form action="add" method="post">
  名称:<input type="text" name="fname"/><br/>
  价格:<input type="text" name="price"/><br/>
  库存:<input type="text" name="fcount"/><br/>
  备注:<input type="text" name="remark"/><br/>
  <input type="submit" value="添加">
</form>
</body>
</html>

4. 总结

整体流程小总结:

  1. 向服务器请求add.html
  2. 服务器返回add.html的代码
  3. 浏览器解析html代码,并在前端显示出来
  4. 填写完内容,点击添加
  5. 由于action=‘add’,因此到web.xml中匹配到add对应的servlet——AddServlet
  6. 到AddServlet中找到doPost方法去执行,获取前端数据处理到后端数据库

如果觉得对你有帮助,辛苦友友点个赞,收个藏呀~~~

想要完整源码的可以私信我呀~~后续我也会上传到CSDN上的

猫猫未来一定会产出更多优质文章的!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十二月的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值