jdbc访问数据库详解

一.jdbc简介
jdbc,java database Connectivity即java数据库连接,是一组定义了如何连接和操作数据库的java api。下面结合一个简单示例说明下具体步骤:

public class Main {
    private static Logger LOG = LoggerFactory.getLogger(Main.class);

    public static String DRIVER    = "com.mysql.jdbc.Driver";
    public static String USER_NAME = "root";
    public static String PASSWORD  = "root";
    public static String TABLE     = "account";
    public static String URL       = "jdbc:mysql://127.0.0.1/bank";

    public static void main(String[] args) {

        //导入驱动
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            LOG.error("驱动加载失败", e);
        }
        //获取连接
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
        } catch (SQLException e) {
            LOG.error("建立连接失败", e);
        }
        try {
            //获取可用于执行sql的statement,statement中也包含了连接到数据库的一些信息
            Statement statement = connection.createStatement();
            String sql = "select account_id,avail_balance from " + TABLE + " limit 2";
            //通过stament执行sql,并获取执行结果,返回的结果resultSet可以看成一个二维表格形式,
            //next方法表明是否还有下一行,可以通过座标或列名获取下一列;只能遍历下一行,不能返回或跳到指定行
            ResultSet resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {
                long id = resultSet.getLong(1);
                long balance = resultSet.getLong(2);
                System.out.println(id + " " + balance);
            }
        } catch (Exception e) {
            LOG.error("sql执行异常");
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    LOG.error("异常");
                }
            }
        }
    }
}

可以看到一共分为5步:加载驱动–>获取连接–>获取Statement对象–>执行sql–>解析结果。上面给的sql是执行的查询操作流程,但对于删除的新增等操作也是类似的。
上面涉及到的比较重要的几个类是:Driver、DriverManager、Connection、Statement、ResultSet,下面就依次介绍下这几个类以及它们之间的联系。
二.jdbc中主要的类及其层次关系
下面这张图比较清楚的说明Driver、DriverManger、Connection、Statement、ResultSet之间的层次关系以及它们大致的功能。
这里写图片描述
下面具体来介绍每个类的作用以及一些值得注意的地方。
1)Driver
使用JDBC操作数据库的第一步就是将数据库对应的驱动(Driver)载入内存。从上面的代码可以看到,所谓的导入驱动本质上只是通过Class.forName()方法将一个具体的类
加载到了内存之中,比如上面的就是:com.mysql.jdbc.Driver这个类,而其实所谓的驱动就是实现了Java.sql.Driver接口的类。下面看下Driver接口中的几个主要方法:
这里写图片描述
主要使用的是两个方法:acceptsURL()、connect()。
acceptsURL()负责检查传入的url,判断驱动能否打开该url,需要指出的是这里的检查并不会对url进行实际的连接操作而只是会对格式进行检查;
每个数据库连接的url都有自己定义的格式,如果检查符合格式则返回true,否则返回false。
例如mysql的url格式为: jdbc:mysql://host:port/database_name
connect()方法负责根据传入的url和用户名、密码等信息建立与数据库的连接,返回可以对数据库进行操作的Connection对象。
因为每种数据库建立连接的方式是不一样的,所以操作不同的数据库需要使用不同的Driver类的connect()方法,这就是不同数据库需要导入不同驱动的原因。
下面手动操作mysql的Driver类进行数据库的连接和协议测试:

public static void testDriver() throws Exception {
    //导入mysql的驱动,并且获得Driver实例
    Driver driver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();
    //url格式测试
    System.out.println("测试标准协议1: " + driver.acceptsURL("jdbc:mysql://"));
    System.out.println("测试标准协议2: " + driver.acceptsURL("jdbc:mysql:mxj://"));
    System.out.println("测试标准协议3: " + driver.acceptsURL("jdbc:mysql:replication://"));
    System.out.println("测试oracle协议: " + driver.acceptsURL("jdbc:oracle:thin:@//<host>:<port>/ServiceName"));
    //建立真正连接
    Properties properties = new Properties();
    properties.put("user", USER_NAME);
    properties.put("password", PASSWORD);
    Connection connection = driver.connect(URL, properties);
    System.out.println(connection == null);
}

