JDBC笔记


一、JDBC介绍

  1. 概念:Java DataBase Connectivity`

    Java数据库连接,使用Java语言操作数据库

  2. JDBC本质

    Java官方(Sun)定义的一套操作所有关系型数据库的规则。各数据库厂商实现者套接口,提供数据库驱动jar包。程序员就可以使用这套接口(JDBC)编程,真正执行代码的是驱动jar包中的实现类【多态】


二、快速入门

  1. 步骤:
    • 导入驱动jar包

    • 注册驱动【让程序识别是哪家的数据库】

      • //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // Driver 类中有一静态代码块,是对驱动的注册,保证了注册驱动的代码仅在类加载时执行唯一的一次
        static {
          try{
             DriverManager.registerDriver(new Driver());
             } catch(SQLException E) {
           throw new RuntimeException("Can't register driver!");}}
    • 获取数据库对象Connection

      • 使用DriverManager类的静态方法获取数据库的连接

      • //获取数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql:///jdbctest", "root", "admin");
        //参数:
        //     user:数据库用户名
        //     password:数据库登录密码
        //     URL:指定连接的路径
        //            MySQL连接的URL:jdbc:mysql://IP地址(域名):端口号/数据库名称【如  "jdbc:mysql://127.0.0.1:3306/jdbctest" 】
        
        //    省略写法:如果连接的是本地MySQL服务器,且端口为3306,可以省略域名与端口
    • 定义SQL【将来把SQL发送给数据库】

      • //定义一条插入语句
        String sql0 = "insert into test01(name) values('汪星人')";
        //定义一条修改语句
        String sql = "update test01 set name = '喵喵喵星人' where id = 4";
    • 获取执行SQL语句的对象Statement

      • 使用Connection类的createStatement()方法获取执行SQL语句的对象

      • Statement sta = conn.createStatement();
    • 执行SQL,接收返回结果

    • 处理结果

      • 如果是增删改语句,会返回数据库中表的受影响的行数
      • 如果是查询语句,会返回一个查询到的结果集
    • 释放资源

      • ConnctionStatement对象时从底层申请的进程资源,用完需要释放

三、JDBC中各个类与接口详解

  1. DriverManager对象

    1. 功能:

      1. 注册驱动【告诉程序应该使用哪个数据库驱动jar包】

        //DriverManager类中注册驱动的方法
        static void registerDriver(Driver driver, DriverAction da);
        
        //一般下面的方式注册  Driver类中静态代码块执行上述方法
        //方法执行。Driver类进入内存,其静态代码块执行,驱动进行注册
        Class.forName("com.mysql.jdbc.Driver");

        2.获取数据库的连接

        // 尝试建立与给定数据库URL的连接。
        static Connection getConnection(String url, String user, String password);
  2. Connection对象

    1. 功能 代表了当前代码与数据库之间的桥梁

      1. 获取SQL语句的执行对象

        Statement createStatement()
        PreparedStatement prepareStatement(String sql)
      2. 管理事务

        // 开启事务: 设置参数为false则为手动提交【为true是autoCommit 自动提交】
            void setAutoCommit(boolean autoCommit)   
        //回滚事务:  回滚并释放此 Connection对象当前持有的任何数据库锁。
            void rollback()  
        //提交事务:  提交,并释放此 Connection对象当前持有的任何数据库锁。
            void commit()  
  3. Statement对象

    1. 功能:用于执行静态的SQL语句并返回其生成的结果的对象【该对象可能会产生SQL注入问题】

    2. 主要方法

      1. 【了解】 可以执行任意语句的方法

        //返回值:  true--第一个结果为ResultSet对象   false--更新操作或者无结果
        boolean execute(String sql)
      2. 可以执行DML语句(增删改)与DDL语句【此条不常用】的方法

        //执行DML语句(增删改表数据 INSERT, UPDATE, DELETE)返回值为影响的行数
        // 执行DDL语句(增删改库、表 create、drop、alter)  --  不常用,无返回值0
        int executeUpdate(String sql)
      3. 可以执行DQL语句(查询)的方法

        // 执行DQL语句(select),该语句返回单个 ResultSet对象。
        ResultSet executeQuery(String sql)  
  4. ResultSet对象

    1. 功能:结果集对象,封装了查询到的结果

      ResultSet对象有一个游标,初始状态指向了查询的表的第一行上面一行

    2. 主要方法

      1. //将光标从当前位置向前移动一行。如果新的当前行有效,返回值true;
                //如果没有更多的行 返回false
        boolean next()
            //  next方法可以使对象的游标向下移动一行
      2. //getxxx(参数):
            //根据获取到的结果的类型不同,方法名不同
        int getInt(参数); //用于获取数据库中int类型的字段
        String getString(参数)    //用于获取数据库表中varchar类型的字段
        
         // 两种形式参数
            //2.1 int columnIndex  参数是当前行某一列的编号【从1开始,不是索引】
            // 2.2 String columnLabel:参数是当前行某一列的列名【比如 id  name  sex...】
    3. ResultSet对象操作步骤

      1. 游标向下移动一行
      2. 判断该行是否还有数据【next()的返回值】
      3. 获取数据
  5. PreparedStatement对象

    1. 功能:用于执行预编译的SQL语句并返回其生成的结果的对象

    2. 解释:

      1. 预编译的SQL语句:参数使用 ? 作为占位符 所以使用PreparedStatement对象时需要给 ? 赋值

      2. PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET SALARY = ? WHERE ID = ?");
    3. 使用步骤与Statement对象的不同之处:

      1. 定义SQL语句时:

        //SQL语句的参数使用 ? 作为占位符   比如
        String sql = "select * from user_login where userName = ? and userPassWord = ?";
      2. 获取SQL语句执行对象时:

        //与Statement对象获取有所不同 Statement对象获取:
        Statement stmt = conn.createStatement();
            //PreparedStatement对象获取  需要传递一个参数
        PreparedStatement pstmt = conn.prepareStatement(String sql);
      3. 执行SQL语句前:

        //执行SQL前,需要给占位符进行赋值,组成完整的SQL语句
        
        /*   赋值方法:void setXxx(int parameterIndex, byte x)
                  参数1:? 的位置编号【有俩 ? 】,从1开始
                  参数2: ? 的值*/
        
             // 比如:
        String sql = "select * from user_login where userName = ? and userPassWord = ?";
        
        //为占位符 ? 赋值  占位符代表的用户名与密码都是字符串,所以使用setString()方法赋值
        pstmt.setString(1, userName);
        pstmt.setString(2, userPassword);
    4. Statement对象相比的好处

      1. 可以防止SQL注入
      2. 效率更高

四、JdbcUtils工具类

  1. 概述: JDBC很多代码都是重复的,为了提高效率【懒】,定义一个工具类抽取一些重复度且相对固定的代码,简化书写

  2. 应该具有的方法:

    1. 驱动的注册
      1. 数据库连接的获取
        需求:不想动DriverManager.getConnection()方法的参数,给它放在配置文件中,保证工具的通用性
      2. 资源的释放
  3. 实现:

    public class JdbcUtils {
        //三个变量也设置为静态,让静态代码块与静态方法可以访问
        private static String url;
        private static String user;
        private static String password;
    
        /*
        期望文件的读取仅执行一次
        把得到的url  user 等信息给下面的getConnection方法参数使用,所以得提升三个变量的作用域
                使用静态代码块可以让下面这段代码在类加载时执行唯一一次
         */
        static{
            //静态代码块的形式获取配置文件的内容
            Properties prop = new Properties();
    
            //获取类加载器,  ClassLoader,本类的类加载器可以找到src下的配置文件
            ClassLoader classLoader = JdbcUtils.class.getClassLoader();
    
            // 使用getResourceAsStream方法获取配置文件路径并以输入流的形式返回
            InputStream propInputStream = classLoader.getResourceAsStream("login.properties");
    
            /*  不以流的形式返回,以统一资源定位符的形式返回,再使用getPath方法转换绝对路径为字符串
            URL res = classLoader.getResource("jdbc.properties");
            String perprotiesPath = res.getPath();*/
    
            //传递配置文件的流对象
            try {
                //读取配置文件的信息【加载到内存】并保存到Properties集合中
                prop.load(propInputStream);
    
                //把配置文件信息赋值给url user password 变量
                url = prop.getProperty("url");
                user = prop.getProperty("user");
                password = prop.getProperty("password");
                String driver = prop.getProperty("Driver");
    
                //注册驱动
                Class.forName(driver);
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }finally {
                //释放配置文件流对象资源
                try {
                    propInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static Connection getConnection() throws SQLException {
            //数据库的连接,不想在实际类与这里动代码,把用户的表信息等等放在配置信息中
            return  DriverManager.getConnection(url, user, password);
        }
    
        /**
         * 定义资源释放方法,参数为只有Statement Connection两个对象【针对DML语句,不查询】
         * @param stmt 参数传递 Statement对象
         * @param conn 参数传递 Connection对象
         */
        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();
                }
            }
        }
    
        /**
         * 资源释放的重载方法
         * 定义资源释放方法,参数为 ResultSet, Statement, Connection两个对象【针对DQL语句,查询】
         * @param rs    参数传递 ResultSet对象
         * @param stmt  参数传递 Statement对象
         * @param conn  参数传递 Connection对象
         */
        public static void close(ResultSet rs, Statement stmt, Connection conn) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            close(stmt, conn);
        }
    }
  4.          JDBC很多代码都是重复的,为了提高效率【懒】,定义工具类
             抽取出一些重复的代码。简化书写
    定义一个JDBC工具类,里面定义静态方法进行
    

五、Jdbc事务管理

  1. 事务:一个包含多个步骤的业务操作 ,如果这个业务操作被事务管理,则这个多个步骤要么同时成功,要么同时失败
  2. 操作:
    • 开启事务
    • 提交事务
    • 回滚事务
  3. 使用Jdbc的Connection对象来管理事务
    • 开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务【意即关闭自动提交】
    • 提交事务:commit()
    • 回滚事务:rollback()
    • 操作时机:在执行操作前开启事务;在全部执行完后提交事务;如果有问题,在异常处理逻辑处catch里面进行回滚

六、数据库连接池

  1. 概念
    • 每次执行都需要从系统获取Connection对象,用完还关闭,比较费时费力,效率低下
    • 其实就是一个容器【集合】,存放数据库连接,当任务访问数据库时,从容器中获取连接对象,使用完毕后归还给容器,达到复用的效果
  2. 好处
    • 节约资源
    • 用户访问高效
  3. 实现
    • 标准接口:javax.sql 包下的 interface DataSource

      • 方法:获取连接

          • 获取连接

            Connection getConnection() 尝试建立与此 DataSource对象所代表的数据源的连接。
            Connection getConnection(String username, String password) 尝试建立与该 DataSource对象所代表的数据源的连接。
          • 归还连接:如果数据库连接对象Connection是由连接池获取的,则调用Connection.close()方法不是释放资源,是归还连接对象到连接池

    • 一般不是我们自己去实现它,由数据库厂商取实现

      • C3P0:数据库连接池技术
      • Druid:数据库连接池技术【有阿里巴巴提供的】
  4. C3P0:数据库连接池技术
    • 4.1 导入jar包 :

      • c3p0-0.9.2.1.jar
      • mchange-commons-java-0.2.3.4.jar【依赖jar包。上面的jar里面的类依赖这个包】
      • 数据库驱动jar包
    • 4.2 使用方式

      • 配置文件方式
        • 定义配置文件
          • 名称:c3p0.propertied 或者 c3p0-config.xml 【不能是其他名称】
          • 路径:默认在项目类路径(classpath)下【直接将配置文件放在src目录下即可】
      • 硬编码,创建连接池、路径...,代码写死【后期维护麻烦,不推荐】
    • 4.3 创建核心对象 --- 数据库连接池对象 CombPooledDataSource

    • 4.4 获取连接:getConnection()方法【都实现了DataSource接口】

    • 4.5 实例

      //1. 获取数据库连接池
      DataSource ds = new ComboPooledDataSource();
      //2. 获取数据库连接对象
      Connection conn = null;
      PreparedStatement pstmt = null;
      try {
          conn = ds.getConnection();
          System.out.println("conn --> " + conn);
          String sql = "insert into user_login values(null, ? , ?)";
          pstmt = conn.prepareStatement(sql);
          //为通配符赋值
          //为通配符 ?  赋值
          pstmt.setString(1, "mmmm");
          //为密码赋值
          pstmt.setString(2, "mmmmmm");
          //执行SQL
          int count = pstmt.executeUpdate();
           System.out.println("受影响的行数:" + count);
      } catch (SQLException e) {
      e.printStackTrace();
      }finally {
      //3. 归还连接   ...}
  5. druid数据库连接池技术

    • 5.1 导入jar包 druid-1.0.9.jar 与数据库驱动jar包

    • 5.2 定义配置文件

      • 是properties形式的
      • 可以叫任意名称,可以放在任意目录
    • 5.3 获取数据库连接池对象:通过工厂类获取的,不是new出来的

      //找到配置文件
      ClassLoader classLoader = Demo01Druid.class.getClassLoader();
      //类加载器可以找到 druid.properties 文件,以输入流的形式返回
      InputStream druidProp = classLoader.getResourceAsStream("druid.properties");
      //创建 Properties 集合
      Properties prop = new Properties();
      //load读取druid.properties 配置文件
      prop.load(druidProp);
      //3。 获取数据库连接池对象:通过工厂类获取的
      DataSource ds = DruidDataSourceFactory.createDataSource(prop);
      //4. 获取数据库连接对象
      Connection conn = ds.getConnection();
    • 5.4 获取连接 getConnection()方法【实现了DataSource接口】

    • 5.5 使用数据库连接池定义工具类JdbcUtils

      • 1.定义一个类 JdbcUtils

      • 2.提供静态代码块加载配置文件,初始化获取数据库的连接池【一个池子就够了】

      • 3.工具类提供方法

        • 获取数据库连接对象
        • 释放资源
        • 获取连接池的方法
      • 具体实现:

        //MyJdbcUtils.java
            private static DataSource dataSource = null;
        
            //获取数据库连接池的静态代码块
            static {
                //获取类加载器,让它找到配置文件
                ClassLoader classLoader = MyJdbcUtils.class.getClassLoader();
                InputStream druidProp = classLoader.getResourceAsStream("druid.properties");
                Properties prop = new Properties();
        
                //把配置文件信息装载进入集合中
                try {
                    prop.load(druidProp);
        
                    //使用工厂类获取数据库的连接池,参数是装载了配置文件信息的Properties集合
                    dataSource = DruidDataSourceFactory.createDataSource(prop);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放资源
                    if (druidProp != null) {
                        try {
                            druidProp.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        
            /**
             *   获取数据库连接对象
             * @return 返回一个数据库的连接对象
             * @throws SQLException
             */
            public static Connection getConnection() throws SQLException {
                //获取连接池中的数据库连接对象,并返回出去
                return dataSource.getConnection();
            }
        
            /**
             *   获取数据库连接池的方法
             * @return
             */
            public static DataSource getDataSource() {
                //返回数据库连接池
                return dataSource;
            }
        
            /**
             *   归还数据库连接对象、释放资源
             * @param rs
             * @param stmt
             * @param conn
             */
            public static void close(ResultSet rs, Statement stmt, Connection conn) {
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                close(stmt, conn);
            }
            /**
             *      close方法的重载形式
             * @param stmt
             * @param conn
             */
            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();
                    }
                }
            }

七、 JDBC封装--Spring Jdbc:JdbcTemplate

  1. 概念:
    • Spring 框架对JDBC的简单封装。提供了一个JdbcTemplate对象,简化了Jdbc的开发
  2. 使用步骤
    • 导入包【5个需要的包与数据库驱动包】

      • image-20200306221834887
    • 创建JdbcTemplate对象,依赖于数据源DataSource数据库连接池

      • JdbcTemplate template = new JdbcTemplate(ds);DataSource可以通过五 中的工具类获取】
    • 调用JdbcTemplate方法完成CRUD操作

      • update():执行DML语句,对数据进行增删改,返回值为受影响的行数

      • Map<String, Object> queryForMap(String sql, Object... args):查询结果,将结果集封装为Map集合

        • 注意!!这个方法查询的结果集长度为1,不能查询多行记录,将列名作为Key,列的值作为Value。封装为一个Map集合
      • List<Map<String, Object>> queryForList(String sql):查询结果,将结果集封装为List集合

        • 表示把上一步获得的每一个Map集合【也就是独立的一条记录】作为一个元素
          封装在集合中
          • Sting类型是列名
          • Objects类型是各个列下的对应的值
          • 比如:id=1007, ename=刘备, job_id=2
      • <t> List<t> query(String sql, RowMapper<t> rowMapper):查询结果,将结果集封装为JavaBean对象</t></t></t>

        • 方法第二个参数是一个RowMapper<T>接口的实现类,可以是自己定义的,也可以是别人定义好的

          • 使用别人定义好的:使用 BeanPropertyRowMapper实现类,构造方法传递要映射的Javabean类【比如Person】以及其字节码对象【比如Person.class】,可以自动找到列的属性与数据库字段名是否匹配

            • List<Person> list = template.query(sql, new BeanPropertyRowMapper<Person>(Person.class));
      • <t> T queryForObject(String sql, Class<t> requiredType):查询结果,将结果集封装为对象【一般是基本数据类型对象】</t></t>

        • 第二个参数需要传递将来要封装的返回值的结果的类型 。比如int.class
        • 这个方法一般是用来执行聚合函数的 比如COUNT(id)