JDBC及衍生知识(下)

JDBC及衍生知识(下)

前言

上一篇文章我们学习了JDBC,今天,我们乘胜追击来学习数据库连接池。

 

 

 

 


数据库连接池

概述

上篇我们使用JDBC的代码中,我们每一次访问都要去获取连接,再释放资源,每一次访问都要这样,这在底层是极大的浪费资源,因为我们在不断的建立连接、释放连接,而我们数据库连接池的存在的意义就是优化这部分的性能。

概念:其实就是一个容器(集合),存放数据库连接的容器。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

数据库连接池将建立的连接保存在一个“pool”中,当我们需要建立连接,不会再去动用系统底层去申请连接,而是会拿pool中的连接对象,当我们访问结束,不会释放掉该连接对象的资源,会将该连接对象再归还给pool。

作用:数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。

优点

  1. 节约资源
  2. 用户访问高效

 

这种池技术并不局限于数据库连接,在Unity3D程序中基础框架我也见识过了,Unity缓存池技术也应用到了“池”技术,针对Unity中的物体,当销毁时并不是真正的销毁,而是setActive(false)让其失活,在需要时才会再setActive(true),对性能非常友好。

 

实现介绍

标准接口:DataSource ( javax.sql包下)

连接池一般都会实现如下方法:

  • 获取连接方法:
Connection getConnection()  
Connection getConnection(String username, String password)  
  • 归还连接:如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接释放资源了,而是归还连接。

该接口下的方法一般不是由我们来实现,是由数据库厂商来实现。

下面我们来介绍两种连接池技术:

C3P0:数据库连接池技术

步骤:

  • 导入jar包

链接:https://pan.baidu.com/s/17Y0OKGm4TTnpCslfjYHHOg
提取码:knnq

需要导入两个jar包,c3p0-0.9.5.2.jar依赖mchange-commons-java-0.2.12.jar

  • 定义配置文件
    • 名称:c3p0.properties 或者 c3p0-config.xml
    • 路径:直接将文件放在src目录下即可
  • 创建核心对象  数据库连接池对象  ComboPooledDataSource
  • 获取连接:getConnection

 

好,接下来我们来一点一点做:

我们新开一个JavaSE项目,创建一个目录lib,将成c3p0的两个jar包导入目录lib中,并添加到项目。

导入MySQL的驱动jar包。

接下来我们需要引入一个c3p0的配置文件,我们可以将上面网盘分享的目录中的c3p0-config.xml文件复制到src根目录下。

OK,基本准备工作就做好了。

配置文件c3p0-config.xml中内容(注意,我的MySQL版本大于8.0,所以在URL中需要一些特别声明一些参数):

<?xml version="1.0" encoding="utf-8"?>
<c3p0-config>
  <!--使用默认的配置读取连接池对象-->
  <default-config>
    <!--连接参数-->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/db4?useSSL=false&amp;serverTimezone=UTC</property>
    <property name="user">root</property>
    <property name="password">964939451</property>
    <!--连接池参数-->
    <!--初始化连接数-->
    <property name="initialPoolSize">5</property>
    <!--最大连接数,超过会报错-->
    <property name="maxPoolSize">10</property>
    <!--申请连接等待时间,超时会报错-->
    <property name="checkoutTimeout">5000</property>
  </default-config>

  <named-config name="otherc3p0"> 
  </named-config>
</c3p0-config>

然后我们在pers.luoluo.datasource.c3p0包下创建一个C3P0Demo1.java文件来使用连接池:

public class C3P0Demo2 {
    public static void main(String[] args) {
        Connection conn=null;
        //1.创建数据库连接池对象,获取DataSource
        //ComboPooledDataSource无参则使用默认配置,
        //有参则使用对应named-config的name的配置
        DataSource ds=new ComboPooledDataSource();
        //2.获取连接对象
        try {
            conn=ds.getConnection();
            //打印一下
            System.out.println(conn);
            
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                //归还连接
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

就如上就可以取到池中连接,非常简单吧。

 

Druid:数据库连接池实现技术

这个连接池,又名“德鲁伊”,是由阿里巴巴公司开发的数据库连接池。

步骤

  • 导入jar包

我这边也给大家提供一个分享链接(不过我这个版本有点老了)

链接:https://pan.baidu.com/s/12UV_ZtQv4pLo6Xmy7kYFTw
提取码:9g12

导入一个druid-1.0.9.jar即可。

  • 定义配置文件
    • 配置文件是properties类型的文件
    • 配置文件可以叫任意名称,可以放在任意的目录下,但是这也意味着你需要手动加载。
  • 加载配置文件:
  • 获取数据库连接池对象:通过工厂类来获取 DruidDataSourceFactory。
  • 获取连接:getConnection

下面我们来实际演示:

在src目录下创建一个配置文件,druid.properties:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db4?useSSL=false&serverTimezone=UTC
username=root
password=964939451
#初始化连接数
initialSize=5
#最大连接数
maxActive=10
#等待时间
maxWait=3000

在包pers.luoluo.druid目录下,我们创建一个DruidDemo1.java文件:

public class DruidDemo1 {
    public static void main(String[] args)  {
        Connection conn=null;
        //1.导入jar包
        //2.定义配置文件
        try {
            //3.手动加载配置文件
            Properties pro=new Properties();
            InputStream is = DruidDemo1.class
                    .getClassLoader()
                    .getResourceAsStream("druid.properties");
            pro.load(is);
            //4.获取连接池对象
            DataSource ds = DruidDataSourceFactory.createDataSource(pro);
            //5.获取连接
            conn=ds.getConnection();
            System.out.println(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
控制台打印:
七月 29, 2020 1:23:09 下午 com.alibaba.druid.pool.DruidDataSource error
严重: testWhileIdle is true, validationQuery not set
七月 29, 2020 1:23:09 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
com.mysql.cj.jdbc.ConnectionImpl@29ca901e

不用理会红色的日志信息,看来我们成功了。

关于类加载器的知识可以好好看看这篇文章

上面是Druid连接池的基本使用,但是其实在实际开发中,我们这样使用也不合适,例如每一次使用都要创建一个连接池对象,这非常的不合理,所以我们一般使用的话也是会创建一个Druid工具类的:

定义工具类:

  1. 定义一个类  JDBCUtils
  2. 提供静态代码块加载配置文件,初始化连接池对象
  3. 提供方法;
    1. 获取连接方法:通过数据库连接池获取连接
    2. 释放资源
    3. 获取连接池的方法

在包pers.luoluo.utils包下创建一个类——JDBCUtile.java。

工具类内容如下:

public class JDBCUtils {
    //1.定义成员变量 DataSource
    private static DataSource ds;

    static {
        try {
            //1.加载配置文件
            Properties pro=new Properties();
            pro.load(JdbcUtils.class
                    .getClassLoader()
                    .getResourceAsStream("druid.properties"));
            //2.获取DataSource
            ds= DruidDataSourceFactory.createDataSource(pro);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * @Description 获取连接
     **/
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    /**
     * @Description 获取连接池的方法
     **/
    public static DataSource getDateSource(){
        return ds;
    }
    /**
     * @Description 释放资源
     **/
    public static void close(Statement stmt,Connection conn){
        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void close(ResultSet rs, Statement stmt, Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        close(stmt,conn);
    }
}

这个工具类和我们之前那个工具类道理一样,只是底层不是手动建立连接或者清理资源了,而是去从连接池中申请连接或归还连接。

连接池工具类定义结束,使用起来也非常方便:

public class DruidDemo2 {
    public static void main(String[] args)  {
        /*给db3中的account表添加一条记录
        */
        Connection conn=null;
        PreparedStatement pstmt=null;
        try {
            //1.获取连接
            conn = JDBCUtils.getConnection();
            //2.定义sql
            String sql="INSERT INTO account VALUES(null,?,?)";
            //3.获取pstmt对象
            pstmt = conn.prepareStatement(sql);
            //4.给预编译的SQL语句中的占位符赋值
            pstmt.setString(1,"Alis");
            pstmt.setDouble(2,2000);
            //5.执行sql
            int count=pstmt.executeUpdate();
            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //6.释放资源
            JDBCUtils.close(pstmt,conn);
        }
    }
}

测试结果正常。

 


Spring JDBC

概念

Spring JDBC是由Spring框架提供的对JDBC的简单封装。

它提供了一个JDBC Template对象来简化JDBC的开发。

具体使用

入门使用

步骤:

  • 导入jar包
    • commons-logging-
    • spring-beans
    • spring-core
    • spring-jdbc
    • spring-tx

这里提供我分享的jar包

链接:https://pan.baidu.com/s/1uLLESl3H863d6m4gG_EVhA
提取码:ya6n

lib目录下的那五个jar包全需要导入

  • 创建JDBCTemplate对象,依赖于数据源DataSource (即需要数据库连接池对象作为参数)
  • 调用JDBCTemplate的方法来完成CRUD的操作
    • update():执行DML语句
    • queryForMap():执行DQL,查询结果,将结果封装为map集合
    • queryForList():执行DQL,查询结果,将结果封装为List集合
    • query():查询结果,将结果封装为JavaBean对象
    • queryForObject:查询结果,将结果封装为对象

我们先将五个需要的jar包导入项目中,然后我们在包pers.luoluo.jdbctemplate下面来写测试代码:

我们的SpringJDBC是对数据库连接池的再优化,所以数据库的jdbc驱动包、连接池(Druid)包这些包当然也得有了,这些是基石。

public class JdbcTemplateDemo1 {
    public static void main(String[] args) {
        //1.导入jar包
        //2.创建JDBCTemplate对象
        JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDateSource());
        //3.调用方法
        String sql="UPDATE account SET balance=5000 WHERE name=?";
        int count=template.update(sql,"Tom");
        System.out.println(count);
    }
}

直接执行就OK,JdbcTemplate的对象template对负责从连接池中建立连接,并且在执行结束后进行连接的归还操作,你要做的就是写好SQL语句执行即可,这是我第一次接触Java的Spring框架,感到非常好用。

DML、DQL语句的执行

有表emp如下:

    id  ename      job_id     mgr  joindate    salary    bonus     dept_id  
------  ---------  ------  ------  ----------  --------  --------  ---------
  1001  孙悟空          4    1004  2000-12-17  8000.00   (NULL)      20
  1002  卢俊义          3    1006  2001-02-20  16000.00  3000.00     30
  1003  林冲            3    1006  2001-02-22  12500.00  5000.00      30
  1004  唐僧            2    1009  2001-04-02  29750.00  (NULL)        20
  1005  李逵            4    1006  2001-09-28  12500.00  14000.00      30
  1006  宋江            2    1009  2001-05-01  28500.00  (NULL)        30
  1007  刘备            2    1009  2001-09-01  24500.00  (NULL)        10
  1008  猪八戒          4    1004  2007-04-19  30000.00  (NULL)       20
  1009  罗贯中          1  (NULL)  2001-11-17  50000.00  (NULL)        10
  1010  吴用           3    1006  2001-09-08  15000.00  0.00         30
  1011  沙僧           4    1004  2007-05-23  11000.00  (NULL)        20
  1012  李逵           4    1006  2001-12-03  9500.00   (NULL)        30
  1013  小白龙          4    1004  2001-12-03  30000.00  (NULL)        20
  1014  关羽           4    1007  2002-01-23  13000.00  (NULL)         10
  1. 修改1号数据的salary为10000
  2. 添加一条记录
  3. 删除刚才添加的记录
  4. 查询id为1的记录,将其封装为Map集合
  5. 查询所有的记录,将其封装为List
  6. 查询所有的记录,将其封装为Emp对象的List集合
  7. 查询总的记录数

好,我们开始。

创建一个pers.luoluo.domain包,domain包中创建一个JavaBean对象,Emp,意为员工

public class Emp {
    private Integer id;
    private String ename;
    private Integer job_id;
    private Integer mgr;
    private Date joindate;
    private Double salary;
    private Double bonus;
    private Integer dept_id;

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", ename='" + ename + '\'' +
                ", job_id=" + job_id +
                ", mgr=" + mgr +
                ", joindate=" + joindate +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", dept_id=" + dept_id +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Integer getJob_id() {
        return job_id;
    }

    public void setJob_id(Integer job_id) {
        this.job_id = job_id;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getJoindate() {
        return joindate;
    }

    public void setJoindate(Date joindate) {
        this.joindate = joindate;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    public Double getBonus() {
        return bonus;
    }

    public void setBonus(Double bonus) {
        this.bonus = bonus;
    }

    public Integer getDept_id() {
        return dept_id;
    }

    public void setDept_id(Integer dept_id) {
        this.dept_id = dept_id;
    }
}

这个类中,我们类的属性全都是引用类型的变量,即基本数据类型的变量我们也使用了对应封装类,这么做的原因和后面的BeanPropertyRowMapper有关,一会你就知道了。

接下来我们通过单元测试的方法来解决那些问题,还记得单元测试吗?下面是我分享的JUnit包:

链接:https://pan.baidu.com/s/1If_y5S16T6P1uXCJwTPb4g
提取码:wun0

pers.luoluo.jdbctemplate包下新建一个java文件来测试,下面我已经将七个任务封装成七个方法,可以利用单元测试执行。

public class JdbcTemplateTest {

    //1.获取JDBCTemplate对象
    private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDateSource());

    // 修改1号数据的salary为10000
    @Test
    public void test1(){
        //2.定义sql
        String sql="UPDATE emp SET salary=10000 WHERE id=1001";
        //3.执行SQL
        int count=template.update(sql);
        Assert.assertEquals(1,count); //测试通过
    }
    // 添加一条记录
    @Test
    public void test2(){
        //2.定义sql
        String sql="INSERT INTO emp(id,ename,dept_id) values(?,?,?)";
        int count=template.update(sql,1015,"郭靖",10);
        Assert.assertEquals(1,count);  //测试通过
    }
    // 删除刚才添加的记录
    @Test
    public void test3(){
        String sql="DELETE FROM emp WHERE id=?";
        int count=template.update(sql,1015);
        Assert.assertEquals(1,count);  //测试通过
    }
    // 查询id为1的记录,将其封装为Map集合
    //将字段封装为key,将数据封装为value
    //注意:queryForMap方法查询的结果集长度只能是1
    @Test
    public void test4(){
        String sql="SELECT * FROM emp WHERE id=?";
        Map<String, Object> map = template.queryForMap(sql, 1001);
        System.out.println(map);
    }
    // 查询所有的记录,将其封装为List
    // 将每一条记录封装为一个Map集合,再将多个Map集合装载到一个List集合中
    @Test
    public void test5(){
        String sql="SELECT * FROM emp";
        List<Map<String, Object>> maps = template.queryForList(sql);
        for(Map<String,Object> map:maps){
            System.out.println(map);
        }
    }
    // 查询所有的记录,将其封装为Emp对象的List集合
    @Test
    public void test6_1(){
        String sql="SELECT * FROM emp";
        //手动重写RowMapper接口的方法实现装载
        List<Emp> query = template.query(sql, new RowMapper<Emp>() {
            @Override
            public Emp mapRow(ResultSet rs, int i) throws SQLException {
                Emp emp = new Emp();
                //获取数据(rs的getXXX的参数传入名必须与数据库端字段名相同)
                emp.setId(rs.getInt("id"));
                emp.setEname(rs.getString("ename"));
                emp.setJob_id(rs.getInt("job_id"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setJoindate(rs.getDate("joindate"));
                emp.setSalary(rs.getDouble("salary"));
                emp.setBonus(rs.getDouble("bonus"));
                emp.setDept_id(rs.getInt("dept_id"));
                return emp;
            }
        });
        for(Emp emp:query){
            System.out.println(emp);
        }
    }
    // 查询所有的记录,将其封装为Emp对象的List集合
    //一般我们使用BeanPropertyRowMapper实现类,可以完成数据到JavaBean的自动封装
    //参数要传入JavaBean的class对象,它会自动匹配变量名和数据库中的字段名
    //另外,JavaBean中的字段对应的数据库字段的数据有NULL的话,JavaBean的那个变量请设置成引用类型
    //因为基本数据类型是不能被赋值NULL的,必须是引用类型才可以。
    //这也就是为什么前面Emp表中的变量的类型int变成了Integer(int的封装类)等
    @Test
    public void test6_2(){
        String sql="SELECT * FROM emp";
        List<Emp> query = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
        for(Emp emp:query){
            System.out.println(emp);
        }
    }
    // 查询总的记录数
    //queryForObject一般用于聚合函数的查询
    @Test
    public void test7(){
        String sql="SELECT COUNT(id) FROM emp";
        long total = template.queryForObject(sql, Long.class);
        Assert.assertEquals(14,total); //测试通过
    }
}

test6中我们分成了两种情况:

第一种是说创建RowMapper的匿名对象 ,这里是匿名内部类的写法,我们在后面重写具体的方法,实现ResultSet到JavaBean的映射。

第二种是使用了一个Spring框架内部提供的类——BeanPropertyRowMapper,通过传入字节码文件,它会自动将数据库字段与JavaBean类中的变量进行映射,需要注意的是JavaBean中的变量建议使用引用类型,如果是基本数据类型,而对应的数据库字段中有个数据是null,那么这里就会报错(因为基本数据类型不能是null,所以这里无法映射过来)。

 

 


JDBC,以及衍生出来的数据库连接池、Spring JDBC的知识点的学习就到此为止了。

青山不改、绿水长流,我们后会有期。

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

发表评论

是的,我就是计算机界的枭雄!