最开始的示例代码中,我们并不是直接获取Driver实例然后获取的Connection而是通过DriverManager来获取的连接,这是因为在实际应用中都是通过DriverManager来管理的Driver。
使用DriverManager的好处在于可以方便的管理多个驱动:只需要传入一个url,DriverManger就会自动需要匹配这个url的驱动并建立连接返回Connection对象。
2)DriverManger
应用程序通过DriverManager来管理驱动,DriverManager的主要功能包括驱动的注册、删除,通过url获取连接或驱动。下面是主要的接口简介:
这里写图片描述
首先是驱动的注册问题,DriverManager如何知道有哪些驱动是可以管理的。首先来看下DriverManger的初始化方法:

/**
 * Load the initial JDBC drivers by checking the System property
 * jdbc.properties and then use the {@code ServiceLoader} mechanism
 */
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

主要是调用了loadInitialDrivers()方法,下面是具体实现:

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

简而言之通过Class.forName()加载系统属性:jdbc.drivers配置的驱动以及通过ServiceLoad加载META-INF/services目录java.sql.Driver文件中配置的驱动(ServiceLoad是Java提供的一种根据配置文件加载类的加载器,类似于spring的依赖注入)。但是这两种方式都只是加载了驱动而并没有向DriverManger注册的步骤,其实真正的注册操作是在Driver里面实现的,具体的来说是通过Driver中包含了一段将自身向DriverManger注册的静态代码块,在Driver被加载到内存中的时候这段代码块就会执行。下面是mysql Driver的注册实现:

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

除了DriverManger本身初始化加载驱动的方式,我们在代码中通过Class.forName()加载的驱动也是这样注册到DriverManager上去的;这些注册到DriverManger上去的驱动,会被保存在下面的数组中

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

解决完了驱动注册的问题,下面再介绍下DriverManger其它接口的大致实现。
getConnection()
这个方法有多个重载形式,作用是根据传入的url建立和对应数据库的连接并返回Connection对象。核心逻辑如下代码–依次尝试每一个注册的驱动能否建立连接:

for(DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    if(isDriverAllowed(aDriver.driver, callerCL)) {
        try {
            println("    trying " + aDriver.driver.getClass().getName());
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
                // Success!
                println("getConnection returning " + aDriver.driver.getClass().getName());
                return (con);
            }
        } catch (SQLException ex) {
            if (reason == null) {
                reason = ex;
            }
        }

    } else {
        println("    skipping: " + aDriver.getClass().getName());
    }
}

getDriver()
传入一个url,返回可以打开这个url的驱动。这里基本逻辑同上,只是并不尝试建立实际的连接,而是通过Driver的acceptsURL方法进行校验

// Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL.
for (DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    if(isDriverAllowed(aDriver.driver, callerClass)) {
        try {
            if(aDriver.driver.acceptsURL(url)) {
                // Success!
                println("getDriver returning " + aDriver.driver.getClass().getName());
            return (aDriver.driver);
            }

        } catch(SQLException sqe) {
            // Drop through and try the next driver.
        }
    } else {
        println("    skipping: " + aDriver.driver.getClass().getName());
    }
}

3)Connection
Connection表示与数据库的连接,具体来说通过Connection对象可以获取到数据库的一些信息,这些信息包括:其表信息,应该支持的SQL语法,数据库内有什么存储过程,此链接功能的信息等等。下面简要介绍下常用的三个功能:
1.获取可以执行sql或存储过程的Statement对象,与数据库进行交互。

Class.forName(DRIVER);
Driver driver = DriverManager.getDriver(URL);
Properties properties = new Properties();
properties.put("name", USER_NAME);
properties.put("password", PASSWORD);
Connection connection = driver.connect(URL,properties);
//静态Statement对象
Statement statement = connection.createStatement();
//预编译Statement对象
PreparedStatement preparedStatement = connection.prepareStatement(SQL);
//存储过程Statement
CallableStatement callableStatement = connection.prepareCall(SQL);

