Java学习笔记——JDBC

  • JDBC基础
    1. 持久化:把数据存到可掉电式存储设备中以供以后使用
    2. JDBC:Java Database Connectivity,独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口
      可以将其理解为SUN公司提供的一套API,可以实现对具体数据库的操作(获取连接、关闭连接、DML、DDL、DCL)
    3. 使用JDBC带来的好处
      • 面向应用的API:Java API,供开发人员使用的抽象接口,不需关注具体的数据库细节
      • 面向数据库的API:Java Driver API,供开发商开发数据库驱动的接口,只需提供标准接口的实现即可
    4. 将JDBC所需jar包引入maven工程
            
                mysql
                mysql-connector-java
                8.0.18
            
    
    1. 基本的JDBC操作过程
      • 获取连接的几种方法(Connection的使用)
        1. 通过Driver类获取
                //1. 创建一个Driver实现类的对象
                Driver driver = new com.mysql.cj.jdbc.Driver();
                //2. 准备连接数据库的基本信息:url、user、password
                String url = "jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC";
                Properties info = new Properties();
                info.put("user", "xxxx");
                info.put("password", "xxxxxxxx");
                //3. 调用Driver接口的connect(url,info)获取数据库连接
                Connection connection = driver.connect(url, info);
        
        1. 读取配置文件,通过DriverManager创建数据库连接
            public static Connection getConnection() {
                //1. 初始化字符串存储driver、url、user、password等必要信息
                String driver = null;
                String url = null;
                String user = null;
                String passwd = null;
        
                //2. 创建Properties对象,将一个文件通过输入流绑定到该对象
                Properties properties = null;
                try {
                    properties = new Properties();
                    InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
                    properties.load(is);
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                //3. 通过绑定后的Properties对象,获取user等信息的真实值
                driver = properties.getProperty("driver");
                url = properties.getProperty("jdbcUrl");
                user = properties.getProperty("user");
                passwd = properties.getProperty("password");
        
                //4. 注册数据库驱动
                Connection connection = null;
                try {
                    Class.forName(driver);
                    //5. 并通过DriverManager获取数据库连接
                    connection = DriverManager.getConnection(url, user, passwd);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
        
                return connection;
            }
        
      • 简单的更新操作(Statement的使用)
              //1.获取数据库连接
              //2. 准备数据插入sql语句
              String sql = "INSERT INTO Data" +
                      "(id, name, email, birth)" +
                      "VALUES" +
                      "(1,'hkk', '@cdc.com', '1988-08-10')";
              //3. 执行插入操作:Statement + executeUpdate
              Statement statement = null;
              try {
                  statement = connection.createStatement();
                  statement.executeUpdate(sql);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
      
      • 简单的查询操作 (ResultSet的使用)
              //1. 获取数据库连接
              //2. 获取Statement对象
              Statement stat = conn.createStatement();
              String sql = "SELECT * FROM data";
              ResultSet rs = null;
              try {
                  //3. 执行查询
                  rs = stat.executeQuery(sql);
                  //4. 处理ResultSet
                  while (rs.next()) {
                      int id = rs.getInt("id");
                      String name = rs.getString("name");
                      String email = rs.getString("email");
                      String birth = rs.getString("birth");
                      System.out.println("id=" + id + " name=" + name + " email=" + email + " birth=" + birth);
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
      
      • 释放对数据库连接的占用
      public static void closeConnection(Connection conn, Statement stat, ResultSet rs) {
              try {
                  if (rs != null)
                      rs.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (stat != null)
                      stat.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (conn != null)
                      conn.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
  • PreparedSatement的使用
    1. Statement存在的问题:
      • 拼接sql语句十分繁琐
      • 存在sql注入问题
      • Statement无法操作BLOB类型变量
      • Statement批量插入时候效率较低
    2. PreparedStatement有关概念
      • 其本身是Statement的一个子接口
      • 其本质是一个预编译的SQL语句
    3. 使用PreparedStatement实现基本的增删改查操作
      • 使用Statement语句,需要拼接sql语句,繁琐易错
      • 本质上是Statement的子接口,传入的sql语句中,带有占位符,顺带控制了sql注入
      • 构造初始sql语句,String sql = “INSERT INTO xxx VALUES(???)”
      • 通过connection获取preparedStatement对象
      • 调用PreparedStatement的setXxxx(int index, Object val)
      • 执行数据库操作
      • 关闭数据库连接
      public void test1(){
              //1. 初始化sql语句
              String sql = "INSERT INTO student(id, name, school) VALUES (?,?,?)";
              //2. 通过connection获取PreparedStatement对象
              Connection connection = null;
              PreparedStatement ps = null;
              try {
                  connection = JDBCTools.getConnection();
                  ps = connection.prepareStatement(sql);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              //3. 通过PreparedStatement对象执行数据库操作
              try {
                  ps.setObject(1, "0000000000");
                  ps.setObject(2, "fmr");
                  ps.setObject(3,"CQUPT");
                  ps.executeUpdate();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              //3. 关闭有关连接
              try {
                  if(ps != null)
                      ps.close();
                  if(connection != null)
                      connection.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
    4. 使用PreparedStatement操作Blob对象
      • 若无法写入Blob对象,需修改MySQL配置文件my.ini,添加参数max_allowed_packet = 16M
      • 代码示例
          /*
              向数据库插入BLOB数据
              1. 建立数据库连接、PreparedStatement对象
              2. 填充sql占位符,在这个过程中插入BLOB数据
              3. 执行update操作
          */
          @Test
          public void testBlobToDB() {
              Connection conn = null;
              PreparedStatement ps = null;
              String sql = "INSERT INTO pictures(owner, pict) VALUES(?,?)";
      
              try {
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
                  ps.setObject(1, "fmr");
                  File f = new File("C:\\Users\\fmr\\Pictures\\memories\\xxx.png");
                  InputStream in = new FileInputStream(f);
                  ps.setBlob(2, in, (int)f.length());
              } catch (SQLException e) {
                  e.printStackTrace();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              }
      
              try {
                  ps.executeUpdate();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
      
              try {
                  if (ps != null)
                      ps.close();
                  if (conn != null)
                      conn.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          /*
              从数据库获取BLOB数据
              1.在ResultSet中,使用getBlob()方法获取BLOB对象
              2. 调用BLOB的getBinaryStream()方法得到输入流
              3. 构建输出流,将读取的BLOB写入特定的文件
          */
          @Test
          public void testBlobFromDB() {
              Connection conn = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
              String sql = "SELECT owner, pict FROM pictures";
              FileOutputStream fos = null;
              InputStream in = null;
              try {
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  rs = ps.executeQuery();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (rs.next()) {
                      //1.在ResultSet中,使用getBlob()方法获取BLOB对象
                      Blob p = rs.getBlob(2);
                      //2. 调用BLOB的getBinaryStream()方法得到输入流
                      in = p.getBinaryStream();
                      //3. 构建输出流,将读取的BLOB写入特定的文件
                      fos = new FileOutputStream("C:\\Users\\fmr\\Desktop\\xxx.png");
                      byte[] buffer = new byte[1024];
                      int length;
                      while ((length = in.read(buffer)) != -1) {
                          fos.write(buffer, 0, length);
                      }
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              try {
                  if (fos != null)
                      fos.close();
                  if(in != null)
                      in.close();
                  if(rs!= null)
                      rs.close();
                  if(ps != null)
                      ps.close();
                  if(conn != null)
                      conn.close();
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
  • DAO的使用
    1. DAO指的是访问数据信息的类,包含了对数据的CRUD,而不包含任何业务逻辑
    2. JavaBean:
      1. 在JavaEE中,Java类的属性是通过getter、setter方法定义的。去掉get、set后,
        将首字母小写,即获得属性名称
      2. JavaSE中说的属性,即成员变量,称之为字段
      3. 一般字段名和属性名是一致的
      4. 操作Java类的属性有一个工具包:beanutils
      5. 使用BeanUtil工具,可以替换DAO类的query方法中,用反射为类属性赋值的操作
    3. 元数据:
      • 描述数据的数据,可从中获取结果集中有多少列,列名是什么等与数据表本身有关的信息
      • 调用ResultSet的getMetaData()方法,获得ResultSetMetaData对象
      • int getColumnCount():sql语句中包含哪些列
      • String getColumnLabel(int column):获取指定的列的别名,索引从1开始
    4. 两种数据库操作编程思想
      • 面向接口编程:开发者不关心具体实现,只需调用格式统一的接口即可
      • ORM编程思想(Object Relational Mapping):
        1. 一个数据表对应一个Java类
        2. 数据表中的一条记录对应Java类的一个对象
        3. 数据表中的一个字段对应Java类的一个属性
    5. 两种重要技术的使用
      • ResultSet的使用,注意:如何sql语句中没有指定列别名,那么getColumnLabel()返回的就是列名
      • 反射技术的使用,为返回的类对象设置属性值
        1. 创建对应的运行时类对象
        2. 在运行时动态地调用指定的运行时类的属性或者方法
    6. 使用JDBC编写DAO可能包含的方法:
      • void update(String sql, Object ... args);//对应INSERT、UPDATE、DELETE操作
      • T get(String sql, Object .. args, Class clazz)//查询一条记录
      • List getForList(String sql, Object ... args, Class clazz)//查询多条记录,以集合形式返回
      • E getForValue(String sql, Object ... args)//返回某条记录某个字段的值或者一个统计值
    7. 自定义DAO的查询方法的实现 (将泛型与PreparedStatement结合使用)
      • 获取Connection
      • 获取PreparedStatement
      • 填充占位符
      • 进行查询,得到ResultSet
      • 通过ResultSet获取ResultSetMetaData
      • 准备一个Map,key:列别名,value:列值
      • 通过ResultSetMetaData得到结果集中有多少列
      • 通过ResultSetMetaData得到结果集中各列的列别名
      • 通过ResultSet得到列别名对应的列值
      • 用反射创建Class对应的对象
      • 遍历Map,使用反射将Map中的value,赋给类属性
      • 返回类对象
    8. DAO中查询方法示例
        //查询一条记录,返回对应的对象
        public  T get(Class clazz, String sql, Object ... args ){
            T entity = null;
            Connection conn = null;
            PreparedStatement ps = null;
            try {
                //1. 获取COnnection
                conn = JDBCTools.getConnection();
                //2. 获取PreparedStatement
                ps = conn.prepareStatement(sql);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //3. 填充SQL语句中的占位符
            try {
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i+1, args[i]);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //4. 进行查询操作,得到ResultSet
            ResultSet rs = null;
            ResultSetMetaData rsmd = null;
            try {
                rs = ps.executeQuery();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //5. 准备Map 列别名:列的值
            Map tmp = new HashMap();
            //6. 获取ResultSetMetaData
            try {
                if(rs.next()){
                    rsmd = rs.getMetaData();
                    //7. 根据ResultSetMetaData对象,获取列数,这个列数是从1开始的
                    for (int i = 0; i < rsmd.getColumnCount(); i++) {
                        //8. 逐个获取列别名、列的值,并写入Map
                        String columnLabel = rsmd.getColumnLabel(i+1);
                        Object columnValue = rs.getObject(columnLabel);
                        tmp.put(columnLabel, columnValue);
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //9. 根据Map中的列别名、列的值,调用反射为返回的类对象赋值,注意,该类必须有默认无参构造器
            for (Map.Entry entry:tmp.entrySet()){
                ReflectionUtils.setFieldValue(entity, entry.getKey(), entry.getValue());
            }
            //10. 关闭相关连接,并返回对象
            try {
                if(ps!= null)
                    ps.close();
                if(conn != null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return entity;
        }
        static void setFieldValue(Object obj, String fieldName, Object fieldValue ){
            try {
                Class clazz = obj.getClass();
                Field f = clazz.getDeclaredField(fieldName);
                f.setAccessible(true);
                f.set(obj, fieldValue);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
  • 数据库批量操作的实现
    1. 层次一:Statement实现
    2. 层次二:PreparedStatement替换Statement实现
    3. 层次三:使用addBatch()/executeBatch()/clearBatch()
    4. 层次四:使用batch的同时关闭自动提交
    5. batch的代码示例
        public static void main(String[] args) {
            Connection conn = null;
            String sql = "INSERT INTO testjob(id, name, banlance)VALUES (?,?,?)";
            PreparedStatement ps = null;
            try {
                conn = JDBCTools.getConnection();
                ps = conn.prepareStatement(sql);
                JDBCTools.beginTransition(conn);
                for (int i = 1; i <= 1000; i++) {
                    ps.setObject(1, i + 2);
                    ps.setObject(2, "fmr");
                    ps.setObject(3, (double) i + 2);
                    ps.addBatch();
                    if(i % 10 == 0){
                        ps.executeUpdate();
                        ps.clearBatch();
                    }
                }
                //conn.commit();
                JDBCTools.commit(conn);
            } catch (SQLException e) {
                e.printStackTrace();
                try {
                    if(conn != null) {
                        conn.rollback();
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            JDBCTools.releaseConnection(null,ps,null,conn);
        }
        
        public static void beginTransition(Connection conn){
            try {
                conn.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        public static void releaseConnection(ResultSet rs,PreparedStatement ps, Statement stat, Connection conn) {
            try {
                if (rs != null)
                    rs.close();
                if(ps != null)
                    ps.close();
                if (stat != null)
                    stat.close();
                if (conn != null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
  • 数据库事务
    1. 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
    2. 为确保数据库中数据的一致性,数据操纵应该是离散的成组的逻辑单元,
      全部逻辑单元完成才算该事务成功,有任何一个逻辑单元失败就算该事务全部失败
      所有从起始点以后的操作应该全部回退到开始状态
    3. 提交(COMMIT):开始一个事务,对数据进行操作,提交后,这些操作就永久保存下来
    4. 回退(ROLLBACK):DBA放弃所作的全部修改而回到事务开始时的状态
    5. 事务的ACID属性
      • 原子性(Atomic):事务不可分割,一个事务中的操作要么都发生要么都不发生
      • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态
      • 隔离性(Isolation):并发的各个事务之间不能相互干扰,
        一个事务内部的操作及所用的数据对并发的其他事务是隔离的
      • 持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变就是永久性的,
        接下来的其他操作和数据库故障对其没有任何影响
    6. 数据库读写中可能出现的问题
      • 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段之后,
        若T2回滚,则T1读取的内容就是临时且无效的
      • 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了该字段,
        之后,T1再次读取同一个字段,值就不同了
      • 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,
        然后T2在该表中插入了一些新的行,之后,若T1再次读取同一个表,就会多出几行
    7. 数据库提供的4种事务隔离级别
      • READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更。
        脏读、不可重复读、幻读的问题都会出现
      • READ COMMITTED(读已提交数据):只允许事务读取已被其他事务提交的变更
        可以避免脏读,但不可重复读、幻读的问题依然存在
      • REPEATABLE READ(可重复读):确保事务可以多次从一个字段种读取相同的值,在该事务持续期间,
        禁止其他事务对该字段进行更新。可以避免脏读、不可重复读,但幻读问题依然存在
      • SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,
        禁止其他事务对该表执行插入、删除、更新操作。所有并发问题都可以解决,但性能很差
      • 数据库系统必须具有隔离并发运行各个事务的能力,使他们之间互不影响
      • 一个事务与其他事务的隔离的程度称为隔离级别。隔离性越高,数据一致性越好,并发性能越差
    8. 在数据库中设置隔离级别
      • Oracle数据库支持2种隔离级别:READ COMMITED \ SERIALIZABLE,前者为默认值
      • MySQL数据库支持4种隔离级别,默认为REPEATABLE READ
    9. 将事务应用到数据库中的操作
      • 获取数据库连接(手动获取连接、通过数据库连接池获取连接)
      • 将一个或多个DML操作,作为一个事务出现
        1. 手动使用PreparedStatement实现sql操作
        2. 使用dbUtils中的QueryRunner类
      • 提交事务
      • 若出现异常,在try-catch中rollback
      • 关闭资源
    10. 数据库事务的代码示例
        public void transaction(double accountA, double accountB){
            Connection conn = null;
            PreparedStatement psA = null;
            PreparedStatement psB = null;
            String sqlA = "UPDATE testjob SET banlance = banlance - ? WHERE name = 'Tom'";
            String sqlB = "UPDATE testjob SET banlance = banlance + ? WHERE name = 'Jerry'";
    
            try {
                conn = JDBCTools.getConnection();
                conn.setAutoCommit(false);
                psA = conn.prepareStatement(sqlA);
                psB = conn.prepareStatement(sqlB);
                psA.setObject(1, accountA);
                psB.setObject(1, accountB);
                psA.executeUpdate();
                psB.executeUpdate();
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally {
                try {
                    if(psA != null)
                        psA.close();
                    if(psB != null)
                        psB.close();
                    if(conn != null)
                        conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
  • 数据库连接池
    1. 引入数据库连接池的好处在于:
      • 对数据库连接这一宝贵资源,做到了重复利用,使用完无需关闭放回线程池就好
      • 不用每次使用数据库连接,都要创建-使用-关闭,提高了程序响应速度
      • 能够自由控制数据库连接的数量,便于管理连接
    2. 数据库连接池的使用
      • C3P0:开源第三方提供,速度慢但是相对稳定
      • DBCP:Apache提供的数据库连接池实现,快但不稳定
        1. 导入依赖
            
                commons-dbcp
                commons-dbcp
                1.4
            
        
        1. 代码示例
            @Test//将用户名、密码等属性写成一个配置文件
            public void testDBCPOnFile(){
                Properties pros = new Properties();
                //InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("DBCP.properties");
                try {
                    InputStream in = new FileInputStream(new File("src/main/resources/DBCP.properties"));
                    pros.load(in);
                    DataSource source = BasicDataSourceFactory.createDataSource(pros);
                    Connection conn = source.getConnection();
                    System.out.println(conn);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        
      • Druid:阿里提供的开源连接池,兼具DBCP、C3P0的优点
        1. 导入依赖
            
                com.alibaba
                druid
                1.0.9
            
        
        1. 代码示例
            @Test
            public void testOnFile(){
                try {
                    Properties pros = new Properties();
                    InputStream in = new FileInputStream(new File("src/main/resources/Druid.properties"));
                    pros.load(in);
                    DataSource source = DruidDataSourceFactory.createDataSource(pros);
                    Connection conn = source.getConnection();
                    System.out.println(conn);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        
  • DBUtils的使用
    1. 导入依赖
        
            commons-dbutils
            commons-dbutils
            1.6
        
    
    1. 代码示例
        @Test
        public void test1() {
            QueryRunner queryRunner = null;
            Connection connection = null;
            try {
                queryRunner = new QueryRunner();
                connection = JDBCTools.getConnection();
                String sql = "INSERT INTO testjob(id,name, banlance) VALUES(?,?,?)";
                int insertCount = queryRunner.update(connection, sql, "2015211211", "gaaag", "1500.0");
                System.out.println(sql);
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCTools.closeConnection(connection, null);
            }
        }
    

你可能感兴趣的:(Java学习笔记——JDBC)