JDBC学习讲义以及连接池技术简介

一、JDBC概述

1.1数据持久化

  • 持久化:把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,数据持久化意味着将内存中的数据保存到硬盘上加以固化。而持久化的实现过程大多通过各种关系数据库来完成。
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

1.2Java中的数据存储技术

在Java中,数据库存取技术可以分为如下几类:

JDBC直接访问数据库。

JDO(Java Data Object)技术。

第三方O/R工具,如Hibernate,Mybatis等

JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC

1.3JDBC介绍

  • JDBC是一个独立于特定的数据库管理系统、通用的SQL数据存取和操作的公共接口,定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使JAVA程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

 

1.4JDBC的体系结构

JDBC接口包括两个层次

  • 面向应用的API:JavaAPI,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果);
  • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

1.5JDBC程序编写步骤

二、获取数据库连接

2.1Driver接口实现类

接口介绍:

  • java.sql.Driver 接口是所有JDBC驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
  • 在程序中不需要直接去访问实现了Driver接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。

加载以及注册驱动

原始状态:

public static void connectMysql3() throws Exception {

    String url = "jdbc:mysql://127.0.0.1:3306/myemployees";
    Properties prop = new Properties();
    prop.setProperty("user", "root");
    prop.setProperty("password", "********");

    //获取驱动
    Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
    Driver driver = (Driver) clazz.newInstance();

    //注册驱动
    DriverManager.registerDriver(driver);

    //获取连接
    Connection conn = DriverManager.getConnection(url, prop);
    System.out.println(conn);
}

由于加载Driver的时候,有静态方法直接实现了驱动注册操作,所以这部分代码可以省略;

另外,为了避免硬编码,将数据与代码解耦,可以将数据放在jdbc.properties单独文件中管理,代码更新如下:

public static void connectMysql() throws Exception {

    InputStream is = ConnectionMySql.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties prop = new Properties();
    prop.load(is);
    String user = prop.getProperty("user");
    String password = prop.getProperty("password");
    String url = prop.getProperty("url");
    String driverClass = prop.getProperty("driverClass");

    //获取驱动,同时会加载注册驱动的静态代码
    Class.forName(driverClass);

    //获取连接
    Connection conn = DriverManager.getConnection(url,user,password);
    System.out.println(conn);
}

三、实现CRUD

3.1 操作访问数据库

数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

在java.sql包中有3个接口分别定义了对数据库的调用的不同方式:

  • Statement:用于执行静态SQL语句并返回它所生成结果的对象。
  • PrepatedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效的执行该语句。
  • CallableStatement:用于执行SQL存储过程;

3.2PreparedStatement实现数据库的增删改查

小问题:为什么不使用Statement操作数据表:

  • sql语句需要拼接字符串,量大繁琐,容易出错。
  • 会存在sql注入问题

首先准备一个简单的学生表如图:

 1.最基本的插入数据

public class PrepareStatementTest {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.读取文件信息
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String user = prop.getProperty("user");
            String password = prop.getProperty("password");
            String url = prop.getProperty("url");
            String driverClass = prop.getProperty("driverClass");

            //2.获取驱动,同时会加载注册驱动的静态代码
            Class.forName(driverClass);

            //3.获取连接
            conn = DriverManager.getConnection(url, user, password);

            //4.预编译sql语句,返回PreparedStatement的实例
            String sql = "INSERT INTO student(name, age, hobby) VALUES(?,?,?);";
            ps = conn.prepareStatement(sql);

            //5.填充占位符
            ps.setString(1, "KOBE");
            ps.setInt(2, 24);
            ps.setString(3, "basketball");

            //6.执行操作
            ps.execute();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.资源关闭
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2.通用的增删改方法

封装工具类

public class JDBCUtils {
    /**
     * 获取连接
     *
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //1.读取文件信息
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties prop = new Properties();
        prop.load(is);
        String user = prop.getProperty("user");
        String password = prop.getProperty("password");
        String url = prop.getProperty("url");
        String driverClass = prop.getProperty("driverClass");

        //2.获取驱动,同时会加载注册驱动的静态代码
        Class.forName(driverClass);

        //3.获取连接
        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 关闭资源
     *
     * @param connection
     * @param statement
     */
    public static void closeResource(Connection connection, Statement statement) {
        try {
            if (statement != null) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void updateTable(String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            //获取连接
            conn = getConnection();
            //prepareStatement预加载sql
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            closeResource(conn, ps);
        }
    }
}

主方法类

public class DeleteTest {
    public static void main(String[] args) {
        String sql ="INSERT INTO student(name, age, hobby) VALUES(?,?,?)";
        JDBCUtils.updateTable(sql,"LOONG",18,"SLEEP");
    }
}

3.最基本的查询数据

封装的关闭资源方法在这里用到,需要新增一个参数,把ResultSet资源也关闭。

因为查询不知道具体返回的列数,查询要用到元数据,还要用到反射,将实例属性得到后才能完成赋值操作。

public class QueryTest {
    public static void main(String[] args) {
        String sql = "SELECT * FROM student WHERE id =?";
        Student student = queryTable(sql,5);
        System.out.println(student);
    }