2.控制sql语句的事务
默认情况下,Connection对关联的Statemen执行的sql语句都是自动提交的,即Statement语句执行完成以后,自动commit影响物理数据库;通过Connection设置
setAutoCommit为false,可以进行事务控制,手动控制Statement语句的commit和rollback。

 Class.forName(DRIVER);
    Driver driver = DriverManager.getDriver(URL);
    Properties properties = new Properties();
    properties.put("name", USER_NAME);
    properties.put("password", PASSWORD);
    Connection connection = driver.connect(URL, properties);
    //设置不自动提交
    connection.setAutoCommit(false);
    Statement statement = connection.createStatement();
    try {
        statement.executeUpdate(SQL);
        //statement对象也可以获取创建它的Connection对象
        //手动提交
        statement.getConnection().commit();
    } catch (SQLException e) {
        //发生异常时,手动回归
        statement.getConnection().rollback();
    }
}

3.获取连接数据库的元数据,即关于数据库整体情况的一些数据

DatabaseMetaData metaData = connection.getMetaData();

4) Statement
Statement用于通过Connection将sql提交给服务器执行,并获取执行的结果;对于批量sql或预编译sql Statement还负责将其整理成数据库能够识别的语句再提交给服务器执行。
这里写图片描述

Sql语句分为增删改查几种(insert,delete,update,select),而JDBC根据是否改变数据,将其分为两类:
查询操作:select
更新操作:update、insert、update
相应的,Statement执行具体sql的方法分为以下几类:
1.不区分语句类型,直接执行
statement提供了execute(String sql)来执行这种查询,定义如下:
这里写图片描述
如果执行的是查询操作,函数返回true,然后可以通过statement.getResultSet() 方法来获取 Resultset结果集;
如果执行的是更新操作,函数返回false,然后可以通过statement.getUpdateCount()方法获取影响的行数。
2.执行查询操作
stament提供了executeQuery(String sql)方法来执行查询操作,具体定义如下:
这里写图片描述
3.执行更新操作
statement提供了executeUpdate()方法来执行更新操作,具体定义如下:
这里写图片描述
4.批量执行sql
有时候需要将一些sql语句一起提交给数据库,批量执行,statement提供了一些方法,对批量sql的支持:
这里写图片描述
这里写图片描述
以上都是普通Statement的功能,对于PrepareStatement和CallBackStatement还会有些不同。
ResultSet指的是查询语句的返回结果,是数据库数据的映射,可以看成一个二维的数组,不过需要根据迭代器来逐行遍历。
Resultset 提供了很多游标定位的方法,部分方法已经在下面列出:
这里写图片描述
三.jdbc访问数据库基本流程总结
一个基本的JDBC工作流程,分为以下几步:
1.加载特定数据库驱动器实现类,并注册驱动器(Driver会注册到DriverManager中);
2. 根据特定的URL,返回可以接受此URL的数据库驱动对象Driver;
3.使用数据库驱动 Driver 创建数据库连接Connection会话;
4. 使用 Connection对象创建 用于操作sql的Statement对象;
5. statement对象 .执行 sql语句,返回结果ResultSet 对象;
6. 处理ResultSet中的结果;
7. 关闭连接,释放资源。

public class DBConnection {

    static final String  URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
    static final String USER_NAME ="louluan";
    static final String PASSWORD = "123456";

    public static void main(String[] args) {
        connectionTest();
    }

    public static void connectionTest(){

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            //1.加载类,并注册驱动器(Driver会注册到DriverManager中)

            //加载Oracle数据库驱动
            Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();

            //2.根据特定的URL,返回可以接受此URL的数据库驱动对象
            Driver driver = DriverManager.getDriver(URL);
            Properties props = new Properties();
            props.put("user", USER_NAME);
            props.put("password", PASSWORD);

            //3.使用数据库驱动创建数据库连接Connection会话
            connection = driver.connect(URL, props);

            //4.获得Statement对象
            statement = connection.createStatement();
            //5.执行 sql语句,返回结果
            resultSet = statement.executeQuery("select * from hr.employees");
            //6.处理结果,取出数据
            while(resultSet.next())
            {
                System.out.println(resultSet.getString(2));
            }

            //7.关闭链接,释放资源
        } catch (ClassNotFoundException e) {
            System.out.println("加载Oracle类失败!");
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
                //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章