JDBC、封装JDBC连接池、第三方连接池工具

主要内容:

  • JDBC简介
    • JDBC来源
    • 通过代码实现JDBC
    • JDBC的改进需求
    • JDBC改进的代码实现
    • JDBC使用的设计模式
    • 封装连接池
    • 封装JDBC连接池
    • ThreadLoacl的使用
    • ThreadLocal常见方法
    • 第三方连接池工具(数据源)
    • 策略模式

JDBC简介

全称:Java DataBase Connection
译名:Java数据库连接

JDBC来源

JDBC的来源:
最早在Java提出连接数据库需求的时候,Java的做法是:Java根据不同数据库的特点,针对每一种数据库都提出了一套代码,在使用不同的数据库和Java进行连接的时候,使用不同的代码。
缺点:

  1. Java程序构建相当复杂,因为程序员要记住多种数据库连接代码,有多少种数据库,就要有多少种连接方式。
  2. Java代码的可重用性相当低,导致Java对数据库连接程序的开发效率下降,后来Java提出了新的思路:在Java中,我们提出了一套“标准(接口)”,将这套接口的实现交给不同的数据库厂商来完成。

优点:

  1. 程序员的工作压力下降,不管使用哪一种数据库连接,使用的接口类型都是一样的
  2. Java中DBC的代码重用度提高了,所有数据库的连接代码结构都是一样的,不同数据库厂商实现的Java中提供的“接口”,就构成了不同数据库的“驱动”。

通过代码实现JDBC

public class TestJDBC {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stat = null;
        ResultSet set = null;
        
        try {
            //[1]加载JDBC驱动
//          Driver driver = new orcale.driver.jdbc.orcaleDriver();
            /*
             * Class.forName()方法会通过你提供的类路径找到这个类并加载进入虚拟机
             * 最重要的是,这个过程不需要实例化对象
             */
            Class.forName("oracle.jdbc.driver.OracleDriver");
            
            //[2]创建数据库连接对象
            String url = "jdbc:orcale:thin:@localhost:1521:orcl";
            String username = "数据库用户名";
            String password = "数据库密码";

            conn = DriverManager.getConnection(url, username, password);

            //[3]创建Statement对象,编写SQL语句
            String sql = "select * from employees";  //注意:此时SQL语句的结尾不要有字符的分号
            stat = conn.createStatement();

            //[4]通过Statement对象执行SQL语句,得到一个ResultSet结果集
            set = stat.executeQuery(sql);

            //[5]获取结果集中的数据,分析数据
            /*
             * 比较ResultSet和Iterator中的next()方法:
             * Iterator:
             *  在迭代器中,hasNext()方法返回boolean值,专门用来判断是否还有下一条记录
             *  但是不会移动记录的位指针
             *  迭代器中的next()方法,不仅负责向下移动位指针,还负责将下一条记录进行返回
             * 
             * ResultSet:
             *  ResultSet的next()方法即负责向下判断是否还存在下一条记录
             *  也负责在存在下一条记录的时候,移动位指针,返回下一条记录
             * 
             * ResultSet.next() = Iterator.hasNext() + Iterator.next()
             */
            while(set.next()) {  //使用while循环负责遍历记录
                //使用下面的7条代码负责遍历一条记录中的7个字段
                System.out.print(set.getInt("emp_id") + ", ");  //emp_id
                System.out.print(set.getString("emp_name") + ", ");  //emp_name
                System.out.print(set.getString("emp_gender") + ", ");  //emp_gender
                System.out.print(set.getDouble("emp_salary") + ", ");  //emp_salary
                System.out.print(set.getDate("emp_birth") + ", ");  //emp_birth
                System.out.print(set.getDouble("commission_pct") + ", ");  //commission_pct
                System.out.println(set.getInt("dept_id"));  //dept_id
            }
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            //[6]关闭结果集对象,关闭Statement对象,关闭连接对象
            try {
                if(set != null) {  //关闭ResultSet
                    set.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(stat != null) {  //关闭Statement
                    stat.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(conn != null) {  //关闭Connection
                    conn.close();
                }
            } catch (SQLException e) {
                    e.printStackTrace();
            }
        }
    }
}

JDBC的改进需求

