主要内容:
JDBC简介
全称:Java DataBase Connection
译名:Java数据库连接
JDBC来源
JDBC的来源:
最早在Java提出连接数据库需求的时候,Java的做法是:Java根据不同数据库的特点,针对每一种数据库都提出了一套代码,在使用不同的数据库和Java进行连接的时候,使用不同的代码。
缺点:
- Java程序构建相当复杂,因为程序员要记住多种数据库连接代码,有多少种数据库,就要有多少种连接方式。
- Java代码的可重用性相当低,导致Java对数据库连接程序的开发效率下降,后来Java提出了新的思路:在Java中,我们提出了一套“标准(接口)”,将这套接口的实现交给不同的数据库厂商来完成。
优点:
- 程序员的工作压力下降,不管使用哪一种数据库连接,使用的接口类型都是一样的
- 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的改进需求
- 注册JDBC驱动:
将Driver驱动路径提取出来,当做一个独立的字符串使用。 - 创建数据库连接对象Connection对象:
将数据库连接的URL、用户名、密码等信息写在一个外部文件中,每次连接数据库,创建Connection对象之前都读取这个外部文件,从外部文件动态的获取数据库连接信息。 - 创建Statement对象,编写SQL语句:
Statement对象在执行SQL语句的时候,容易引起“SQL注入”,使用PreparedStatement对象替换Statement对象。 - 执行SQL,得到结果集ResultSet:
- 分析结果集,处理数据:
当前查询结果集记录的方式,将每一条记录的每一个字段独立出来,互相之间没有关系,我们应该创建一个POJO类,将每一条记录封装为这个POJO类的一个对象,当前记录的字段取值就是POJO对象的属性值。 - 关闭结果集、关闭Statement、关闭连接对象Connection:
关闭过程可以简化为关闭连接对象一个步骤。
JDBC改进的代码实现
Properties配置文件的读写:
- 创建一个.properties类型的文件
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:orcale:thin:@localhost:1521:orcl
jdbc.username=数据库用户名
jdbc.password=数据库密码
- 在Java代码中创建Properties对象,实现对外存资源文件的加载:
Properties pro = new Properties();
//将处于源码包中(src文件夹)中的配置文件以Properties流的方式进行返回,读入JVM内存中
InputStream is = TestJDBC.class.getResourceAsStream("/jdbc.properties");
//将流中保存的键值对信息读入Properties对象中,顺便解析成为键值对
pro.load(is);
- 从Properties对象中获取配置键值对:
//通过键找到值,找到的值就是配置信息
jdbcDriver = pro.getProperty("jdbc.driver");
jdbcUrl = pro.getProperty("jdbc.url");
jdbcUsername = pro.getProperty("jdbc.username");
jdbcPassword = pro.getProperty("jdbc.password");
使用PreparedStatement替换Statement对象:
- 演示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);
- 分析上述代码引起SQL注入的原因:
- 在查询条件中,我们强制注入了一个永真的条件。
- 现在SQL语句传递参数值的方式是拼接字符串,字符串本身没有识别SQL注入的功能。
- 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语句
- 总结PreparedStatement的优点:
- PreparedStatement对SQL中的参数占位符进行预编译,所有传递进来的参数都当做一个整体看待。
从根本上杜绝了SQL注入的原因——PreparedStatement不会引起SQL注入。 - PreparedStatement在对SQL执行预编译的时候,会将SQL语句存放在内存中,便于多次访问和执行这句SQL,不需要每次都重新加载SQL——PreparedStatement的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;