数据库 (二)--JDBC

JDBC概述

JDBC(JavaDataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。

JDBC原理

早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!
image
JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
当然还有第三方公司专门为某一数据库提供驱动,这样的驱动往往不是开源免费的!

JDBC连接数据库(六个步骤)

  • 装载数据库驱动程序;
  • 建立数据库连接;
  • 创建数据库操作对象;
  • 访问数据库,执行sql语句;
  • 处理返回结果集;
  • 断开数据库连接。

连接角度看 JDBC

image

  1. TCP建立连接的三次握手;
  2. MySQL认证的三次握手;
  3. 真正的SQL执行;
  4. MySQL的关闭;
  5. TCP的四次握手关闭;

尝试连接

import org.junit.Test;
import java.sql.*;

public class TestJDBC {
    @Test  //基本查询语句d
    public void statement() throws Exception {
        //驱动
        String driver ="com.mysql.jdbc.Driver";
        Class.forName(driver);

        //数据库链接
        String url ="jdbc:mysql://localhost:3306/jtdb";
        Connection cn = DriverManager.getConnection(url,"root","1234");

        //得到语句对象,它来执行sql语句
        Statement stat = cn.createStatement(); //创建语句对象

        //查询返回结果集 resultset
        String sql = "select * from dept";
        ResultSet rs = stat.executeQuery(sql);  //执行查询的语句

        //打印数据库表元数据,列名,dept字段数
        int cols = rs.getMetaData().getColumnCount();
        for(int i = 1 ; i <= cols ; i++ ){
            System.out.print(rs.getMetaData().getColumnName(i)+"\t");
        }
        System.out.println();//换行

        //打印表中数据
        while(rs.next()){
            for(int i =1 ; i<=cols ;i++){
                //rs.getString(i);  //获取第几列值,返回字符串类型
                System.out.print(rs.getString(i)+"\t");
            }
            System.out.println();  //每一条数据打印完毕换行
        }
    }

    @Test //预编译查询语句
    public void prepareStatment() throws Exception {
        String driver ="com.mysql.jdbc.Driver"; //驱动
        String url = "jdbc:mysql://localhost:3306/jtdb"; //链接

        //预编译sql中用?作为占位符,将来会被真实值替换掉
        String sql = "select * from dept where deptno = ?" ;

        Class.forName(driver);
        Connection cn = DriverManager.getConnection(url,"root","1234");  //获取链接

        //带参数
        PreparedStatement ps = cn.prepareStatement(sql);  //返回预编译对象
        ps.setString(1,"3");  //参数1:指?的位置 可能有多个;参数2:是参数值

        //查询
        ResultSet rs =ps.executeQuery();
        int cols = rs.getMetaData().getColumnCount(); //列的总数
        for (int i = 1 ; i <= cols ; i++){
            //打印列的名称
            System.out.print(rs.getMetaData().getColumnName(i)+"\t");
        }

        //打印数据
        while(rs.next()){//每次向下取一条,直到结尾
            System.out.println();//换行
            for(int i = 1 ; i <= cols ; i++){
                System.out.print(rs.getString(i)+"\t");
            }
        }
    }

}

参数含义

DriverManager

驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中后续的处理就可以对这个列表进行操作。

    Class.forName("com.mysql.jdbc.Driver");//加载驱动
Connection

在JDBC中连接被抽象为Connection的连接对象

DriverManager.getConnection(url,username,password)
Statement

用于执行不带参数的简单 SQL 语句,每次执行 SQL语句,数据库都要执行 SQL 语句的编译

Statement stat = connection.createStatement();

prepareStatement与Statement的区别
1、Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句。
2、PrepareStatement是预编译的SQL语句对象,sql语句被预编译并保存在对象中。被封装的sql语句代表某一类操作,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值。
3、使用PrepareStatement对象执行sql时,sql被数据库进行解析和编译,然后被放到命令缓冲区,每当执行同一个PrepareStatement对象时,它就会被解析一次,但不会被再次编译。在缓冲区可以发现预编译的命令,并且可以重用。
4、PrepareStatement可以减少编译次数提高数据库性能。

ResultSet

当我们查询数据库时,返回的是一个二维的结果集,我们这时候需要使用 ResultSet 来遍历结果集,获取每一行的数据。

ResultSet rs = stat.executeQuery(sql);

数据库连接池

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
  连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
image

池化

连接池类似于线程池或者对象池,数据库连接池为系统的运行带来以下优势:

  • 昂贵的数据库连接资源得到重用;
  • 减少数据库连接建立和释放的时间开销,提高系统响应速度;
  • 统一的数据库连接管理,避免连接资源的泄露。

数据库连接池的工作原理

  • 连接池的建立
  • 连接池中连接的使用管理
  • 连接池的关闭

第一、连接池的建立。
一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。
Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

第二、连接池的管理。
连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

第三、连接池的关闭。
当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

连接池需要注意的点

1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。
这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。
为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他,如果没有就抛出一个异常给用户。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?
系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

阅读 51

推荐阅读