    public static Student queryTable(String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取连接
            conn = JDBCUtils.getConnection();
            //prepareStatement预加载sql
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }

            //执行,并返回结果集
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取结果集列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                Student student = new Student();
                for (int i = 0; i < columnCount; i++) {
                    //获取每列数值
                    Object columnValue = rs.getObject(i + 1);
                    //获取列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //赋值
                    Field field = Student.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(student, columnValue);
                }
                return student;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResource(conn, ps, rs);
        }
        return null;
    }
}

4.通用的查询方法

理念:

为了不让方法只能一个类使用,我们引入了泛型的概念,传参的时候指定类,就做到类通用了;

为了返回结果不再限制为一条,我们将结果集用ArrayList装,将一次赋值变成循环赋值。

public static <T> List<T> queryTable(Class<T> tClass, String sql, Object... args) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        //获取连接
        conn = JDBCUtils.getConnection();
        //prepareStatement预加载sql
        ps = conn.prepareStatement(sql);
        //填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }

        //执行,并返回结果集
        rs = ps.executeQuery();
        //获取结果集的元数据
        ResultSetMetaData rsmd = rs.getMetaData();
        //获取结果集列数
        int columnCount = rsmd.getColumnCount();
        //new结果集
        ArrayList<T> list = new ArrayList<>();
        while (rs.next()) {
            T t = tClass.newInstance();
            for (int i = 0; i < columnCount; i++) {
                //获取每列数值
                Object columnValue = rs.getObject(i + 1);
                //获取列名/别名
                String columnLabel = rsmd.getColumnLabel(i + 1);
                //赋值
                Field field = tClass.getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(t, columnValue);
            }
            list.add(t);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //关闭资源
        JDBCUtils.closeResource(conn, ps, rs);
    }
    return null;
}

具体应用到编写业务需要考虑事务:

将connect的默认自动提交给取消掉(connect.setAutoCommit(false)),

过程中出现异常,捕获异常中执行connect.rollback方法。

直到最后再connect.submit

四、数据库连接池

引入原因

由于我们开发过程中使用数据库,原理是通过主程序获取数据连接->进行sql操作->断开连接。

这一套操作存在一些问题:

  • 普通的JDBC操作数据库通过DriverManager获取连接,每次都要将Connection加载到内存,验证用户名密码还要一些时间,操作完成再断开连接,这种方式会消耗大量资源和时间,使得数据库的连接没有得到很好的重复利用,若是人数特别多的情况下,频繁操作数据库,服务器可能崩溃。
  • 每次操作完都要断开连接,若是出现异常,数据库连接未能关闭,会导致数据库系统中内存泄漏。
  • 而且开发无法控制创建的连接对象数量,系统资源无限制的被分配出去,也会有内存泄漏隐患。

数据库连接池技术思想

  • 基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是新建立一个。
  • 数据库连接池在初始化时创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来决定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池的优点

1.资源重用

由于数据库连接池得以重用,避免了频繁创建、释放引起得大量性能开销。另一方面也增加了系统运行环境的平稳性。

2.更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。

3.新的资源分配手段

对于多应用共享同一数据库而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源。

4.统一的连接管理,避免数据库连接泄漏

较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄漏。

简单案例:

maven项目添加个pom

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.5</version>
</dependency>

获取连接的方法改一下

public class JDBCUtils {

    private static DataSource source;

    static {
        try {
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
            Properties prop = new Properties();
            prop.load(is);
            source = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取连接
     *
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        return source.getConnection();
    }
    }

配置文件druid.properties

参数有很多,可以根据需求自定义配置一些

url=jdbc:mysql://localhost:3306/test
username=root
password=123456789
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=8
......
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿星_Alex

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

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

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

打赏作者

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

抵扣说明:

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

余额充值