JDBC数据库接口(Java)

1、阅前浏览:

1.1 JAR包定义

JAR 文件就是 Java Archive File,顾名思意,它的应用是与 Java 息息相关的,是 Java 的一种文档格式。JAR 文件非常类似 ZIP 文件——准确的说,它就是 ZIP 文件,所以叫它文件包。JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中,包含了一个 META-INF/MANIFEST.MF 文件,这个文件是在生成 JAR 文件的时候自动创建的。

1.2 接口的作用

接口是一种特殊类。用于抽象出行为特征与常量属性。接口即可以强化规则又可以有效地降低类与类之间的耦合度,在未来业务变更时降低项目修改的难度

比如有一个汽车工厂【CarFactory】,负责生产汽车。但是不负责生产发动,因此汽车【CarFactory】可以设置发动机引擎规则,即为【Engine接口】。然后交给跑车发动机研发商【SprotEngineFactory】和越野车发动机研发商【SuvEngineFactory】进行研发。研发成功后可以直接组装到汽车上即可。

1.3 JAVA规范

1.3.1 JAVASE规范
JAVASE(Java Platform,Standard Edition)是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台的总称.。指定了JAVA编程语言开发时规范。指Java类在单独执行,在单独处理业务时需要遵守语法规则。比如 继承,访问权限,接口实现,控制语句,标识符命名
1.3.2 JAVAEE规范
JAVAEE规范基于JAVASE规范。制定了Java与不同服务器之间开发规则。Java在进行商业开发时遵守开发规则共有13中规范。在商业开发过程中,往往需要Java类与不同服务器进行沟通来解决当前业务。由于在商业开发过程中,Java需要与13种不同服务器进行沟通,因此SUN公司根据13种服务器特征指定13套接口,这13套接口统称为JAVAEE规范。SUN公司相当于【汽车制造工厂】负责提供接口(Engine),但是不负责提供接口中实现类。接口中实现类由不同服务器厂商来提供。服务器厂商将完成接口实现类以JAR包形式提供。Java程序员通过jar包得到接口中实现类,从而实现与指定服务器之间交流。

1.4 API :

应用程序编程接口(这里的接口不是狭义的interface)

API包括:API源码、API字节码、API帮助文档。

使用API的时候,以上三元素必须保证版本一致。

2、 JDBC简介

  1. Java Database Connectivity:Java语言连接数据库
  2. JDBC是SUN公司做的关于Java与数据库的接口
  3. 具体实现是由各大数据库公司来制作,做出jar包称为连接数据库的驱动
  4. 向对应数据库官网下载该jar包,导入到项目中

思考:MySQL数据库的原理和Oracle数据库的原理不同,那我们的Java程序应该怎么写呢?MySQL数据库一套原理,应该对应一套Java程序 Oracle数据库是另一套原理,也应该对应另一套Java程序 如果采用以上这种机制,Java程序的扩展力很差。

解决方案:SUN公司制定好一套接口,这套接口就是一套连接数据库的API,这套API叫做:JDBC 换句话说:Java程序员根本不需要关系底层的数据库是哪种类型,编写一套Java代码就可以连接所有的数据库。 可见我们Java程序员是面向JDBC接口调用相关的方法来完成数据库的CRUD(增删改查)操作。

3、JDBC开发前的准备工作

3.1 下载驱动

第一:需要从官网下载数据库对应版本的驱动程序,驱动是xxx.jar,例如MySQL的:mysql-connector-java-5.1.26-bin.jar

第二:以上的jar包下载之后,要想让JVM能够进行加载,必须将上面的jar包配置到环境变量classpath当中。

3.2 配置环境变量classpath

classpath=.;D:\course\05-JDBC\resource\mysql-connector-java-5.1.26\mysql-connector-java-5.1.26-bin.jar

4、认识JDBC实现的接口

实现功能

对应接口

驱动管理

DriverManager

连接接口

Connection DatabaseMetaData

语句对象接口

Statement PreparedStatement CallableStatement

结果集接口

ResultSet ResultSetMetaData

MySQL与Oracle实现

Oracle实现

  • ojdbc6.jar / ojdbc14.jar
  • Class.forName(“oracle.jdbc.OracleDriver”)
    MySQL实现
  • mysql-connector-java-5.1.8-bin.jar
  • Maven可搜索jar包mysql-connector-java
  • Class.forName(“com.mysql.cj.jdbc.Driver”)
    URL连接地址后缀

连接数据库的时候,可以在 url 地址字符串后添加后缀参数,设置或开启某些功能,拼接规则同 webget 请求方式,使用 ?&。如:jdbc:mysql:///testdb?a=1&b=2