  1. 注册JDBC驱动:
    将Driver驱动路径提取出来,当做一个独立的字符串使用。
  2. 创建数据库连接对象Connection对象:
    将数据库连接的URL、用户名、密码等信息写在一个外部文件中,每次连接数据库,创建Connection对象之前都读取这个外部文件,从外部文件动态的获取数据库连接信息。
  3. 创建Statement对象,编写SQL语句:
    Statement对象在执行SQL语句的时候,容易引起“SQL注入”,使用PreparedStatement对象替换Statement对象。
  4. 执行SQL,得到结果集ResultSet:
  5. 分析结果集,处理数据:
    当前查询结果集记录的方式,将每一条记录的每一个字段独立出来,互相之间没有关系,我们应该创建一个POJO类,将每一条记录封装为这个POJO类的一个对象,当前记录的字段取值就是POJO对象的属性值。
  6. 关闭结果集、关闭Statement、关闭连接对象Connection:
    关闭过程可以简化为关闭连接对象一个步骤。

JDBC改进的代码实现

Properties配置文件的读写:

  1. 创建一个.properties类型的文件
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:orcale:thin:@localhost:1521:orcl
jdbc.username=数据库用户名
jdbc.password=数据库密码
  1. 在Java代码中创建Properties对象,实现对外存资源文件的加载:
Properties pro = new Properties();
//将处于源码包中(src文件夹)中的配置文件以Properties流的方式进行返回,读入JVM内存中
InputStream is = TestJDBC.class.getResourceAsStream("/jdbc.properties");
//将流中保存的键值对信息读入Properties对象中,顺便解析成为键值对
pro.load(is);
  1. 从Properties对象中获取配置键值对:
//通过键找到值,找到的值就是配置信息
jdbcDriver = pro.getProperty("jdbc.driver");
jdbcUrl = pro.getProperty("jdbc.url");
jdbcUsername = pro.getProperty("jdbc.username");
jdbcPassword = pro.getProperty("jdbc.password");

使用PreparedStatement替换Statement对象:

  1. 演示Statement执行带有参数的SQL语句引起SQL注入:
//[3]创建Statement对象,编写SQL语句
String arg = "8000 or 1=1";  //这是一个查询参数
String sql = "select * from employees where emp_salary > " + arg;
stat = conn.createStatement();
//[4]通过Statement对象执行SQL语句,得到一个ResultSet结果集
set = stat.executeQuery(sql);
  1. 分析上述代码引起SQL注入的原因:
    1. 在查询条件中,我们强制注入了一个永真的条件。
    2. 现在SQL语句传递参数值的方式是拼接字符串,字符串本身没有识别SQL注入的功能。
  2. PreparedStatement对象的使用:
PreparedStatement stat = null;
//String arg = "8000 or 1=1";  //这是一个查询参数
String sql = "select * from employees where emp_salary > ?";  //在SQL语句中,所有的条件下,都使用?作为参数占位符
stat = conn.prepareStatement(sql);  //此时stat对象的来源已经是通过conn预编译得到
//[4]通过Statement对象执行SQL语句,得到一个ResultSet结果集
stat.setDouble(1, 8000.0);  //按照参数占位符的序号,为所有的?赋予参数
set = stat.executeQuery();  //注意:在使用PreparedStatement对象执行SQL语句的时候,执行方法中不要再次传递SQL语句
  1. 总结PreparedStatement的优点:
    1. PreparedStatement对SQL中的参数占位符进行预编译,所有传递进来的参数都当做一个整体看待。
      从根本上杜绝了SQL注入的原因——PreparedStatement不会引起SQL注入。
    2. PreparedStatement在对SQL执行预编译的时候,会将SQL语句存放在内存中,便于多次访问和执行这句SQL,不需要每次都重新加载SQL——PreparedStatement的SQL执行效率更高。

通过POJO对象封装查询结果:
1.回忆数据库中概念和Java中概念的对应关系:
JDBC、封装JDBC连接池、第三方连接池工具_第1张图片
2.代码实现:
创建和数据表表名、字段对应的POJO类:

public class Employee implements Serializable {
    private static final long serialVersionUID = -2675232990254926945L;

