主要内容:

  • 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_idSystem.out.print(set.getString("emp_name") + ", ");  //emp_nameSystem.out.print(set.getString("emp_gender") + ", ");  //emp_genderSystem.out.print(set.getDouble("emp_salary") + ", ");  //emp_salarySystem.out.print(set.getDate("emp_birth") + ", ");  //emp_birthSystem.out.print(set.getDouble("commission_pct") + ", ");  //commission_pctSystem.out.println(set.getInt("dept_id"));  //dept_id}}catch(Exception e) {e.printStackTrace();}finally {//[6]关闭结果集对象,关闭Statement对象,关闭连接对象try {if(set != null) {  //关闭ResultSetset.close();}} catch (SQLException e) {e.printStackTrace();}try {if(stat != null) {  //关闭Statementstat.close();}} catch (SQLException e) {e.printStackTrace();}try {if(conn != null) {  //关闭Connectionconn.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中概念的对应关系:

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方法@Overridepublic 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;}@Overridepublic 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方法@Overridepublic 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 -> empIdemp.setEmpName(set.getString("emp_name"));  //emp_name -> empNameemp.setEmpGender(set.getString("emp_gender"));  //emp_gender -> empGenderemp.setEmpSalary(set.getDouble("emp_salary"));  //emp_salary -> empSalaryemp.setEmpBirth(set.getDate("emp_birth"));  //emp_birth -> empBirthemp.setCommissionPct(set.getDouble("commission_pct"));  //commission_pct -> commissionPctemp.setDeptId(set.getInt("dept_id"));  //dept_id -> deptIdempList.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) {  //关闭Connectionconn.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.回收连接@Overridepublic 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示意图:

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<>();@Overridepublic List<Employee> selectAllEmployee() {SelectExecutor<Employee> exe = new SelectExecutor<Employee>() {@Overridepublic List<Employee> execute(Connection conn) throws Exception {PreparedStatement stat = null;ResultSet set = null;List<Employee> empList = new ArrayList<Employee>();//[3]编写SQL,预编译PreparedStatementString 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);}@Overridepublic Employee selectEmployeeById(Integer empId) {List<Employee> list = template.selectTemplate(new SelectExecutor<Employee>() {@Overridepublic List<Employee> execute(Connection conn) throws Exception {PreparedStatement stat = null;ResultSet set = null;List<Employee> empList = new LinkedList<Employee>();//[3]编写SQL,预编译PreparedStatementString 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);}@Overridepublic Integer insertEmployee(Employee emp) {return template.updateTemplate(new UpdateExecutor() {@Overridepublic 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;}});}@Overridepublic Integer deleteEmployeeById(Integer empId) {return template.updateTemplate(new UpdateExecutor() {@Overridepublic 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;}});}@Overridepublic Integer updateEmployeeById(Employee emp) {return template.updateTemplate(new UpdateExecutor() {@Overridepublic 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、封装JDBC连接池、第三方连接池工具相关推荐

  1. jdbc封装mysql_用Java手动封装JDBC连接池(一)

    JDBC存在的问题 代码的冗余:在对数据库进行增删改查时,每个操作的JDBC流程和SQL执行代码的流程都一样,造成代码的冗余,所以我们可以把冗余的部分封装起来,封装之后,我们就不用再去写JDBC流程, ...

  2. JDBC:使用连接池管理连接

    2019独角兽企业重金招聘Python工程师标准>>> 一.数据库连接池 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出.对数据库连接的管理能显 ...

  3. JDBC、JDBC连接池、JDBCTemplate

    2019独角兽企业重金招聘Python工程师标准>>> JDBC: 1. 概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库* ...

  4. JDBC实例--JDBC连接池技术解密,连接池对我们不再陌生

    一.为什么我们要用连接池技术? 前面的数据库连接的建立及关闭资源的方法有些缺陷.统舱传统数据库访问方式:一次数据库访问对应一个物理连接,每次操作数据库都要打开.关闭该物理连接, 系统性能严重受损. 解 ...

  5. JDBC连接池c3p0连接异常和日志处理

    1.c3p0连接数据库由于时区没限制报出的错误 代码: package C3P0;import com.mchange.v2.c3p0.ComboPooledDataSource; import co ...

  6. java数据库配置_java--数据库(文件配置连接,自定义连接池)

    import java.util.ResourceBundle; public class Mtest7Demo { //使用properties配置文件完成数据库的连接 /* * 开发中获得连接的4 ...

  7. JDBCC3P0连接池Druid连接池

    typora-root-url: img typora-copy-images-to: img JDBC&连接池 回顾 会使用mysql字符串函数 CONCAT: 连接字符串 CHAR_LEN ...

  8. java中常用的连接池_java数据库连接池

    编写标准的数据源(规范) Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口.这样应用程序可以方便的切换不同厂商的连接池! 常见的 ...

  9. 数据库连接池-连接的关闭内幕

    们经常会遇到这样那样的连接未关闭的问题,连接没有及时关闭导致的直接后果就是内存泄漏直至down机.我们也都知道解决的方式,但是在解决了问题之后经常会思考为什么会这样呢?连接close()掉,然后在创建 ...

最新文章

  1. Java8 中有趣酷炫的小技巧
  2. 感恩节里我成了一个不解风情的爸爸
  3. 智能车竞赛技术报告 | 单车拉力组 - 沈阳航空航天大学 - 青梅绿茶队
  4. C语言入门经典——基础知识(数据类型)(32位与64位系统基本数据类型的字节数)
  5. 双缝干涉试验为什么恐怖?
  6. C# TreeNode的使用方法
  7. unity3d学习笔记(一)-在一个GameObject上进行多个AudioSource的控制
  8. 【Linux进程、线程、任务调度】一 Linux进程生命周期 僵尸进程的含义 停止状态与作业控制 内存泄漏的真实含义 task_struct以及task_struct之间的关系
  9. 关于统计时间切片标签的一些sql
  10. Google Chrome 调试JS简单教程[更新]
  11. mysql之delete删除记录后数据库大小不变
  12. python起简易http server
  13. Mac版本Jmeter下载安装教程
  14. TouchDesigner案例(十)缤纷
  15. 自恢复保险丝工作原理
  16. linux大业内存,linux 内存占用过大分析
  17. 德日车企瓜分美系车在华市场
  18. DTS、杜比2.0、杜比5.1、AC3各是什么意思
  19. 直流输入过压保护电路
  20. PHPCMS留言板制作

热门文章

  1. STM32滤波电容个数和大小的确定
  2. uni-app 启动广告页
  3. 关于数据治理的读书笔记 - 企业数据治理的“道、法、术、器”
  4. canvas 绘制七巧板
  5. 微信公共号---LBS的开发
  6. 对标测评YD云电脑和天翼云电脑公众版
  7. 05【数据的备份与恢复】
  8. idea为web项目添加tomcat并配置Artifacts
  9. 应用程序存在文件包含漏洞(Unix系统) WASC Threat Classification 解决办法
  10. Python中的sys中的stdout