功能

参数

备注

指定系统时区

serverTimezone=GMT%2B8

mysql 8x 必须指定

定义写入字符集

useUnicode=true&characterEncoding=UTF-8

储存中文时指定

多SQL执行

allowMultiQueries=true

允许一次执行多条SQL

sqlYog mycat 都默认开启多SQL执行

5、JDBC开发(五)六部曲

  • 一般情况下我们在整个连接中都需要try,catch,其中1~5步在try中,第6步在finally中
  • 错误类型为SQLException,finally中close()或者使用java新语法try()定义三者
try{
//1.注册驱动(两种方式,以下是注册驱动的第一种方式,后期再讲解第二种注册驱动的方式。)  
  //创建驱动对象
  java.sql.Driver driver = new com.mysql.jdbc.Driver(); //多态
  //完成驱动注册 
  DriverManager.registerDriver(driver); 
  }catch(SQLException e){
  e.printStackTrace();
  }

//2.获取连接对象
  //url可以添加各种功能后缀,其中8x的mysql必须指定时区
  String url = "jdbc:mysql://localhost:3306/database";
  String user = "root";
  String password = "root";
  Connection conn = DriverManager.getConnection(url,user,password);    

//3.获取数据库操作对象
/*
 通过连接对象Connection获取数据库操作对象Statement代码:
一个数据库连接对象可以创建多个数据库操作对象。
*/

  Statement stmt = conn.createStatement();
  Statement stmt2 = conn.createStatement();
  
//4.执行SQL语句[DQL]
  ResultSet rs = stmt.excuteQuery("DQL语句select");
//执行SQL语句[DML]
  int count = stmt.executeUpdate("DML语句insert update delete");
//执行SQL语句
	boolean flag = stmt.excute(sql);
//SQL语句结尾不用加分号,注意空格干扰语义

//(5.处理查询结果,执行DQL语句才有第五步,执行DML语句不用处理查询结果集)
  while(rs.next()){
      String a1 = rs.getString(1);        //以列的下标,JDBC下标都以1开始
      String a2 = rs.getString("col2");    //以列的名称
  }
//next方法如果当前行有数据返回true,否则false.getXXX()取得字段内容

//6.释放资源
 Connection、Statement、ResultSet都会占用一定的资源,它们需要全部释放/关闭
* 释放资源的注意事项:
- 为了保证每一个资源都可以关闭,建议将关闭资源的代码写到finally语句块当中。
- 先关闭ResultSet、再关闭Statement、最后关闭Connection。
- 在finally语句块当中关闭这些资源的话必须保证这些变量在finally语句块当中是可以访问的,需要将try语句块当中的局部变量提出到try语句块之外。
- 关闭这些资源的时候统一调用close方法
- 关闭资源之前最好判断这些资源是否为空,避免空指针异常的发生。
- 关闭的close方法有异常,需要编写代码的时候处理这些异常,分别对其进行异常处理,不要一块try进行处理。

  try {
      if (rs!=null) rs.close();
  } catch (SQLException e) {                
      e.printStackTrace();
  }
    try {
      if (ps!=null) ps.close();
  } catch (SQLException e) {                
      e.printStackTrace();
  }
    try {
      if (conn!=null) conn.close();
  } catch (SQLException e) {                
      e.printStackTrace();
  }

6、两种不同驱动注册方式

  1. 第一种:
    Driver driver = new com.mysql.jdbc.Driver();
    DriverManager.registerDriver(driver); // 这行代码可以忽略不写
  2. 第二种:(这种方式使用较多)
    Class.forName(“com.mysql.jdbc.Driver”);

Class.forName(“com.mysql.jdbc.Driver”);
作用是:将com.mysql.jdbc.Driver这个类装载到JVM当中。装载过程中会自动执行静态代码块完成驱动的注册。

7、将数据库的配置信息写到文件中,通过IO+Propertie形式读取信息

1、driver、url、user、password
以上信息应该是可配置的,不应该写死到java程序当中。写到程序中不容易扩展。

2、应该将以上的配置信息写到属性配置文件当中,以后要想连接其它数据库,直接让用户修改配置文件即可。**

补充:什么是属性配置文件?
  • 属性配置文件通常扩展名是:xx.properties,但不是必须的
  • 属性配置文件当中配置了大量的key和value,通常key和value之间使用“=”隔开,但不是必须的
  • 属性配置文件当中的key不能重复,重复的话value会覆盖
  • java.util.Properties属性类是Map集合,key和value都是String类型,并且是线程安全的
  • 属性配置文件当中的key一旦确定,不要修改,一般都是修改value,因为key是写死在程序中的。