    //在POJO类当中,所有的字段类型推荐使用包装类类型
    private Integer empId;  //注意:在POJO类属性当中,所有的属性都不使用_(下划线),下划线是两个单词的界定,Java中的属性名使用驼峰命名法
    private String empName;
    private String empGender;
    private Double empSalary;
    private Date empBirth;
    private Double commissionPct;
    private Integer deptId;
	//空构造
    public Employee() {
        super();
    }
	//有参构造
    public Employee(Integer empId, String empName, String empGender, Double empSalary, Date empBirth,
            Double commissionPct, Integer deptId) {
        super();
        this.empId = empId;
        this.empName = empName;
        this.empGender = empGender;
        this.empSalary = empSalary;
        this.empBirth = empBirth;
        this.commissionPct = commissionPct;
        this.deptId = deptId;
    }
	//公有的getset方法
    public Integer getEmpId() {
        return empId;
    }

    /*
     * 属性的set方法一定注意命名格式:驼峰命名法
     * 属性:EmpId -> setEmpId -> empId
     * 要求:所有属性的首字母小写
     * 
     * 关于属性的get方法
     * boolean类型的属性值不要用is开头
     * Boolean isWorking -> isWorking() -> working
     */
    public void setEmpId(Integer empId) {
        this.empId = empId;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public String getEmpGender() {
        return empGender;
    }
    public void setEmpGender(String empGender) {
        this.empGender = empGender;
    }
    public Double getEmpSalary() {
        return empSalary;
    }
    public void setEmpSalary(Double empSalary) {
        this.empSalary = empSalary;
    }
    public Date getEmpBirth() {
        return empBirth;
    }
    public void setEmpBirth(Date empBirth) {
        this.empBirth = empBirth;
    }
    public Double getCommissionPct() {
        return commissionPct;
    }
    public void setCommissionPct(Double commissionPct) {
        this.commissionPct = commissionPct;
    }
    public Integer getDeptId() {
        return deptId;
    }
    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }
    
    //重写hashCode和equals方法
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((commissionPct == null) ? 0 : commissionPct.hashCode());
        result = prime * result + ((deptId == null) ? 0 : deptId.hashCode());
        result = prime * result + ((empBirth == null) ? 0 : empBirth.hashCode());
        result = prime * result + ((empGender == null) ? 0 : empGender.hashCode());
        result = prime * result + ((empId == null) ? 0 : empId.hashCode());
        result = prime * result + ((empName == null) ? 0 : empName.hashCode());
        result = prime * result + ((empSalary == null) ? 0 : empSalary.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (commissionPct == null) {
            if (other.commissionPct != null)
                return false;
        } else if (!commissionPct.equals(other.commissionPct))
            return false;
        if (deptId == null) {
            if (other.deptId != null)
                return false;
        } else if (!deptId.equals(other.deptId))
            return false;
        if (empBirth == null) {
            if (other.empBirth != null)
                return false;
        } else if (!empBirth.equals(other.empBirth))
            return false;
        if (empGender == null) {
            if (other.empGender != null)
                return false;
        } else if (!empGender.equals(other.empGender))
            return false;
        if (empId == null) {
            if (other.empId != null)
                return false;
        } else if (!empId.equals(other.empId))
            return false;
        if (empName == null) {
            if (other.empName != null)
                return false;
        } else if (!empName.equals(other.empName))
            return false;
        if (empSalary == null) {
            if (other.empSalary != null)
                return false;
        } else if (!empSalary.equals(other.empSalary))
            return false;
        return true;
    }

	//重写toString方法
    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + ", empGender=" + empGender + ", empSalary="
                + empSalary + ", empBirth=" + empBirth + ", commissionPct=" + commissionPct + ", deptId=" + deptId
                + "]";
    }
}

