JDBC概述

  • 什么是JDBC

    • JDBC(Java DataBase Connectivity, Java数据库连接), 是一种用于执行SQL语句的Java API,为多种关系数据库提供统一访问, 它由一组用Java语言编写的类和接口组成

  • JDBC访问数据库编码步骤

    • 加载一个Driver驱动

    • 创建数据库连接(Connection)

    • 创建SQL命令发送器Statement

    • 通过Statement发送SQL命令并得到结果

    • 处理结果(select语句)

    • 关闭数据库资源ResultSet Statement Connection

  • JDBD历史版本及特征

    • JDBC 1.0

      • JDBC 1.0 随JDK1.1一起发布,JDBC操作相关的接口和类位于java.sql包中
    • JDBC 2.0

      • JDBC 2.0 API被划分为两部分:核心API和扩展API, 有两个包, 分别是java.sql包javax.sql包
      • java.sql核心API包
        • 在支持新功能方面:包括结果集可以向后滚动,批量的更新数据。另外,还提供了UNICODE字符集的字符流操作
        • 在支持SQL的数据类型方面:新增加的BLOB, CLOB,和数组接口能够是应用程序操作大块的数据类型
      • javax.sql扩展API包
        • DataSource数据源接口:
          • JDBC1.0是原来是用DriverManager类来产生一个对数据源的连接。JDBC2.0用一种替代的方法,使用DataSource的实现,代码变的更小巧精致,也更容易控制
        • Connection pooling:
          • 如果DataSource对象实现与一个支持连接池的中间层的服务器一起工作,DataSource对象就会自动的返回连接池中的连接,这个连接也是可以重复利用的
        • Distrubute transaction:
          • 在一个事务中涉及到了多个数据库服务器。获得一个用来支持分布式事务的连接与获得连接池中的连接是很相似的。同样,不同之处在于DataSource的实现上的不同,而不是在应用程序中获得连接的方式上有什么不同
        • Rowsets:
          • RowSet接口扩展了ResultSet接口。这样RowSet对象就有了ResultSet对象所有的功能。不可以滚动的ResultSet变成了可以滚动的RowSet