8、关于SQL注入

8.1 你怎么看待SQL注入?

SQL注入不一定是一种危害,但大多数情况下会危及系统的安全,有一些系统中的特殊功能可能还需要SQL注入功能, 此时为了支持SQL注入,需要使用java.sql.Statement接口。

8.2 怎么解决SQL注入,或者说怎么防止SQL注入?

java.sql.PreparedStatement叫做预编译的数据库操作对象,可以防止SQL注入,
原理:对SQL语句框架进行预先编译,再接收用户提供的信息,即使提供的信息当中含有SQL语句的
关键字,但是这些关键字并没有参与SQL语句的编译,那么它也只是一个普通的字符串,SQL语句原意不会被扭曲,防止了SQL注入。PreparedStatement是Statement的子接口大多数情况下还是需要防止SQL注入的,所以PreparedStatement使用较多。

8.3 Statement和PreparedStatement区别

  1. Statement是先进行SQL语句拼接,再进行SQL语句的编译,存在SQL注入问题;
  2. PreparedStatement是预先编译SQL语句框架(带有占位符的SQL),然后再给占位符赋值,防止SQL注入;
  3. Statement编译一次,执行一次,编译一次,执行一次,PreparedStatement编译一次框架,执行N次,
  4. 所以PreparedStatement效率较高;PreparedStatement使用较多。

9、使用PreparedStatemet实现CRUD

public class JDBCTest{
   public static void main(String[] args) {
     Connection conn = null;
     PreparedStatement ps = null;
     try {
       //1、注册驱动
       Class.forName("com.mysql.jdbc.Driver");
       //2、获取连接
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "root");
       //3、获取预编译的数据库操作对象
       String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
       ps = conn.prepareStatement(sql);
       ps.setInt(1, int);
       ps.setString(2, "String");
       ps.setString(3, "String");
       //3、获取预编译的数据库操作对象
       String sql = "update dept set dname = ? , loc = ? where deptno = ?";
       ps = conn.prepareStatement(sql);
       ps.setString(1, "String");
       ps.setString(2, "String");
       ps.setInt(3, int);
       //3、获取预编译的数据库操作对象
       String sql = "delete from dept where deptno = ?";
       ps = conn.prepareStatement(sql);
       ps.setInt(1, int);
       //4、执行SQL语句
       int count = ps.executeUpdate();
       System.out.println(count);
     } catch (Exception e) {
       e.printStackTrace();
     } finally{
       //5、释放资源
       if(ps != null){
         try {
           ps.close();
         } catch (SQLException e) {
           e.printStackTrace();
         }
       }
       if(conn != null){
         try {
           conn.close();
         } catch (SQLException e) {
           e.printStackTrace();
         }
       }
     }
   }
 }

10、最终使用

  1. 驱动自动注册,不再注册
  2. 通过properties文件获取连接器
  3. 根据需求,有参数的SQL语句用预编译的数据库操作对象,无参可以用普通的
  4. 执行语句并处理结果
  5. 一定记得关闭数据库连接,可以通过jdk1.7特性,在try小括弧中定义

11、自用工具类-创建JDBCUtils

由于数据库的连接与关闭代码比较类似,所以我们常自己书写一个工具类去实现操作,减少代码冗余
工具类常定义为一个静态类,以下是一个工具类的案例,实现了通过配置文件,获取连接对象与关闭连接

public class JDBCUtils {
    //工具类不能够产生对象,只能通过类名.的方式调用方法。
    private JDBCUtils(){
    }
    //通过配置文件获取数据
    private static Properties prop = null;
    static{
        prop = new Properties();
        try {
            File file = new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath());
            FileInputStream fis = new FileInputStream(file);
            //FileInputStream fis=JDBCUtils.class.getClassLoader().getResourceAsStream("conf.properties");
            prop.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //PUBLIC返回连接对象
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName(prop.getProperty("driver"));
        return DriverManager.getConnection(
                prop.getProperty("url"),
                prop.getProperty("user"),
                prop.getProperty("password"));
    }
    //PUBLIC关闭资源方法
    public static void close(Connection conn, Statement stat, ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                rs = null;
            }
        }
        if(stat != null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                stat = null;
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                conn = null;
            }
        }
    }
}

12、更多数据操作

12.1 数据库元数据

  • 通过数据库连接对象的getMetaData()方法返回数据库元数据对象
  • 数据库名称:String dbmd.getDatabaseProductName()
  • 数据库驱动版本:String dbmd.getDriverVersion()
  • 数据库用户名:String dbmd.getUserName()
  • 其他各种方法