注意:

  1. 给所有的POJO对象都保留一个空的构造器
  2. 注意属性的驼峰命名:
    2.1所有属性的命名,首字母小写
    2.2Boolean类型的属性不要使用is开头
  3. 可以让POJO类实现Serializable接口,使用serialVersionUID属性唯一标识这个POJO类型。

解析ResultSet,将一条记录封装成为一个POJO对象:

List<Employee> empList = new LinkedList<Employee>();
Employee emp = null;
//[5]获取结果集中的数据,分析数据
while(set.next()) {  //使用while循环负责遍历记录
    //每一次while循环都得到一条数据库记录,我们的目的是将这一条记录包装成一个POJO对象
    //set.getXXX()得到字段值 -> pojo.setXXX()赋给对应属性的set方法
    emp = new Employee();
    //装载POJO对象的过程就是获取一条记录中所有字段的过程
    emp.setEmpId(set.getInt("emp_id"));  //emp_id -> empId
    emp.setEmpName(set.getString("emp_name"));  //emp_name -> empName
    emp.setEmpGender(set.getString("emp_gender"));  //emp_gender -> empGender
    emp.setEmpSalary(set.getDouble("emp_salary"));  //emp_salary -> empSalary
    emp.setEmpBirth(set.getDate("emp_birth"));  //emp_birth -> empBirth
    emp.setCommissionPct(set.getDouble("commission_pct"));  //commission_pct -> commissionPct
    emp.setDeptId(set.getInt("dept_id"));  //dept_id -> deptId

    empList.add(emp);
}
empList = new ArrayList<>(empList);  //将善于增删的LinkedList转换位善于遍历的ArrayList
for (Employee employee : empList) {
    System.out.println(employee);
}

关闭数据库连接资源的问题:
1.Connection、PreparedStatement、ResultSet对象之间的关系:

  • Connection -预编译-> PreparedStatement -执行SQL-> ResultSet,其中根本是Connection对象

结论:

  • 在关闭数据库连接的时候,直接关闭Connection对象,就能够连带关闭PreparedStatement对象和ResultSet对象

2.代码改进:

finally {
    //[6]关闭结果集对象,关闭Statement对象,关闭连接对象
     try {
        if(conn != null) {  //关闭Connection
            conn.close();
        }
     } catch (SQLException e) {
        e.printStackTrace();
     }
}

JDBC使用的设计模式

1.静态代理模式
嘉靖皇帝,不上朝,不理政,除了吃喝玩乐,什么都不会
现在掌管朝政大权的是大臣严嵩
突然有一天,上来奏折,说南方有叛军起义,还有洪水泛滥,还有干旱,田地颗粒无收,现在已经天下大乱
嘉靖皇帝着急了……
皇帝现在不会理政,但是还要处理洪水问题、叛乱问题、干旱问题
于是乎皇帝找到严嵩老臣,向他请教……
严嵩说:咱们这么这么这么办……
皇帝说:OK,没毛病,下诏书!
问题:从明面上来说,治理天下的是谁?真正干活的是谁?
治理天下:皇帝
真正干活:严嵩(皇帝的代理核心)
有一天,皇帝说,我要娶媳妇!
宣布娶媳妇:皇帝
真正娶媳妇:皇帝

class 皇帝 {
    严嵩 严嵩 = new 严嵩();
    public void 治理叛军() {
        严嵩.治理叛军();
    }

    public void 治理洪水() {
        严嵩.治理洪水();
    }

    public void 治理干旱() {
        严嵩.治理干旱();
    }

    public void 娶媳妇() {
        皇帝自己娶媳妇;
    }
}

上述设计模式就是静态代理模式:

  • 代理对象(皇帝)
  • 代理核心(严嵩)

皇帝不会的事情,都交给严嵩去做
也就是说在代理对象中,无法完成的方法,交给代理核心完成
在代理对象中,代理对象自己能够执行的方法,交给代理对象自己完成

封装连接池