JDBC初识

  • 创建项目和模块, 将jar文件放入项目的lib目录中

    在这里插入图片描述

  • 给当前项目添加依赖(告诉当前项目 / 模块可以依赖jar文件中的代码)

    在这里插入图片描述

  • 点击OK

    在这里插入图片描述

  • 向部门表中添加一条数据

    package com.xxx.test1;
    
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.Statement;
    
    public class TestJDBC {
        public static void main(String[] args) throws Exception {
            /*
            向Dept表增加一条数据
            */
            //1加载驱动 Driver
            Driver driver = new com.mysql.cj.jdbc.Driver();
            //2注册驱动 DriverManager
            DriverManager.registerDriver(driver);
            //3获得链接 Connection
            /*
            user: 用户名
            password: 密码
            url: 统一资源定位符 定位我们要连接的数据库的
            1协议         jdbc:mysql
            2IP          127.0.0.1/localhost
            3端口号       3306
            4数据库名字   mydb
            5参数
            协议://ip:端口/资源路径?参数名=参数值&参数名=参数值&....
            jdbc:mysql://127.0.0.1:3306/mydb
            */
            String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
            String user = "root";
            String password = "root";
            Connection connection = DriverManager.getConnection(url, user, password );
            //4获得语句对象 Statment
            Statement statement = connection.createStatement();
            //5执行SQL语句,返回结果
            /*
            insert delete update 操作都是调用 statement.executeUpdate
            executeUpdate 返回一个int值, 代表数据库多少行数据发生了变化
            */
            String sql = "insert into dept values(50,'教学部','北京');";
            int rows = statement.executeUpdate(sql);
            System.out.println("影响数据行数为:"+rows);
            //6释放资源
            /*
            注意顺序
            后获得的先关闭,先获得的后关闭
            */
            statement.close();
            connection.close();
        }
    }
    
  • 总结

    • MySQL8中数据库连接的四个参数有两个发生了变化

      • String driver = "com.mysql.cj.jdbc.Driver";
      • String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
      • String url = ".......serverTimezone=GMT%2B8";
    • 错误1:Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc2.Driver

      • 原因:没有添加jar包或者com.mysql.jdbc2.Driver路径错误
    • 错误2:Exception in thread "main" java.sql.SQLException: No suitable driver found for jbdc:mysql://127.0.0.1:3306/stumgr

      • 原因:url错误
    • 错误3:Exception in thread "main" java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

      • 原因:用户名或者密码错误
    • 错误4:Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Duplicate entry '90' for key 'PRIMARY'

      • 原因:主键冲突
    • 错误5:Public Key Retrieval is not allowed

      • 如果用户使用 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥;可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者AllowPublicKeyRetrieval=True 参数以允许客户端从服务器获取公钥;但是需要注意的是 AllowPublicKeyRetrieval=True 可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启, 在jdbc连接添加上参数 allowPublicKeyRetrieval=true 即可,注意参数间用&
  • 驱动的加载

    • 加载数据库驱动时, 我们可以通过自己创建一个实例的方式, 然后去注册驱动

      在这里插入图片描述

    • 在查看Driver的源代码时我们发现, 该类内部有一个静态代码块, 在代码块中就是在实例化一个驱动并在驱动中心注册.

    • 静态代码块会在类进入内存时执行, 也就是说, 我们只要让该类字节码进入内存, 就会自动完成注册, 不需要我们手动去new

    • 所以我们在代码中直接使用反射, 通过Class.forName("com.mysql.jdbc.Driver"), 加载该类进入内存即可

    • 我们继续查看jar包发现, jar包中已经默认配置了驱动类的加载

      在这里插入图片描述

      • jar--META-INF--services--java.sql.Driver--com.mysql.jdbc.Driver, 在加载jar包时, 会自动读取该内容并加载驱动, 所以我们不去编写Class.forName("com.mysql.jdbc.Driver"), 程序也是可以自动完成加载驱动的
  • 结合异常处理代码

    package com.xxx.test1;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestJDBC3 {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        private static String user = "root";
        private static String password = "root";
    
        public static void main(String[] args)  {
            Connection connection=null;
            Statement statement=null;
    
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                statement = connection.createStatement();
                String sql = "insert into dept values(DEFAULT, '助教部门','北京');";
                int rows = statement.executeUpdate(sql);
                System.out.println("影响数据行数为:"+rows);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != statement){
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

JDBC完成CURD

  • 删除和修改部门信息

    package com.xxx.test1;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestJDBC4 {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        private static String user = "root";
        private static String password = "root";
    
        public static void main(String[] args)  {
            //testDelete();
            testUpdate();
        }
    
        public static void testUpdate(){
            Connection connection = null;
            Statement statement = null;
    
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                statement = connection.createStatement();
                String sql = "update dept set dname='总部',loc='北京' where deptno= 30 ";
                int rows = statement.executeUpdate(sql);
                System.out.println("影响数据行数为:"+rows);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != statement){
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void testDelete(){
            Connection connection = null;
            Statement statement = null;
    
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user,password);
                statement = connection.createStatement();
                String sql = "delete from dept where deptno = 40";
                int rows = statement.executeUpdate(sql);
                System.out.println("影响数据行数为:"+rows);
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != statement){
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 查询全部 员工信息

    package com.xxx.test1;
    
    import java.sql.*;
    
    public class TestJDBC5 {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        private static String user = "root";
        private static String password = "root";
        
        public static void main(String[] args)  {
            testQuery();
        }
    
        public static void testQuery(){
            Connection connection = null;
            Statement statement = null;        
            ResultSet resultSet = null;
    
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                statement = connection.createStatement();
                String sql = "select * from emp";
                resultSet = statement.executeQuery(sql);
    
                while(resultSet.next()){
                    int empno = resultSet.getInt("empno");
                    String ename = resultSet.getString("ename");
                    String job = resultSet.getString("job");
                    int mgr = resultSet.getInt("mgr");
                    Date hiredate = resultSet.getDate("hiredate");
                    double sal = resultSet.getDouble("sal");
                    double comm = resultSet.getDouble("comm");
                    int deptno = resultSet.getInt("deptno");
                    System.out.println(""+empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno);
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != resultSet){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } 
                }
    
                if(null != statement){
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 总结

    • ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。

    • 我们如果想要取得某一条记录,就要使用ResultSetnext()方法, 如果我们想要得到ResultSet里的所有记录,就应该使用while循环

    • ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行

    • 初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面

      方法名 说明
      boolean next() 将光标从当前位置向下移动一行
      boolean previous() 游标从当前位置向上移动一行
      void close() 关闭ResultSet对象
      int getInt(int colIndex) 以int形式获取结果集当前行指定列号值
      int getInt(String colLabel) 以int形式获取结果集当前行指定列名值
      float getFloat(int colIndex) 以float形式获取结果集当前行指定列号值
      float getFloat(String colLabel) 以float形式获取结果集当前行指定列名值
      String getString(int colIndex) 以String形式获取结果集当前行指定列号值
      String getString(String colLabel) 以String形式获取结果集当前行指定列名值
  • 为什么将结果封装成对象或者对象集合?

    • java 是面向对象的编程语言, java 中所有的数据处理都是基于面向对象的编码风格实现的, 让数据以符合 java 风格的形式存在, 便于对数据的后续处理

    • ResultSet 集合虽然可以存放数据, 但是它是 JDBC 中查询数据的一种手段, 是一种数据的临时存储方案, 使用完毕是要进行释放和关闭

  • 封装后台查询数据并在前台显示

    在这里插入图片描述

  • 如何将结果集中的数据在java中进行存储和传递?

    • 准备和数据库表格相对应的一个实体类, 用于封装结果集中的每一条数据, 数据库表格中的每一个字段就是实体类的一个属性, 实体类的一个对象就可以用于存储数据库表中的一条记录

    • 准备实体类:

      package com.xxx.entity
      
      import java.io.Serializable;
      import java.util.Date;
      
      /*
      实体类:
      和数据库表格名称和字段是一一对应的类
      该类的对象主要用处是存储从数据库中查询出来的数据
      除此以外, 该类没有任何的其他功能
      
      要求:
      类名和表名保持一致
      属性个数和数据库的表的列数保持一致
      属性的数据类型和列的数据类型保持一致
      属性名和数据库表格的列名要保持一致
      所有的属性必须都是私有的 (出于安全考虑)
      实体类的属性推荐写成包装类
      日期类型推荐写成 java.util.Date
      所有的属性都要有get和set方法
      必须具备空参构造方法
      实体类应当实现序列化接口 (mybatis缓存  分布式需要)
      实体类中其他构造方法可选
      */
      public class Emp implements Serializable {
          private Integer empno;    
          private String ename;    
          private String job;    
          private Integer mgr;    
          private Date hiredate;    
          private Double sal;    
          private Double comm;    
          private Integer deptno;
      
          @Override
          public String toString() {
              return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + '}';
          }
      
          public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double comm, Integer deptno) {
              this.empno = empno;        
              this.ename = ename;        
              this.job = job;        
              this.mgr = mgr;        
              this.hiredate = hiredate;        
              this.sal = sal;        
              this.comm = comm;        
              this.deptno = deptno;
          }
      
          public Emp(){}
      
          public Integer getEmpno() {
              return empno;
          }
      
          public void setEmpno(Integer empno) {
              this.empno = empno;
          }
      
          public String getEname() {
              return ename;
          }
      
          public String getJob() {
              return job;
          }
      
          public void setJob(String job) {
              this.job = job;
          }
      
          public Integer getMgr() {
              return mgr;
          }
      
          public void setMgr(Integer mgr) {
               this.mgr = mgr;
          }
      
          public Date getHiredate() {
              return hiredate;
          }
      
          public void setHiredate(Date hiredate) {
              this.hiredate = hiredate;
          }
      
          public Double getSal() {
              return sal;
          }
      
          public void setSal(Double sal) {
              this.sal = sal;
          }
      
          public Double getComm() {
              return comm;
          }
      
          public void setComm(Double comm) {
              this.comm = comm;
          }
      
          public Integer getDeptno() {
              return deptno;
          }
      
          public void setDeptno(Integer deptno) {
              this.deptno = deptno;
          }
      }
      
    • 使用实体类封装结果集

      package com.xxx.test1;
      
      import com.msb.entity.Emp;
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.List;
      
      public class TestJDBC5 {
          private static String driver = "com.mysql.cj.jdbc.Driver";
          private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
          private static String user = "root";
          private static String password = "root";
      
          public static void main(String[] args)  {
              List<Emp> emps = testQuery();
              // 遍历集合
              for (Emp emp : emps) {
                  System.out.println(emp);
              }
          }
      
          public static List<Emp>  testQuery(){
              Connection connection = null;
              Statement statement = null;
              ResultSet resultSet = null;
      
              List<Emp> list =null;
              try{
                  Class.forName(driver);
                  connection = DriverManager.getConnection(url, user, password);
                  statement = connection.createStatement();
                  String sql = "select * from emp";
                  resultSet = statement.executeQuery(sql);
      
                  list = new ArrayList<>();
                  while(resultSet.next()){
                      int empno = resultSet.getInt("empno");
                      String ename = resultSet.getString("ename");
                      String job = resultSet.getString("job");
                      int mgr = resultSet.getInt("mgr");
                      Date hiredate = resultSet.getDate("hiredate");
                      double sal = resultSet.getDouble("sal");
                      double comm = resultSet.getDouble("comm");
                      int deptno = resultSet.getInt("deptno");
                      Emp emp =new Emp(empno, ename, job, mgr, hiredate, sal, comm, deptno);
                      list.add(emp);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  if(null != resultSet){
                      try {
                          resultSet.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != statement){
                      try {
                          statement.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != connection){
                      try {
                          connection.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
              return list;
          }
      }
      

SQL注入攻击

  • 概述

    • SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统

    • 以模拟登录为例: 在前台输入用户名和密码,后台判断信息是否正确,并给出前台反馈信息,前台输出反馈信息

    • 具体实验步骤如下:

      • 创建数据库表

        在这里插入图片描述

      • 创建实体类
        public class Account implements Serializable {
            private int aid;
            private String username;
            private String password;
            private int money;
        }
        
      • 测试代码
        package com.xxx.test2;
        
        import com.xxx.entity.Account;
        import com.xxx.entity.Emp;
        
        import java.sql.*;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.Scanner;
        
        public class TestInjection {
            private static String driver = "com.mysql.cj.jdbc.Driver";
            private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
            private static String user = "root";
            private static String password = "root";
        
            public static void main(String[] args) {
                Scanner sc = new Scanner(System.in);
                System.out.println("请输入用户名");
                String username = sc.next();
                System.out.println("请输入密码");
                String pwd = sc.next();
        
                Account account = getAccount(username, pwd);
                System.out.println(null != account ? "登录成功" : "登录失败");
                sc.close();
            }
        
            public static Account getAccount(String username, String pwd){
                Connection connection = null;
                Statement statement = null;
                ResultSet resultSet = null;
                Account account = null;
        
                try{
                    Class.forName(driver);
                    connection = DriverManager.getConnection(url, user, password);
                    statement = connection.createStatement();
                    String sql = "select * from account where username ='"+username+"' and password ='"+pwd+"'";
                    System.out.println(sql);
                    resultSet = statement.executeQuery(sql);
        
                    while(resultSet.next()){
                        int aid = resultSet.getInt("aid");
                        String usernamea = resultSet.getString("username");
                        String pwda = resultSet.getString("password");
                        double money = resultSet.getDouble("money");
                        account = new Account(aid, usernamea, pwda, money);
                        System.out.println(account);
                    }
                } catch (Exception e){
                    e.printStackTrace();
                } finally {
                    if(null != resultSet){
                        try {
                            resultSet.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
        
                    if(null != statement){
                        try {
                            statement.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
        
                    if(null != connection){
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
                }
                return account;
            }
        }
        
      • 测试结果为:

        在这里插入图片描述

      • 解决办法
        • 当输入了精心设计的用户名密码后,即使是错误的,也能登录成功。让登录功能形同虚设。这是为什么呢,这就是SQL注入风险,原因在于SQL语句是字符串拼接的。
        • SQL语句中拼接的内容破坏了SQL语句原有的判断逻辑
        • 如何解决呢?
          • 使用 PreparedStatement 预编译语句对象就可以解决掉

预编译语句对象

  • 使用预编译语句对象防止注入攻击

    package com.xxx.test2;
    
    import com.msb.entity.Account;
    
    import java.sql.*;
    import java.util.Scanner;
    
    public class TestInjection2 {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        private static String user = "root";
        private static String password = "root";
    
        public static void main(String[] args) {
            Scanner sc =new Scanner(System.in);
            System.out.println("请输入用户名");
            String username = sc.next();
            System.out.println("请输入密码");
            String pwd = sc.next();
    
            Account account = getAccount(username, pwd);
            System.out.println(null != account ? "登录成功" : "登录失败");
            sc.close();
        }
    
        public static Account getAccount(String username, String pwd){
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
    
            Account account = null;
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                /*
                使用PreparedStatement语句对象防止注入攻击
                PreparedStatement 可以使用 ? 作为参数的占位符
                使用 ? 作为占位符, 即使是字符串和日期类型, 也不使用单独再添加 ''
                connection.createStatement(); 获得的是普通语句对象 Statement
                connection.prepareStatement(sql); 可以获得一个预编译语句对象 PreparedStatement
                如果 SQL 语句中有 ? 作为参数占位符号, 那么要在执行CURD之前先设置参数
                通过 set***(问号的编号, 数据) 方法设置参数
                */
                String sql = "select * from account where username = ? and password = ?";
                preparedStatement = connection.prepareStatement(sql); //这里已经传入SQL语句
    
                //设置参数
                preparedStatement.setString(1, username);
                preparedStatement.setString(2, pwd);
    
                //执行CURD
                resultSet = preparedStatement.executeQuery(); //这里不需要再传入SQL语句
    
                while(resultSet.next()){
                    int aid = resultSet.getInt("aid");
                    String usernamea = resultSet.getString("username");
                    String pwda = resultSet.getString("password");
                    double money = resultSet.getDouble("money");
                    account = new Account(aid, usernamea, pwda, money);
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != resultSet){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){                
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return account;
        }
    }
    
  • prepareStatment对象set*** 方法上, 会对单引号进行转译处理, 也就是说, ? 中的数据的单引号 会被转义成 \’, 这样就单引号就不会破坏sql语句的结构

    • SELECT * FROM users WHERE userName = ? AND password = ?

    • preparedStatement.setString(1, "xiaoming");

    • preparedStatement.setString(2,'anything' OR 'x'='x');

    • 会被转义为

      • SELECT * FROM users WHERE userName = 'xiaoming' AND password = 'anything\' OR\'x\'=\'x\''
    • 而不是

      • SELECT * FROM users WHERE userName = 'xiaoming' AND password = 'anything' OR 'x'='x'
    • 说白了就是把值当中的所有单引号给转义了 --> 这就达到了防止sql注入的目的,说白了mysql驱动的PreparedStatement实现类的setString(); 方法内部做了单引号的转义, 而Statement不能防止sql注入, 就是因为它没有把单引号做转义,而是简单粗暴的直接拼接字符串, 所以达不到防止sql注入的目的

  • 预编译

    • 当客户端发送一条sql语句给DBMS时, MySQL的执行流程如下图

      在这里插入图片描述
  • sql命令的执行流程如下

    • 客户端向服务器端发送SQL命令

    • 服务器端连接模块连接并验证

    • 缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行

    • 解析器解析SQL为解析树,如果出现错误,报SQL解析错误。如果正确,向下传递

    • 预处理器对解析树继续处理,处理成新的解析树

    • 优化器根据开销自动选择最优执行计划,生成执行计划

    • 执行器执行执行计划,访问存储引擎接口

    • 存储引擎访问物理文件并返回结果

    • 如果开启缓存,缓存管理器把结果放入到查询缓存中

    • 返回结果给客户端

  • 总结

    • 客户发送一条SQL语句给DBMS后,DBMS总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多

    • 预编译语句 PreparedStatementjava.sql 中的一个接口,它是 Statement 的子接口。通过 Statement 对象执行SQL语句时,需要将SQL语句发送给DBMS,由 DBMS 首先进行编译后再执行。预编译语句和 Statement 不同,在创建 PreparedStatement 对象时就指定了SQL语句,该语句立即发送给 DBMS 进行编译。当该编译语句被执行时,DBMS 直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译。预编译的SQL语句处理性能稍微高于普通的传递变量的办法

    • 例如: 我们需要执行多次 insert 语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高

  • 预编译如何开启?

    • 我们可以通过设置URL中的参数来控制预编译是否开启

    • useServerPrepStmts 是否开启预编译

    • cachePrepStmts 是否启用预编译缓存

  • Statement和PreparedStatment的关系和区别

    • 关系

      • public interface PreparedStatement extends Statement
    • 区别

      • PreparedStatment安全性高, 可以避免SQL注入
      • PreparedStatment简单不繁琐,不用进行字符串拼接
      • PreparedStatment性能高,用在执行多个相同数据库DML操作时, 可以减少sql语句的编译次数

PrepareStatement完成CURD

  • 代码展示

    package com.xxx.test3;
    
    import com.xxx.entity.Emp;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestPreparedSstatement {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        private static String user = "root";
        private static String password = "root";
        public static void main(String[] args) {
            //testAdd();
            //testUpdate();
            //testDelete();
            testQuery();
        }
    
        public static void testAdd(){
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
    
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user,password);
                String sql = "insert into emp values(DEFAULT ,?,?,?,?,?,?,?)";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
    
                //设置参数
                preparedStatement.setString(1,"Mark");
                preparedStatement.setString(2,"MANAGER" );            preparedStatement.setInt(3,7839);            
                preparedStatement.setDate(4,new Date(System.currentTimeMillis()));            
                preparedStatement.setDouble(5,3000.12);            
                preparedStatement.setDouble(6,0.0);            
                preparedStatement.setDouble(7,30);
    
                //执行CURD
                int rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
                System.out.println(rows);
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(null != preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if(null != connection){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void testUpdate(){
        // 根据工号修改员工表中的数据
        Connection connection = null;
        PreparedStatement preparedStatement = null;
    
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user,password);
            String sql = "update emp set ename =? ,job=? where empno =?";
            preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
    
            //设置参数
            preparedStatement.setString(1,"Jhon");
            preparedStatement.setString(2,"ANALYST" );
            preparedStatement.setInt(3,7935);
    
            //执行CURD
            int rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            System.out.println(rows);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(null != preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if(null != connection){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    } 
    
    public static void testDelete(){
        // 根据工号删除员工表中的数据
        Connection connection = null;
        PreparedStatement preparedStatement = null;
    
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password);
            String sql = "delete from emp where empno =?";
            preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
            //设置参数
            preparedStatement.setInt(1, 7935);
    
            //执行CURD
            int rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            System.out.println(rows);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(null != preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if(null != connection){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void testQuery(){
        // 查询名字中包含字母A的员工信息
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
    
        List<Emp> list = null;
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password); 
            /*
            使用PreparedStatement语句对象防止注入攻击
            PreparedStatement 可以使用 ? 作为参数的占位符
            使用 ? 作为占位符, 即使是字符串和日期类型, 也不使用单独再添加 ''
            connection.createStatement();获得的是普通语句对象 Statement
            connection.prepareStatement(sql);可以获得一个预编译语句对象PreparedStatement
            如果SQL语句中有?作为参数占位符号,那么要在执行CURD之前先设置参数
            通过set***(问号的编号,数据) 方法设置参数
            */
            String sql = "select * from emp where ename like ? ";
            preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
            //设置参数
            preparedStatement.setString(1,"%A%");
    
            //执行CURD
            resultSet = preparedStatement.executeQuery();   // 这里不需要再传入SQL语句
            list = new ArrayList<Emp>();
            while(resultSet.next()){
                int empno = resultSet.getInt("empno");
                String ename = resultSet.getString("ename");
                String job = resultSet.getString("job");
                int mgr = resultSet.getInt("mgr");
                Date hiredate = resultSet.getDate("hiredate");
                double sal = resultSet.getDouble("sal");
                double comm = resultSet.getDouble("comm");
                int deptno = resultSet.getInt("deptno");
                Emp emp = new Emp(empno, ename, job, mgr, hiredate, sal, comm, deptno);
                list.add(emp);
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(null != resultSet){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if(null != preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // 遍历集合
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }
    

批处理

  • PreparedStatement批处理

    • 什么是批处理?

      • 当我们有多条sql语句需要发送到数据库执行的时候,有两种发送方式,一种是执行一条发送一条sql语句给数据库,另一个种是发送一个sql集合给数据库,也就是发送一个批sql到数据库
      • 普通的执行过程是:每处理一条数据,就访问一次数据库;而批处理是:累积到一定数量,再一次性提交到数据库,减少了与数据库的交互次数,所以效率会大大提高,很显然两者的数据库执行效率是不同的,我们发送批处理sql的时候数据库执行效率要高
    • statement语句对象实现批处理有如下问题

      • 缺点
        • 采用硬编码效率低,安全性较差
      • 原理
        • 编码,每次执行时相似SQL都会进行编译
    • PreparedStatement + 批处理

      • 优点
        • 语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)
      • 原理
        • 相似SQL只编译一次,减少编译次数
      • 注意
        • 需要设置批处理开启&rewriteBatchedStatements=true
          package com.xxx.test4;
          
          import java.sql.*;
          public class TestBatch {
              private static String driver = "com.mysql.cj.jdbc.Driver";
              private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&&rewriteBatchedStatements=true";
              private static String user = "root";
              private static String password = "root";
              public static void main(String[] args) {
                  testAddBatch();
              }
              // 定义一个方法,向部门表增加1000条数据
              public static void testAddBatch(){
                  Connection connection = null;
                  PreparedStatement preparedStatement = null;
                  try{
                      Class.forName(driver);
                      connection = DriverManager.getConnection(url, user, password);
                      String sql = "insert into dept values (DEFAULT ,?,?)";
                      preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                      
                      //设置参数
                      for (int i = 1; i <= 10663; i++) {
                          preparedStatement.setString(1, "name");
                          preparedStatement.setString(2, "loc");
                          preparedStatement.addBatch();   // 将修改放入一个批次中
                          if(i%1000==0){
                              preparedStatement.executeBatch();
                              preparedStatement.clearBatch(); // 清除批处理中的数据
                          }
                      }
          
                      /*
                      整数数组中的元素代表执行的结果代号
                      SUCCESS_NO_INFO -2
                      EXECUTE_FAILED  -3
                      */
                      /*int[] ints = */
                      preparedStatement.executeBatch();
                      preparedStatement.clearBatch();
                  } catch (Exception e){
                      e.printStackTrace();
                  } finally {
                      if(null != preparedStatement){
                          try {
                              preparedStatement.close();
                          } catch (SQLException e) {
                              e.printStackTrace();
                          }
                      }
          
                      if(null != connection){
                          try {
                              connection.close();
                          } catch (SQLException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
          

事务及回滚点

  • JDBC中使用事务

    • 事务回顾:

      • 事务概念
        • 在逻辑上一组不可分割的操作, 由多个sql语句组成, 多个sql语句要么全都执行成功, 要么都不执行
        • 原子性 一致性 隔离性 持久性
      • JDBC控制事物主要就是在学习如何让多个数据库操作成为一个整体, 实现要么全都执行成功, 要么全都不执行
      • 在JDBC中,事务操作是自动提交
      • 一条对数据库的DML(insert、update、delete)代表一项事务操作, 操作成功后, 系统将自动调用commit()提交, 否则自动调用rollback()回滚, 在JDBC中, 事务操作方法都位于接口java.sql.Connection中, 可以通过调用setAutoCommit(false)来禁止自动提交
      • 之后就可以把多个数据库操作的表达式作为一个事务,在操作完成后调用commit()来进行整体提交,倘若其中一个表达式操作失败,都不会执行到commit(),并且将产生响应的异常;此时就可以在异常捕获时调用rollback()进行回滚,回复至数据初始状态.事务开始的边界则不是那么明显了,它会开始于组成当前事务的所有statement中的第一个被执行的时候。事务结束的边界是commit或者rollback方法的调用
  • 使用事务保证转账安全性

    • 代码展示

      package com.xxx.test5;
      
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      
      public class TestTransaction {
          private static String driver = "com.mysql.cj.jdbc.Driver";
          private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&&rewriteBatchedStatements=true";    private static String user="root";
          private static String user = "root";
          private static String password = "root";
          public static void main(String[] args) {
              testTransaction();
          }
          // 定义一个方法, 向部门表增加1000条数据
          public static void testTransaction(){
              Connection connection = null;
              PreparedStatement preparedStatement = null;
              /*
              JDBC 默认是自动提交事务
              每条DML都是默认提交事务的, 多个preparedStatement.executeUpdate();    都会提交一次事务
              如果想手动控制事务, 那么就不能让事务自动提交    
              通过Connection对象控制connection.setAutoCommit(false);
              如果不设置 默认值为true, 自动提交, 设置为false之后就是手动提交了
              无论是否发生回滚, 事务最终会一定要提交的 提交我们建议放在finally之中进行提交
              如果是转账的过程中出现异常了, 那么我们就要执行回滚, 回滚操作应该方法catch语句块中
              */
              try{
                  Class.forName(driver);
                  connection = DriverManager.getConnection(url, user, password);
                  // 设置事务手动提交
                  connection.setAutoCommit(false);
                  String sql = "update account set money =money- ? where aid = ?";
                  preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                  // 转出
                  preparedStatement.setDouble(1, 100);            
                  preparedStatement.setInt(2, 1);            
                  preparedStatement.executeUpdate();
                  // 产生异常
                  //int i = 1/0;
      
                  // 转入
                  preparedStatement.setDouble(1, -100);            
                  preparedStatement.setInt(2, 2);            
                  preparedStatement.executeUpdate();
              } catch (Exception e){
                  if(null != connection){
                      try {
                           connection.rollback(); // 回滚事务
                      } catch (SQLException ex) {
                          ex.printStackTrace();
                      }
                  }
                  e.printStackTrace();
              }finally {
                  // 提交事务
                  if(null != connection){
                      try {
                          connection.commit();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != preparedStatement){
                      try {
                          preparedStatement.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      } 
                  }
      
                  if(null != connection){
                      try {
                          connection.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
      
  • 设置回滚点

    • 代码展示

      package com.xxx.test5;
      
      import java.sql.*;
      import java.util.LinkedList;
      
      public class TestTransaction2 {
          private static String driver ="com.mysql.cj.jdbc.Driver";
          private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&&rewriteBatchedStatements=true";    private static String user="root";
          private static String user = "root";
          private static String password = "root";
          public static void main(String[] args) {
              testAddBatch();
          }
          // 定义一个方法, 向部门表增加1000条数据
          public static void testAddBatch(){
              Connection connection = null;
              PreparedStatement preparedStatement = null;
      
              LinkedList<Savepoint> savepoints = new LinkedList<Savepoint>();
              try{
                  Class.forName(driver);
                  connection = DriverManager.getConnection(url, user, password);
                  connection.setAutoCommit(false);
                  String sql = "insert into dept values (DEFAULT ,?,?)";
                  preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
      
                  //设置参数
                  for (int i = 1; i <= 10663; i++) {
                      preparedStatement.setString(1, "name");
                      preparedStatement.setString(2, "loc");
                      preparedStatement.addBatch();   // 将修改放入一个批次中
                      if(i%1000 == 0){
                          preparedStatement.executeBatch();
                          preparedStatement.clearBatch(); // 清除批处理中的数据
                          
                          // 设置回滚点
                          Savepoint savepoint = connection.setSavepoint();
                          savepoints.addLast(savepoint);
                      }
                      // 数据在 100001条插入的时候出现异常
      
                      if(i == 10001){
                          int x =1/0;
                      }
                  }
      
                  /*
                  整数数组中的元素代表执行的结果代号
                  SUCCESS_NO_INFO -2
                  EXECUTE_FAILED  -3
                  /*int[] ints = */
                  preparedStatement.executeBatch();            
                  preparedStatement.clearBatch();
              } catch (Exception e){
                  if(null != connection){
                      try {
                          //Savepoint sp = savepoints.getLast();
                          Savepoint sp = savepoints.get(4);
                          if(null != sp){
                              // 选择回滚点
                              connection.rollback(sp);    // 回滚
                          }
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != preparedStatement){
                      try {
                          preparedStatement.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != connection){
                      try {
                          connection.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
      

JDBC API总结阅读

  • Connection接口

    • 作用:代表数据库连接

    • 方法摘要

      • close ()
        • 立即释放此 Connection 对象的数据库和 JDBC 资源,而不是等待它们被自动释放
      • commit ()
        • 使所有上一次提交 / 回滚后进行的更改成为持久更改,并释放此 Connection 对象当前持有的所有数据库锁
      • createStatement ()
        • 创建一个 Statement 对象来将 SQL 语句发送到数据库
      • prepareCall (String sql)
        • 创建一个 CallableStatement 对象来调用数据库存储过程
      • prepareStatement (String sql, int autoGeneratedKeys)
        • 创建一个默认 PreparedStatement 对象,该对象能获取自动生成的键
      • rollback ()
        • 取消在当前事务中进行的所有更改,并释放此 Connection 对象当前持有的所有数据库锁
      • setAutoCommit (boolean autoCommit)
        • 将此连接的自动提交模式设置为给定状态
  • DriverManager类

    • 作用:管理一组 JDBC 驱动程序的基本服务

    • 应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。

    • 在调用 getConnection 方法时,DriverManager 会试着从初始化时加载的那些驱动程序以及使用与当前 applet 或应用程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序

    • 方法摘要

      • getConnection (String url)
        • 试图建立到给定数据库 URL 的连接
      • getConnection (String url, Properties info)
        • 试图建立到给定数据库 URL 的连接
      • getConnection (String url, String user, String password)
        • 试图建立到给定数据库 URL 的连接
  • Statement接口

    • 作用:用于将 SQL 语句发送到数据库中,或理解为执行sql语句

    • 有三种 Statement对象:

      • Statement
        • 用于执行不带参数的简单SQL语句
      • PreparedStatement(从 Statement 继承)
        • 用于执行带或不带参数的预编译SQL语句
      • CallableStatement(从PreparedStatement 继承)
        • 用于执行数据库存储过程的调用
      方法 作用
      ResultSet executeQuery(String sql) 执行SQL查询并获取到 ResultSet对象
      int executeUpdate(String sql) 可以执行插入、删除、更新等操作,返回值是执行该操作所影响的行数
  • PreparedStatement接口

    • 关系:public interface PreparedStatement extends Statement

    • 区别

      • PreparedStatment 安全性高,可以避免SQL注入
      • PreparedStatment 简单不繁琐,不用进行字符串拼接
      • PreparedStatment 性能高,用在执行多个相同数据库DML操作时
  • ResultSet接口

    • ResultSet 对象是 executeQuery() 方法的返回值,它被称为结果集,它代表符合 SQL 语句条件的所有行,并且它通过一套 getXXX 方法(这些 get 方法可以访问当前行中的不同列)提供了对这些行中数据的访问

    • ResultSet 里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行

    • 我们如果想要取得某一条记录,就要使用 ResultSetnext() 方法 ,如果我们想要得到 ResultSet 里的所有记录,就应该使用 while循环

    • ResultSet 对象自动维护指向当前数据行的游标。每调用一次 next() 方法,游标向下移动一行

    • 初始状态下记录指针指向第一条记录的前面,通过 next() 方法指向第一条记录。循环完毕后指向最后一条记录的后面

      方法名 说明
      boolean next() 将光标从当前位置向下移动一行
      boolean previous() 游标从当前位置向上移动一行
      void close() 关闭 ResultSet 对象
      int getInt(int collIndex) 以 int 形式获取结果集当前行指定列号值
      int getInt(String collLabel) 以 int 形式获取结果集当前行指定列名值
      float getFloat(int collIndex) 以 float 形式获取结果集当前行指定列号值
      Float getFloat(String collLabel) 以 float 形式获取结果集当前行指定列名值
      String getString(int collIndex) 以 String 形式获取结果集当前行指定列号值
      String getString(String collLabel) 以 String 形式获取结果集当前行指定列名值

DAO模式

  • 引入

    • DAO(Data Access Object)是一个数据访问接口

    • 数据访问

      • 顾名思义就是与数据库打交道,夹在业务逻辑与数据库资源中间
  • 在核心J2EE模式中是这样介绍DAO模式的

    • 为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中

    • 用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法

    • 在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储

  • 概述

    • 简单来说, 就是定义一个接口, 规定一些增删改查的方法, 然后交给实现类去实现, 它介于数据库和业务逻辑代码之间, 这样当我们需要操作数据库是, 根据接口定义的API去操作数据库就可以了, 每个方法都是一个原子性的操作, 例如: 增加、修改、删除等

  • Dao模式要求项目必须具备这样几个结构

    • 实体类

      • 和数据库表格一一对应的类, 单独放入一个包中, 包名往往是 pojo/entity/bean, 要操作的每个表格都应该有对应的实体类
        • emp > class Emp
        • dept > class Dept
        • account > class Account
    • DAO层

      • 定义了对数据要执行那些操作的接口和实现类, 包名往往是 dao/mapper, 要操作的每个表格都应该有对应的接口和实现类
        • emp > interface EmpDao > EmpDaoImpl
        • dept > interface DeptDao > DeptDaoImpl
    • Mybatis/Spring JDBCTemplate中, 对DAO层代码进行了封装, 代码编写方式会有其他变化

  • 项目的搭建

    • 1.创建项目

    • 2.添加jar包

    • 3.创建包

    • 4.创建实体类Emp

    • 5.创建后台的接口EmpDao和实现类EmpDaoImpl

  • 导入各个层级的接口和页面之后的项目

    • 项目结构截图如下

      在这里插入图片描述

    • 实体类代码

      public class Emp implements Serializable {
          private Integer empno;    
          private String ename;    
          private String job;    
          private Integer mgr;    
          private Date hiredate;    
          private Double sal;    
          private Double comm;    
          private Integer deptno;
      
      public class Dept implements Serializable {
          private Integer deptno;    
          private String dname;    
          private String loc;
      
    • DAO接口代码

      package com.xxx.dao;
      
      import com.xxx.pojo.Emp;
      
      public interface EmpDao {
          /**
           * 向数据库Emp表中增加一条数据的方法
           * @param emp 要增加的数据封装成的Emp类的对象
           * @return 增加成功返回大于0 的整数,增加失败返回0
           */
          int addEmp(Emp emp);
          /**
           * 根据员工编号删除员工信息的方法
           * @param empno 要删除的员工编号
           * @return 删除成功返回大于0的整数,失败返回0
           */
          int deleteByEmpno(int empno);
      }
      
    • DAO实现类代码

      package com.xxx.dao.impl;
      
      import com.xxx.dao.EmpDao;
      import com.xxx.pojo.Emp;
      
      import java.sql.*;
      
      public class EmpDaoImpl implements EmpDao {
          private static String driver = "com.mysql.cj.jdbc.Driver";    
          private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";   
          private static String user = "root";    
          private static String password = "root";
      
          @Override
          public int addEmp(Emp emp) {
              // 向 Emp表 中增加一条数据
              Connection connection = null;
              PreparedStatement preparedStatement = null;
              int rows = 0;
      
              try{
                  Class.forName(driver);
                  connection = DriverManager.getConnection(url, user, password);
                  String sql = "insert into emp values(DEFAULT ,?,?,?,?,?,?,?)";
                  preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                  //设置参数
                  preparedStatement.setObject(1,emp.getEname());            
                  preparedStatement.setObject(2,emp.getJob() );            
                  preparedStatement.setObject(3,emp.getMgr());            
                  preparedStatement.setObject(4,emp.getHiredate());            
                  preparedStatement.setObject(5,emp.getSal());            
                  preparedStatement.setObject(6,emp.getComm());            
                  preparedStatement.setObject(7,emp.getDeptno());
                  //执行CURD
                  rows = preparedStatement.executeUpdate();   // 这里不需要再传入SQL语句
              } catch (Exception e){
                  e.printStackTrace();
              } finally {
                  if(null != preparedStatement){
                      try {
                          preparedStatement.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != connection){
                      try {
                          connection.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
              return rows;
          }
          @Override
          public int deleteByEmpno(int empno) {
              // 向 Emp表 中增加一条数据
              Connection connection = null;
              PreparedStatement preparedStatement = null;
              int row = 0;
      
              try {
                  Class.forName(driver);
                  connection = DriverManager.getConnection(url, user,password);
                  String sql = "delete from emp where empno =?";
                  preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                  //设置参数
                  preparedStatement.setObject(1, empno);
      
                  //执行CURD
                  rows =preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
              } catch (Exception e){
                  e.printStackTrace();
              } finally {
                  if(null != preparedStatement){
                      try {
                          preparedStatement.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
      
                  if(null != connection){
                      try {
                          connection.close();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
              return rows;
          }
      }
      

员工管理系统开发

  • DAO接口

    package com.xxx.dao;
    
    import com.xxx.pojo.Emp;
    
    import java.util.List;
    
    public interface EmpDao {
        /**
         * 向数据库Emp表中增加一条数据的方法
         * @param emp 要增加的数据封装成的Emp类的对象
         * @return 增加成功返回大于0 的整数,增加失败返回0
         */
        int addEmp(Emp emp);
        /**
         * 根据员工编号删除员工信息的方法
         * @param empno 要删除的员工编号
         * @return 删除成功返回大于0的整数,失败返回0
         */
        int deleteByEmpno(int empno);
        /**
         * 查看数据库表格中所有的员工信息
         * @return 所有员工信息封装的一个List<Emp>集合
         */
        List<Emp> findAll();
    
        /**
         * 根据员工编号修改员工其他所有字段的方法
         * @param emp 员工编号和其他7个字段封装的一个Emp类对象
         * @return 修改成功返回大于0的整数,失败返回0
         */
        int updateEmp(Emp emp);
    }
    
    package com.xxx.dao;
    
    import com.xxx.pojo.Dept;
    
    import java.util.List;
    
    public interface DeptDao {
        /**
         * 查询全部门的方法
         * @return Dept对象封装的List集合
         */
        List<Dept> findAll();
        int addDept(Dept dept);
    }
    
  • DAO实现类

    package com.xxx.dao.impl;
    
    import com.xxx.dao.EmpDao;
    import com.xxx.pojo.Emp;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class EmpDaoImpl implements EmpDao {
        private static String driver = "com.mysql.cj.jdbc.Driver";
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";    
        private static String user = "root";    
        private static String password = "root";
    
        @Override
        public int addEmp(Emp emp) {
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            int rows = 0;
    
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                String sql = "insert into emp values(DEFAULT ,?,?,?,?,?,?,?)";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //设置参数
                preparedStatement.setObject(1,emp.getEname());            
                preparedStatement.setObject(2,emp.getJob() );            
                preparedStatement.setObject(3,emp.getMgr());            
                preparedStatement.setObject(4,emp.getHiredate());            
                preparedStatement.setObject(5,emp.getSal());            
                preparedStatement.setObject(6,emp.getComm());            
                preparedStatement.setObject(7,emp.getDeptno());
                //执行CURD
                rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try{
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return rows;
        }
    
        @Override
        public int deleteByEmpno(int empno) {
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            int rows = 0;
    
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                String sql = "delete from emp where empno =?";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //设置参数
                preparedStatement.setObject(1,empno);
                //执行CURD
                rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } 
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return rows;
        }
    
        @Override
        public List<Emp> findAll() {
            // 查询名字中包含字母A的员工信息
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            List<Emp> list = null;
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                String sql = "select * from emp";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //执行CURD
                resultSet = preparedStatement.executeQuery();   // 这里不需要再传入SQL语句
                list = new ArrayList<Emp>() ;
                while(resultSet.next()){
                    int empno = resultSet.getInt("empno");
                    String ename = resultSet.getString("ename");
                    String job = resultSet.getString("job");
                    int mgr = resultSet.getInt("mgr");
                    Date hiredate = resultSet.getDate("hiredate");
                    double sal = resultSet.getDouble("sal");
                    double comm = resultSet.getDouble("comm");
                    int deptno = resultSet.getInt("deptno");
                    Emp emp = new Emp(empno, ename, job, mgr, hiredate, sal, comm, deptno);
                    list.add(emp);
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != resultSet){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return list;
        }
    
        @Override
        public int updateEmp(Emp emp) {
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            int rows = 0;
    
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user,password);
                String sql = "update emp set ename =? ,job=?, mgr =?,hiredate =?,sal=?,comm=?,deptno=? where empno =?";
                preparedStatement = connection.prepareStatement(sql);    //这里已经传入SQL语句
                //设置参数
                preparedStatement.setObject(1,emp.getEname());
                preparedStatement.setObject(2,emp.getJob() );
                preparedStatement.setObject(3,emp.getMgr());            
                preparedStatement.setObject(4,emp.getHiredate());            
                preparedStatement.setObject(5,emp.getSal());            
                preparedStatement.setObject(6,emp.getComm());            
                preparedStatement.setObject(7,emp.getDeptno());            
                preparedStatement.setObject(8,emp.getEmpno());
    
                //执行CURD
                rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return rows;
        }
    }
    
    package com.xxx.dao.impl;
    
    import com.msb.dao.DeptDao;
    import com.msb.pojo.Dept;
    import com.msb.pojo.Emp;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class DeptDaoImpl implements DeptDao {
        private static String driver = "com.mysql.cj.jdbc.Driver";    
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";    
        private static String user = "root";    
        private static String password = "root";
        @Override
        public List<Dept> findAll() {
            // 查询名字中包含字母A的员工信息
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
    
            List<Dept> list = null;
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                String sql = "select * from dept";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //执行CURD
                resultSet = preparedStatement.executeQuery();   // 这里不需要再传入SQL语句
                list = new ArrayList<Dept>() ;
                while(resultSet.next()){
                    int deptno = resultSet.getInt("deptno");
                    String dname = resultSet.getString("dname");
                    String loc = resultSet.getString("loc");
                    Dept dept = new Dept(deptno, dname, loc);
                    list.add(dept);            
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != resultSet){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return list;
        }
    
        @Override
        public int addDept(Dept dept) {
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            int rows = 0;
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                String sql = "insert into dept values(?,?,?)";
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //设置参数
                preparedStatement.setObject(1,dept.getDeptno());
                preparedStatement.setObject(2,dept.getDname());
                preparedStatement.setObject(3,dept.getLoc());
    
                //执行CURD
                rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return rows;
        }
    }
    
  • EmpManageSystem类

    package com.xxx.view;
    
    import com.xxx.dao.DeptDao;
    import com.xxx.dao.EmpDao;
    import com.xxx.dao.impl.DeptDaoImpl;
    import com.xxx.dao.impl.EmpDaoImpl;
    import com.xxx.pojo.Dept;
    import com.xxx.pojo.Emp;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    import java.util.Scanner;
    
    public class EmpManageSystem {
        private static Scanner sc = new Scanner(System.in);    
        private static EmpDao empDao = new EmpDaoImpl();    
        private static DeptDao deptDao = new DeptDaoImpl();    
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");;
        public static void main(String[] args) {
            while(true){
                showMenu();
                System.out.println("请录入选项");
                int option = sc.nextInt();
                switch (option){
                    case 1:
                        case1();
                        break;
                    case 2: 
                        case2();                    
                        break;
                    case 3:
                        case3();
                        break;
                    case 4:
                        case4();                    
                        break;
                    case 5:
                        case5();
                        break;
                    case 6:
                        case6();
                        break;
                    case 7:
                        break;
                    default: 
                        System.out.println("请正确输入选项");
                }
            }
        }
    
        private static void case1() {
            List<Emp> emps = empDao.findAll();
            emps.forEach(System.out::println);
        }
    
        private static void case2(){
            List<Dept> depts = deptDao.findAll();
            depts.forEach(System.out::println);
        }
    
        private static void case3(){
            System.out.println("请输入要删除的员工编号");
            int empno=sc.nextInt();
            empDao.deleteByEmpno(empno);
        }
    
        private static void case4(){
            System.out.println("请输入员工编号");
            int empno = sc.nextInt();
            System.out.println("请输入员工姓名");        
            String ename =sc.next();        
            System.out.println("请输入员工职位");        
            String job =sc.next();        
            System.out.println("请输入员工上级");        
            int mgr =sc.nextInt();        
            System.out.println("请输入员工入职日期,格式为yyyy-MM-dd");        
            Date hiredate = null;
            try {
                hiredate = simpleDateFormat.parse(sc.next());
            } catch (ParseException e) {
                e.printStackTrace();
            }
            System.out.println("请输入员工工资");        
            double sal = sc.nextDouble();        
            System.out.println("请输入员工补助");        
            double comm = sc.nextDouble();        
            System.out.println("请输入员工部门号");        
            int deptno = sc.nextInt();
            Emp emp = new Emp(empno, ename, job, mgr, hiredate, sal, comm,deptno);
            empDao.updateEmp(emp);
        }
    
        private static void case5(){
            System.out.println("请输入员工姓名");
            String ename = sc.next();
            System.out.println("请输入员工职位");
            String job = sc.next();
            System.out.println("请输入员工上级");
            int mgr = sc.nextInt();
            System.out.println("请输入员工入职日期,格式为yyyy-MM-dd");
            Date hiredate = null;
            try {
                hiredate = simpleDateFormat.parse(sc.next());
            } catch (ParseException e) {
                e.printStackTrace();
            }
            System.out.println("请输入员工工资");
            double sal = sc.nextDouble();
            System.out.println("请输入员工补助");
            double comm = sc.nextDouble();
            System.out.println("请输入员工部门号");
            int deptno = sc.nextInt();
            Emp emp = new Emp(null, ename, job, mgr, hiredate, sal, comm, deptno);
            empDao.addEmp(emp);
        }
    
        private static void case6(){
            System.out.println("请录入部门号");
            int deptno = sc.nextInt();
            System.out.println("请录入部门名称");
            String dname = sc.next();
            System.out.println("请录入部门位置");
            String loc = sc.next();
            Dept dept = new Dept(deptno, dname, loc);
            deptDao.addDept(dept);
        }
    
        public static void showMenu(){
            System.out.println("************************************");
            System.out.println("* 1 查看所有员工信息");        
            System.out.println("* 2 查看所有部门信息");        
            System.out.println("* 3 根据工号删除员工信息");        
            System.out.println("* 4 根据工号修改员工信息");        
            System.out.println("* 5 增加员工信息");        
            System.out.println("* 6 增加部门信息");        
            System.out.println("* 7 退出");        
            System.out.println("************************************");
        }
    }
    

  • BaseDao抽取

    • BaseDAO代码

    package com.xxx.dao;
    
    import com.xxx.pojo.Emp;
    
    import java.lang.reflect.Field;
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public abstract class BaseDao {
        private static String driver = "com.mysql.cj.jdbc.Driver";    
        private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";   
        private static String user = "root";    
        private static String password = "root";
    
        public int baseUpdate(String sql, Object ... args){
            // 向 Emp表中增加一条数据
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            int rows = 0;
            
            try{
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                preparedStatement = connection.prepareStatement(sql);
                //设置参数
                for (int i = 0; i <args.length; i++) {
                    preparedStatement.setObject(i+1, args[i]);
                }
    
                //执行CURD
                rows = preparedStatement.executeUpdate();    // 这里不需要再传入SQL语句
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } 
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return rows;
        }
    
        public List baseQuery(Class clazz,String sql,Object ... args) {
            // 查询名字中包含字母A的员工信息
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            List list = null;
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(url, user, password);
                preparedStatement = connection.prepareStatement(sql);   //这里已经传入SQL语句
                //设置参数
                for (int i = 0; i <args.length ; i++) {
                    preparedStatement.setObject(i+1, args[i]);
                }
    
                //执行CURD
                resultSet = preparedStatement.executeQuery();   // 这里不需要再传入SQL语句
                list = new ArrayList() ;
                // 根据字节码获取所有 的属性
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);  // 设置属性可以 访问
                }
    
                while(resultSet.next()){
                    // 通过反射创建对象
                    Object obj = clazz.newInstance();   //默认在通过反射调用对象的空参构造方法
                    for (Field field : fields) {    // 临时用Field设置属性
                    String fieldName = field.getName();     // empno  ename job .... ...
                    Object data = resultSet.getObject(fieldName);
                    field.set(obj, data);
                    }
                    list.add(obj);
                } 
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                if(null != resultSet){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                } 
    
                if(null != preparedStatement){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if(null != connection){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            return list;
        }
    }
    
  • 到实现类代码

    package com.xxx.dao.impl;
    
    import com.xxx.dao.BaseDao;
    import com.xxx.dao.EmpDao;
    import com.xxx.pojo.Emp;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class EmpDaoImpl extends BaseDao implements EmpDao {
        @Override
        public int addEmp(Emp emp) {
            String sql = "insert into emp values(DEFAULT ,?,?,?,?,?,?,?)";
            return baseUpdate(sql, emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno());
        }
    
        @Override
        public int deleteByEmpno(int empno) {
            String sql = "delete from emp where empno = ?";
            return baseUpdate(sql, empno);
        }
    
        @Override
        public List<Emp> findAll() {
            String sql = "select * from emp";
            return baseQuery(Emp.class, sql);
        }
    
        @Override
        public int updateEmp(Emp emp) {
            String sql = "update emp set ename =? ,job=?, mgr =?,hiredate =?,sal=?,comm=?,deptno=? where empno =?";      
            return baseUpdate(sql, emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno(),emp.getEmpno());    
        }
    }
    
    package com.xxx.dao.impl;
    
    import com.msb.dao.BaseDao;
    import com.msb.dao.DeptDao;
    import com.msb.pojo.Dept;
    import com.msb.pojo.Emp;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class DeptDaoImpl extends BaseDao implements DeptDao {
        @Override
        public List<Dept> findAll() {
            String sql = "select * from dept";
            return baseQuery(Dept.class, sql);
        }
    
        @Override
        public int addDept(Dept dept) {
            String sql = "insert into dept values(?,?,?)";
            return baseUpdate(sql, dept.getDeptno(),dept.getDname(),dept.getLoc());
        }
    }
    

连接池的使用

  • 建立数据库连接的两种方式:

    • 传统连接方式

      • 首先调用Class.forName()方法加载数据库驱动,然后调用DriverManager.getConnection()方法建立连接
    • 连接池方式

      • 连接池解决方案是在应用程序启动时就预先建立多个数据库连接对象, 然后将连接对象保存到连接池中
      • 当客户请求到来时, 从池中取出一个连接对象为客户服务
      • 当请求完成时,客户程序调用close()方法,将连接对象放回池中
      • 对于多于连接池中连接数的请求,排队等待
      • 应用程序还可根据连接池中连接的使用率, 动态增加或减少池中的连接数
  • 传统方式存在问题

    • Connection对象在每次执行DML和DQL的过程中都要创建一次, DML和DQL执行完毕后, connection对象都会被销毁

    • connection对象是可以反复使用的, 没有必要每次都创建新的

    • 该对象的创建和销毁都是比较消耗系统资源的, 如何实现connection对象的反复使用呢?

      • 使用连接池技术实现
  • 连接池的优势

    • 预先准备一些链接对象, 放入连接池中, 当多个线程并发执行时, 可以避免短时间内一次性大量创建链接对象, 减少计算机单位时间内的运算压力, 提高程序的响应速度

    • 实现链接对象的反复使用, 可以大大减少链接对象的创建次数, 减少资源的消耗

    在这里插入图片描述

    • 具体实现如下

    在这里插入图片描述

    • 定义连接池
      package com.xxx.dao;
      
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.util.LinkedList;
      
      public class MyConnectionPool {
          private static String driver = "com.mysql.cj.jdbc.Driver";    
          private static String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
          private static String user = "root";    
          private static String password = "root";    
          private static int initSize = 1;    
          private static int maxSize = 1;
      
          private static LinkedList<Connection> pool;
          static{
              // 加载驱动
              try {
                  Class.forName(driver);
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } 
              // 初始化pool
              pool = new LinkedList<Connection>();
              // 创建5个链接对象
              for (int i = 0; i <initSize; i++) {
                  Connection connection = initConnection();
                  if(null != connection){
                      pool.add(connection);
                      System.out.println("初始化连接"+connection.hashCode()+"放入连接池");
                  }
              }
          }
      
          // 私有的初始化一个链接对象的方法
          private static Connection initConnection(){
              try {
                  return DriverManager.getConnection(url, user, password);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              return null;
          }
      
          // 共有的向外界提供链接对象的
          public static Connection getConnection(){
              Connection connection = null;
              if(pool.size() > 0){
                  connection = pool.removeFirst();    // 移除集合中的第一个元素
                  System.out.println("连接池中还有连接:"+connection.hashCode());
              } else{
                  connection = initConnection();
                  System.out.println("连接池空,创建新连接:"+connection.hashCode());
              }
              return connection;
          }
      
          // 共有的向连接池归还连接对象的方法
          public static void returnConnection(Connection connection){
              if(null != connection){
                  try {
                      if(!connection.isClosed()){
                          if(pool.size()<maxSize){
                              try {
                                  connection.setAutoCommit(true);     // 调整事务状态
                                  System.out.println("设置连接:"+connection.hashCode()+"自动提交为true");
                              } catch (SQLException e) {
                                  e.printStackTrace();
                              }
                              pool.addLast(connection);
                              System.out.println("连接池未满,归还连接:"+connection.hashCode());
                          } else {
                              try {
                                  connection.close();
                                  System.out.println("连接池满了,关闭连接:"+connection.hashCode());
                              } catch (SQLException e) {
                                  e.printStackTrace();
                              }
                          }
                      } else {
                          System.out.println("连接:"+connection.hashCode()+"已经关闭,无需归还");
                      }
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              } else {
                  System.out.println("传入的连接为null, 不可归还");
              }
          }
      }
      

log4j日志框架_了解

  • 什么是日志log

    • 异常信息 登录成功失败的信息 其他重要操作的信息

      • 日志可以记录程序的运行状态, 运行信息, 用户的一些常用操作.

      • 日志可以帮助我们分析程序的运行状态, 帮我们分析用户的操作习惯, 进而对程序进行改进

  • 如何记录日志

    • 方式1:System.out.println(.....) e.printStackTrace();

      • 缺点:不是保存到文件,不能长久存储
    • 方式2:IO流System.out.println(.....) e.printStackTrace(); 写入文件

      • 缺点:操作繁琐, IO流操作容易阻塞线程, 日志没有等级, 日志的格式不能很好的定制, 要想实行编程复杂
    • 方式3:使用现成的日志框架,比如log4j

      • 优点:1长久保存 2有等级 3格式可以很好的定制 4代码编写简单
  • log4j日志的级别

    • FATAL

      • 指出现非常严重的错误事件,这些错误可能导致应用程序异常中止
    • ERROR

      • 指虽有错误,但仍允许应用程序继续运行
    • WARN

      • 指运行环境潜藏着危害
    • INFO

      • 指报告信息,这些信息在粗粒度级别上突出显示应用程序的进程
    • DEBUG

      • 指细粒度信息事件,对于应用程序的调试是最有用的
  • 使用log4j记录日志

    • 加入jar包 log4j-1.2.8.jar

    • 加入属性文件 src 下 log4j.properties

      log4j.rootLogger = error,logfile
      
      log4j.appender.stdout = org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.Target = System.err
      log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout
      
      log4j.appender.logfile = org.apache.log4j.FileAppender
      log4j.appender.logfile.File = d:/msb.log
      log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
      log4j.appender.logfile.layout.ConversionPattern = %d{yyyy-MM-dd   HH:mm:ss} %l %F %p %m%n
      
  • 通过属性文件理解log4j的主要API

    • Appender日志目的地:

      • ConsoleAppender FileAppender
    • Layout 日志格式化器:

      • SimpleLayout PatternLayout
  • 代码中记录日志

    //创建一个日志记录器
    private static final Logger logger = Logger.getLogger(DBUtil.class.getName()); 
    //在合适的地方添加日志
    logger.info("正确的读取了属性文件:"+prop); 
    logger.debug("正确的关闭了结果集"); 
    logger.error("DML操作错误:"+e); 
    
  • 理解日志格式化字符的含义

    • %p

      • 输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL
    • %d

      • 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}
    • %r

      • 输出自应用程序启动到输出该log信息耗费的毫秒数
    • %t

      • 输出产生该日志事件的线程名
    • %l

      • 输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数
        • test.TestLog4j.main(TestLog4j.java:10)
    • %c

      • 输出日志信息所属的类目,通常就是所在类的全名
    • %M

      • 输出产生日志信息的方法名
    • %F

      • 输出日志消息产生时所在的文件名称
    • %L

      • 输出代码中的行号
    • %m

      • 输出代码中指定的具体日志信息
    • %n

      • 输出一个回车换行符,Windows平台为"rn",Unix平台为"n"
    • %x

      • 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中
    • %%

      • 输出一个"%"字符
  • 使用log4j记录日志 连接池中通过log4j记录日志

    • 代码展示

      package com.xxx.dao;
      
      import com.msb.util.PropertiesUtil;
      import org.apache.log4j.Logger;
      
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.util.LinkedList;
      
      public class MyConnectionPool {
          private static String driver;    
          private static String url;    
          private static String user;    
          private static String password;    
          private static int initSize;    
          private static int maxSize;    
          private static Logger logger;
          private static LinkedList<Connection> pool;
      
          static{
              logger = Logger.getLogger(MyConnectionPool.class);
              // 初始化参数
              PropertiesUtil propertiesUtil = new PropertiesUtil("/jdbc.properties");
              driver = propertiesUtil.getProperties("driver");
              url = propertiesUtil.getProperties("url");
              user = propertiesUtil.getProperties("user");
              password = propertiesUtil.getProperties("password");
              initSize = Integer.parseInt(propertiesUtil.getProperties("initSize"));
              maxSize = Integer.parseInt(propertiesUtil.getProperties("maxSize"));
              // 加载驱动
              try {
                  Class.forName(driver);
              } catch (ClassNotFoundException e) {
                  logger.fatal("找不到数据库驱动类"+driver, e);
              }
              // 初始化pool
              pool = new LinkedList<Connection>();
              // 创建5个链接对象
              for (int i = 0; i < initSize ; i++) {
                  Connection connection = initConnection();
                  if(null != connection){
                      pool.add(connection);
                      logger.info("初始化连接"+connection.hashCode()+"放入连接池");
                  }
              }
          }
      
          // 私有的初始化一个链接对象的方法
          private static Connection initConnection(){
              try {
                  return DriverManager.getConnection(url, user, password);
              } catch (SQLException e) {
                  logger.fatal("初始化连接异常",e);
              } return null;
          }
      
          // 共有的向外界提供链接对象的
          public static Connection getConnection(){
              Connection connection = null;
              if(pool.size() > 0){
                  connection = pool.removeFirst();     // 移除集合中的第一个元素
                  logger.info("连接池中还有连接:"+connection.hashCode());
              } else {
                  connection = initConnection();
                  logger.info("连接池空,创建新连接:"+connection.hashCode());
              }
              return connection;
          }
      
          // 共有的向连接池归还连接对象的方法
          public static void returnConnection(Connection connection){
              if(null != connection){
                  try {
                      if(!connection.isClosed()){
                          if(pool.size()<maxSize){
                              try {
                                  connection.setAutoCommit(true);     // 调整事务状态
                                  logger.debug("设置连接:"+connection.hashCode()+"自动提交为true");
                              } catch (SQLException e) {
                                  e.printStackTrace();
                              }
                              pool.addLast(connection);
                              logger.info("连接池未满,归还连接:"+connection.hashCode());
                          } else {
                              try {
                                  connection.close();
                                  logger.info("连接池满了,关闭连接:"+connection.hashCode());
                              } catch (SQLException e) {
                                  e.printStackTrace();
                              }
                          }
                      } else {
                          logger.info("连接:"+connection.hashCode()+"已经关闭,无需归还");
                      }
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              } else {
                  logger.warn("传入的连接为null,不可归还");
              }
          }
      }
      

三大范式

  • 概述

    • 什么是范式

      • 必须保证数据库设计的合理性, 对数据库设计总结的一些经验性的规范, 称之为范式
        • 数据库设计关系整个系统的架构,关系到后续开发效率和运行效率
        • 数据库的设计主要包含了设计表结构和表之间的联系
    • 什么是合理数据库

      • 结构合理
      • 冗余较小
      • 尽量避免插入删除修改异常
    • 如何才能保证数据库设计水平

      • 遵循一定的规则
      • 在关系型数据库中这种规则就称为范式
    • 什么是范式(NF = NormalForm)

      • 范式是符合某一种设计要求的总结
      • 要想设计一个结构合理的关系型数据库,必须满足一定的范式
    • 范式分类

      • 第一范式: 列原子性
      • 第二范式: 数据和联合主键完全相关性
      • 第三范式: 数据和主键直接相关性
        • Boyce Codd范式 = BCNF
        • 由 Boyce 和 Codd 提出的
        • 比3NF又进了一步
        • 通常认为是修正的第三范式
      • 第四范式
      • 第五范式
      • 总结
        • 各个范式是依次嵌套包含的
        • 范式越高,设计质量越高,在现实设计中也越难实现
        • 一般数据库设计,只要达到第三范式,即可避免异常的出现
  • 第一范式

    • 要求

      • 最基本的范式
      • 数据库表每一列都是不可分割基本数据项,同一列中不能有多个值
      • 简单说就是要确保每列保持原子性
      • 第一范式的合理遵循需要根据系统的实际需求来定

      在这里插入图片描述

    • 示例

      • 用户表(用户名,家庭地址)
      • 用户表(用户名,省,城市,详细地址)
      • 系(系名称,系主任,系高级职称人数)
      • 系(系名称,系主任,系教授人数,系副教授人数)
  • 第二范式

    • 要求

      • 第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(言)。即在一个数据库表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中
    • 示例

      • 学号和课程编号作为联合主键
      • 课程名称只依赖于课程编号,而和学号没有关系
    • 解决

      • 提取出学生表

      在这里插入图片描述

      • 提取成课程表

      在这里插入图片描述

      • 提取选课表,存放选课记录

      在这里插入图片描述

  • 第三范式

    • 要求

      • 确保数据表中的每一列数据都和主键直接相关,而不能间接相关
      • 属性不依赖于其他非主属性
    • 示例1:学生班级表

      学号(主键) 学生姓名 班级编号 班级名称 班级信息
      023145 张三 987654 3班 特招班
      023146 李四 987654 3班 特招班
      023147 王五 987655 4班 普通班
      023258 赵六 987654 3班 特招班
      • 完善之后的方案:
        学号(主键) 学生姓名 班级编号
        023145 张三 987654
        023146 李四 987654
        023147 王五 987655
        023258 赵六 987654
        班级编号(主键) 班级名称 班级信息
        987654 3班 特招班
        987655 4班 普通班
    • 示例2:订单明细表

      编号(主键) 图书id 图书名称 价格 作者 出版社 出版日期 数量
      023145 1 精通Java 60.00 张三 清华出版社 2007 1
      023146 2 Oracle 65.00 李四 机械出版社 2009 1
      023147 3 JSP 87 王五 电子出版社 2014 3
      023258 1 精通Java 60.00 张三 清华出版社 2007 2
      023259 2 Oracle 65.00 李四 机械出版社 2009 3
      • 完善之后的方案:分割成图书表和订单表两种表
      图书id 图书名称 价格 作者 出版社 出版日期
      1 精通 Java 60.00 张三 清华出版社 2007
      2 Oracle 65.00 李四 机械出版社 2009
      3 JSP 87 王五 电子出版社 2014
      4 Struts2 56 赵六 清华出版社 2005
      编号(主键) 图书id 数量
      023145 1 1
      023146 2 1
      023147 3 3
      023258 2 2
      023259 2 3
  • 范式的总结

    • 优点

      • 结构合理
      • 冗余较小
      • 尽量避免插入删除修改异常
    • 缺点

      • 性能降低
      • 多表查询比单表查询速度慢
    • 数据库的设计应该根据当前情况和需求做出灵活的处理

      • 在实际设计中,要整体遵循范式理论
      • 如果在某些特定的情况下还死死遵循范式也是不可取的,因为可能降低数据库的效率,此时可以适当增加冗余而提高性能
    • 示例:

      • 比如经常购物车条目的中除了条目编号,商品编号,商品数量外,可以增加经常使用的商品名称,商品价格等
    • 范式是指导数据设计的规范化理论,可以保证数据库设计质量

    • 第一范式: 字段不能再分

    • 第二范式: 不存在局部依赖

    • 第三范式: 不含传递依赖(间接依赖)

    • 使用范式可以减少冗余,但是会降低性能

    • 特定表的的设计可以违反第三范式,增加冗余提高性能


数据之间的三大关系

  • 一对一 A表中的一条数据对应B表中的一条数据

    在这里插入图片描述

  • 一对多 A表中的一条数据对应B表中的多条数据

    在这里插入图片描述

  • 多对多 A表中对应B表中多条数据, 同样B表中对应A表中多条数据

    • 多对多需要通过中间表体现关系

    • 中间表讲多对多的关系转变成两个一对多

      在这里插入图片描述

更多推荐