12.2表元数据

  • 通过结果集的getMetaData()方法返回结果集表元数据对象
  • 表中字段的总数:int rsmd.getColumnCount()
  • 表中第i个字段的名称:String rsmd.getColumnName(i)
  • 表中第i个字段的类型:String rsmd.getColumnTypeName(i)
  • 注意SQL中以1开始
  • 其他各种方法

12.3 获取自增主键值

//PreparedStatement对象获取自增是通过创建时,第二个参数指明可以获取自增键
PreparedStatement preparedStatement = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//PreparedStatement对象获取自增的第二种方法,第二个参数传入GeneratedKeys的字段名列表,字符串数组
PreparedStatement preparedStatement = con.prepareStatement(sql,new String[]{"id"});
//Statement对象获取自动是通过执行语句时,第二个参数指明可以获取自增键
statement.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
//获取自增键结果集
ResultSet rs = preparedStatement/statement.getGeneratedKeys();
//游标下移,获取数据
rs.next();
int id = rs.getInt(1)12345678910

12.4批量更新

12.4.1 批量更新的引入
  • 批处理:发送到数据库作为一个单元执行的一组更新语句
  • 批处理降低了应用程序和数据库之间的网络调用
  • 相比单个SQL语句的处理,批处理更为有效
12.4.2批量更新API
  • 添加SQL语句到数据库操作对象中
  • Statement.addBatch(String sql)
  • PreparedStatement.addBatch()
  • 把对象中语句列表中的所有SQL语句发送给数据库进行处理
  • executeBatch()
  • 清空当前SQL语句列表
  • clearBatch()
12.4.3使用批量更新注意
  • 防止OutOfMemory错误
  • 如果操作对象中待处理的SQL语句过多,可能会产生内存溢出错误
  • 一定要及时处理SQL语句列表

13、 JDBC实现事务处理

13.1 默认情况下JDBC是如何处理事务的**?

JDBC默认情况下采用自动提交机制,所谓的自动提交机制是只要在Java程序当中执行一条DML语句则提交一次。这种事务处理机制有的时候不满足我们实际的需求,实际的业务当中可能要求多条DML语句组合起来才能完成整个流程。
所以JDBC的这种自动提交机制必须改成手动提交。

13.2 什么时候提交?什么时候回滚?

当在这个业务当中所有的DML语句都执行成功的时候提交,只要其中任意一条DML语句执行失败,则回滚事务。 但无论是提交事务还是回滚事务,事务都会结束。

JDBC自动提交改为手动提交,事务处理包括以下三行核心程序?

将自动提交修改为手动提交
     connection.setAutoCommit(false); //false表示关闭自动提交机制,默认值是true

等待整个业务的执行结束,正常结束手动提交
     connection.commit();

等待整个业务的执行结束,假设其中一个DML语句出现了异常,为了保证数据的安全,
   建议手动回滚,在catch语句块当中捕获异常之后,执行以下代码进行回滚:
     connection.rollback();

注意:事务只和DML语句有关系。

13.3 JDBC事务实现

Connection conn = null;
try {
    conn = DriverManager.getConnection(".", "", "");
    boolean autoCommit = conn.getAutoCommit();//获取自动提交状态
    conn.setAutoCommit(false);//设定自动提交
    //...执行SQL语句
    conn.commit();//提交事务
    conn.setAutoCommit(autoCommit);//还原自动提交状态
} catch (SQLException e) {
    if (conn == null) {
        try {
            conn.rollback();//当发生错误,回滚事务
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
}

13.4 设置隔离级别

JDBC中通过Connection提供的方法设置事务隔离级别

Connection. setTransactionIsolation(int level)
//可选参数
Connection.TRANSACTION_READ_UNCOMMITTED        //1(读未提交数据)
Connection.TRANSACTION_READ_COMMITTED        //2(读已提交数据)
Connection.TRANSACTION_REPEATABLE_READ        //4(可重复读)
Connection.TRANSACTION_SERIALIZABLE            //8(串行化)
Connection.TRANSACTION_NONE                    //0(不使用事务)

14、行级锁的使用

1. 行级锁
* 行级锁又被称为悲观锁
* 在当前事务没有结束之前,被锁定的记录(精确到表中的行)其它事务是无法修改的
* 当然,没有被锁定的行,是可以被其它事务修改的
* 行级锁仅对索引列有作用,否则为表级锁。

怎么实现行级锁呢?

conn.setAutoCommit(false);
 ....
 String sql = "select ename,sal from emp where job='MANAGER' for update";
 ....
 conn.commit();
 conn.rollback();