连接池的构建:
连接池的主要功能:

 /*
     * 连接池的功能:
     * 1.初始化连接池:
     * 创建20个JDBC4Connection对象,将JDBC4Connection对象封装成MyConnection对象
     * 封装成MyConnection对象之后,就能够保证在调用close()方法的时候是回收连接,而不是释放连接
     * 将20个MyConnection对象放在freeConns集合当中,作为闲置连接等待调用
     * 
     * 2.分配连接:
     * 当有DAO执行方法申请连接对象的时候,为这个DAO方法分配MyConnection对象
     * 如果freeConns中还有闲置连接,从这些闲置;连接中获取第一个(取栈顶元素)
     * 交给DAO方法使用
     * 将这个正在被占用的MyConnection对象加入activConns集合中,代表被占用
     * 
     * 3.回收连接:
     * 回收连接的方法非常简单,就是调用MyConnection中的close()方法即可
     * 将当前被释放的连接从activeConns中删除
     * 加入到freeConns集合中(元素入栈)
     */

初始化连接池:

 //1.初始化连接池:
    static {
        try {
            //[1.1]读取配置文件信息
            Properties pro = new Properties();
            InputStream is = ConnectionFactory.class.getResourceAsStream("/jdbc.properties");
            pro.load(is);
            jdbcDriver = pro.getProperty("jdbc.driver");
	        jdbcUrl = pro.getProperty("jdbc.url");
            jdbcUsername = pro.getProperty("jdbc.username");
            jdbcPassword = pro.getProperty("jdbc.password");
            jdbcAutoCommit = pro.getProperty("jdbc.autocommit");
            jdbcMaxConn = pro.getProperty("jdbc.maxconn");
            jdbcTimeout = pro.getProperty("jdbc.timeout");
            jdbcTryTime = pro.getProperty("jdbc.trytime");
            //[1.2]注册驱动
            Class.forName(jdbcDriver);
            System.out.println("数据库驱动注册成功");
            
            /*
             * [1.3]通过DriverManager获取20个MySQL提供的JDBC4Connection对象,作为代理核心使用
             * 将代理核心对象封装成MyConnection对象,加入freeConns集合
             */
            for(int i = 0; i < Integer.parseInt(jdbcMaxConn); i++) {

                //1.创建代理核心
                Connection jdbc4conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
                jdbc4conn.setAutoCommit(Boolean.parseBoolean(jdbcAutoCommit));

                //2.封装代理对象MyConnection对象
                MyConnection mconn = new MyConnection(jdbc4conn);

                //3.加入闲置连接集合
                freeConns.push(mconn);
            }

        }catch(Exception e) {
            e.printStackTrace();
            System.out.println("数据库驱动注册失败");
        }
    }

分配连接:

//2.分配连接
    public static Connection getConnection() {
        Connection conn = null;

        //[1]从闲置连接集合中选择一个闲置连接,出栈
        if(!freeConns.isEmpty()) {
            conn = freeConns.pop();
            //[2]将这个出栈的连接对象加入占用连接集合
            activConns.add(conn);

            System.out.println("连接分配成功");
        }else {  //没有闲置连接

            //如果没有得到连接对象,那就等待1000毫秒,再次重试
            for(int i = 0; i < Integer.parseInt(jdbcTryTime); i++) {
                try {
                    Thread.sleep(Integer.parseInt(jdbcTimeout));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(!freeConns.isEmpty()) {  //重新尝试获取连接
                    conn = freeConns.pop();
                    activConns.add(conn);
                    System.out.println("连接分配成功");
                }
                if(conn != null) {  //尝试成功,获取对象
                    break;
                }
            }
            //如果重试3次之后,还没有得到连接,直接返回null的conn对象
        }
        //[3]返回这个连接对象
        if(conn == null) {
            System.out.println("连接分配失败");
        }
        return conn;
    }

回收连接:

//3.回收连接
        @Override
        public void close() throws SQLException {
            //TODO:回收连接的方法
            //只要更改这个close()方法即可,使得调用这个方法的时候不是销毁连接对象,而是将连接对象归还连接池
            //[1]将被占用的连接释放
            activConns.remove(this);

            //[2]将这个连接加入空闲连接栈的栈顶

            freeConns.push(this);

            System.out.println("连接已释放");
        }

封装JDBC连接池

使用连接池的方案:
创建一个常备的连接池,其中保持一定数量的常备连接,DAO需要访问数据库的时候,就从这个连接池中获取连接对象,用过之后不是释放连接,而是将这个连接对象返回给连接池,保证连接的重用,可以提高数据库的运行效率

连接对象的线程安全问题:
连接对象Connection本身是线程不安全的
如果两个线程公用同一个Connection对象,将会导致数据的不安全

解决方案:
为分配连接对象和连接对象所有的方法都加上同步锁
注意:

  • 为了保证线程之间对Connection对象的绝对安全,需要为同一Connection对象中的全部方法加锁

这会导致:

  1. 导致代码的修改程度相对较高
  2. 线程之间的共享效率下降

线程安全性问题的根源是什么:

  1. 多个线程之间共享同一个资源对象,构成资源竞争
  2. 如果对共享资源不加锁,将会导致数据的脏读写
  3. 数据的脏读写就是线程的安全性问题

解决方案:

  1. 为每一个线程都创建一个Connection对象的副本
  2. 让每一个线程都重复利用这个一个副本,自己跑自己的
  3. 只要线程之间不构成资源共享,就从根本上杜绝了线程安全问题

问题:

  • 如何将一个Connection对象和一个Thread线程进行“绑定”——ThreadLocal

ThreadLoacl的使用

ThreadLocal示意图:
JDBC、封装JDBC连接池、第三方连接池工具_第2张图片

ThreadLocal常见方法

  • initialValue():
    提供第一次访问当前ThreadLocal的线程一个共享资源拷贝副本,一个protected修饰的方法,用来在子类继承ThreadLocal的时候进行重写
    在默认情况下,initalValue()方法返回的默认值是null,如果将这个方法进行重写的话,可以用这个方法返回一个默认值。

  • get():
    返回当前ThreadLocal中与当前线程绑定的一个共享资源的副本,在当前ThreadLocal对象中有一个共享资源的原本,当每次调用这个get()方法的时候,ThreadLocal会为调用这个get()的线程分配一个共享资源原本的拷贝版本,并将这个拷贝版本交给调用get()方法的线程,保存在线程的Map当中。
    get()方法有一个前提,就是在ThreadLocal中已经存在一个与当前线程绑定的资源对象,如果一个线程第一次调用get()方法,将会返回一个null,如果一个ThreadLocal已经重写的initialValue()方法,那么此时将会返回initalValue()中声明的对象

  • set(T value):
    将共享资源的副本和调用set()方法的线程进行绑定,如果共享资源的副本已经存在,但是尚且没有线程进行访问,副本处于未绑定状态,此时如果有线程进行访问,可以通过set方法将资源副本和调用set方法的线程进行绑定,绑定过程就是创建一个键值对,并且将这个键值对存入当前线程的Map中的过程。
    **键:**ThreadLocal对象;**值:**和当期前线程绑定的资源副本

  • remove():
    释放掉与当前线程相关的资源副本,在连接池中,资源副本就是Connection对象,释放资源副本,就相当于连接用完了,要放回闲置连接池当中

第三方连接池工具(数据源)

作用:

  1. 创建、保存、分配、销毁连接
  2. 保证连接对象的线程安全性

常见的数据源:

  • C3P0
  • DBCP

C3P0数据源的使用方式:

public class C3P0ConnectionPool {
    private static ComboPooledDataSource cpds;
    static {
        try {

            /*
             * 在创建ComboPooledDataSource对象的时候,C3P0连接池会自动访问位于类路径下的文件名为c3p0.properties的那个文件
             * 只要这个文件存在,C3P0连接池就会自动访问其中的数据库配置,不必手动加载
             */
            cpds = new ComboPooledDataSource();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn = cpds.getConnection();
            conn.setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }
}

配置文件:

#驱动类路径
c3p0.driverClass=oracle.jdbc.driver.OracleDriver
#数据库访问地址URL
c3p0.jdbcUrl=jdbc:oracle:thin:@localhost:1521:orcl
#数据库用户名
c3p0.user=root
#数据库密码
c3p0.password=root
#连接池中保持常备连接的最大值
c3p0.maxPoolSize=20
#连接池中保持常备连接的最小值
c3p0.minPoolSize=5
#初始化连接池的时候,连接的数量,这个取值最好在minPoolSize和maxPoolSize之间取值
c3p0.initialPoolSize=5
#当连接池中常备连接耗尽之后,一次性增长多少连接
c3p0.acquireIncrement=5
#一个线程在无法获取闲置连接的情况下,能够重试的次数
c3p0.acquireRetryAttempts=30
#线程在无法获取闲置连接的时候,每次重试之间的时间间隔
c3p0.acquireRetryDelay=1000

策略模式

使用场景:
一段代码中,可以分为n多段,其中:

  • 前m段代码可以重用
  • 后i段代码可以重用

那么我们就将两边的代码抽取出来,构建成为一个模板方法,在这个模板方法中插入什么样的内容,执行的就是什么方法,策略模式适用于两端代码可以重用,中间代码可以动态替换的地方

模板类型:

  • 作用:将能够重用的重复代码写入模板方法中,在调用模板方法的地方发挥作用
public class DAOTemplate<T> {
    /**
     * 执行查询方法的模板
     */
    public List<T> selectTemplate(SelectExecutor exe) {
        Connection conn = null;
        List<T> resultList = null;
        try {
            conn = C3P0ConnectionPool.getConnection();
            //----------从这一句向上,都是模板的上半部分----------
            //----------中间插入动态替换的SQL执行过程
            resultList = exe.execute(conn);
            //----------从这一句向下,都是模板的下半部分----------
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            if(conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return resultList;
    }
    /**
     * 执行增删改方法的模板
     */
    public int updateTemplate(UpdateExecutor exe) {

        Connection conn = null;
        int result = 0;
        try {

            conn = C3P0ConnectionPool.getConnection();

            //----------从这一句向上,都是模板的上半部分----------
            //----------中间插入动态替换的SQL执行过程
            result = exe.execute(conn);

            //----------从这一句向下,都是模板的下半部分----------
            conn.commit();
        }catch(Exception e) {
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            if(conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

执行器类:

  • 作用:相当于汉堡的肉馅,每一个执行器的执行方法都是一段可以在模板中动态替换的代码,我们通过更换执行器类的对象,动态替换执行的代码,起到重用模板,替换执行核心内容的作用
public interface SelectExecutor<T> {
    public List<T> execute(Connection conn) throws Exception;
}
public interface UpdateExecutor {
    public int execute(Connection conn) throws Exception;
}

使用策略模式实现的DAO类型:
作用:

  • 将所有DAO方法中重复的部分都使用模板方法代替
public class EmployeeDAOImpl implements EmployeeDAO {

    private DAOTemplate<Employee> template = new DAOTemplate<>();

    @Override
    public List<Employee> selectAllEmployee() {
        SelectExecutor<Employee> exe = new SelectExecutor<Employee>() {

            @Override
            public List<Employee> execute(Connection conn) throws Exception {
                PreparedStatement stat = null;
                ResultSet set = null;
                List<Employee> empList = new ArrayList<Employee>();

                //[3]编写SQL,预编译PreparedStatement
                String sql = "select * from employees";
                stat = conn.prepareStatement(sql);

                //[4]执行SQL,得到结果集
                set = stat.executeQuery();

                //[5]分析结果集,得到数据
                Employee emp = null;
                while(set.next()) {  //从数据库记录 -> pojo对象

                    emp = new Employee();

                    emp.setEmpId(set.getInt("emp_id"));
                    emp.setEmpName(set.getString("emp_name"));
                    emp.setEmpGender(set.getString("emp_gender"));
                    emp.setEmpSalary(set.getDouble("emp_salary"));
                    emp.setEmpBirth(set.getDate("emp_birth"));
                    emp.setCommissionPct(set.getDouble("commission_pct"));
                    emp.setDeptId(set.getInt("dept_id"));

                    empList.add(emp);
                }
                return empList;
            }
        };
        return template.selectTemplate(exe);
    }

    @Override
    public Employee selectEmployeeById(Integer empId) {

        List<Employee> list = template.selectTemplate(new SelectExecutor<Employee>() {

            @Override
            public List<Employee> execute(Connection conn) throws Exception {

                PreparedStatement stat = null;
                ResultSet set = null;

                List<Employee> empList = new LinkedList<Employee>();

                //[3]编写SQL,预编译PreparedStatement
                String sql = "select * from employees where emp_id = ?";
                stat = conn.prepareStatement(sql);

                //[4]执行SQL,得到结果集
                stat.setInt(1, empId);
                set = stat.executeQuery();  //ResultSet中只有1条记录

                //[5]分析结果集,得到数据
                Employee emp = null;
                if(set.first()) {  //如果当前结果集当中存在至少一条记录,那么set.first()将会返回true,并且将记录的位指针指向第一条记录
                    emp = new Employee();
                    emp.setEmpId(set.getInt("emp_id"));
                    emp.setEmpName(set.getString("emp_name"));
                    emp.setEmpGender(set.getString("emp_gender"));
                    emp.setEmpSalary(set.getDouble("emp_salary"));
                    emp.setEmpBirth(set.getDate("emp_birth"));
                    emp.setCommissionPct(set.getDouble("commission_pct"));
                    emp.setDeptId(set.getInt("dept_id"));

                    empList.add(emp);
                }
                return empList;
            }
        });
        return list.get(0);
    }

    @Override
    public Integer insertEmployee(Employee emp) {
        return template.updateTemplate(new UpdateExecutor() {
            @Override
            public int execute(Connection conn) throws Exception {
                PreparedStatement stat = null;
                int result = 0;  //代表当前增删改方法影响的行数

                //对于增删改操作来讲,我们需要开启事务,提交事务,如果遇见异常,我们还要回滚事务

                //[3]编写SQL,预编译
                String sql = "insert into Employees "
                        + "(emp_name, emp_gender, emp_salary, emp_birth, commission_pct, dept_id) "
                        + "values (?, ?, ?, ?, ?, ?)";
                stat = conn.prepareStatement(sql);

                //[4]设定参数,执行SQL,得到结果影响行数:pojo对象 -> 数据库记录
                stat.setString(1, emp.getEmpName());
                stat.setString(2, emp.getEmpGender());
                stat.setDouble(3, emp.getEmpSalary());
                stat.setDate(4, emp.getEmpBirth());
                stat.setDouble(5, emp.getCommissionPct());
                stat.setInt(6, emp.getDeptId());

                result = stat.executeUpdate();

                //[5]分析影响行数
                return result;
            }
        });
    }

    @Override
    public Integer deleteEmployeeById(Integer empId) {
        return template.updateTemplate(new UpdateExecutor() {
            @Override
            public int execute(Connection conn) throws Exception {
                PreparedStatement stat = null;
                int result = 0;
                String sql = "delete from Employees where emp_id = ?";
                stat = conn.prepareStatement(sql);
                
                stat.setInt(1, empId);

                result = stat.executeUpdate();

                return result;
            }
        });
    }
    
    @Override
    public Integer updateEmployeeById(Employee emp) {
        return template.updateTemplate(new UpdateExecutor() {
            @Override
            public int execute(Connection conn) throws Exception {
                PreparedStatement stat = null;
                int result = 0;

                String sql = "update Employees set emp_name = ?, emp_gender = ?, emp_salary = ?, emp_birth = ?, "
                        + "commission_pct = ?, dept_id = ? where emp_id = ?";

                stat = conn.prepareStatement(sql);

                stat.setString(1, emp.getEmpName());
                stat.setString(2, emp.getEmpGender());
                stat.setDouble(3, emp.getEmpSalary());
                stat.setDate(4, emp.getEmpBirth());
                stat.setDouble(5, emp.getCommissionPct());
                stat.setInt(6, emp.getDeptId());

                stat.setInt(7, emp.getEmpId());

                stat.executeUpdate();

                return result;
            }
        });
    }
}

你可能感兴趣的:(jdbc,后端,java)