第21章 JDBC
1. 基础知识
Java连接数据库,是通过操作系统完成相应的连接,这种连接方式被称为桥连接。以操作系统为桥梁(比如在windows系统中利用数据源ODBC),连接Java程序和关系型数据库。局限性:应用程序不可跨平台。
JDBC推出后,可以利用Java程序直接访问关系型数据库。
- Java提供了访问数据库的一系列的接口声明
- 各个数据库厂商提供这些接口的实现类,通过这些实现类可以访问对应数据库(驱动)
- 通过连接发送SQL语句,数据库给出执行结果
访问网络中某个数据库,需要以下信息
- IP地址
- 数据库的端口号
- 数据库的账号和密码
- 数据库名
这些内容组成的“连接字符串”
2. 连接MySQL数据库
2.1 前置准备
- 打开MySQL数据库服务
-
在Java工程中,导入MySQL的驱动,让其参与编译
(1)将jar包复制工程的任意位置,建议新建一个lib的文件夹
(2)让jar包参与编译
右键工程,点击properties
点击“Java Build Path”,打开“Libraries”选项卡,点击右侧的“Add JARs”
操作示范
找到,并选择刚刚复制的jar包文件,点击OK
找到并选中
点击OK后,可以看到mysql的驱动已经参与到了编译环境中
操作示范
2.2 查询的示例
创建连接各个数据库的连接的代码,格式都是固定的
基本思路:
- 通过Class.forName("")加载驱动类
//1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
- 声明连接字符串和账号密码
//2.配置连接字符串
String url ="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
- 利用DriverManager创建数据库连接(Connection)
//3.创建数据库连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
4.声明SQL语句
//4.声明SQL语句
String sql = "select empno,ename,hiredate from emp";
5.加载和执行SQL语句
//5.加载SQL语句
Statement s = conn.createStatement();
//6.执行SQL语句,得到查询结果(结果集对象)
ResultSet rs = s.executeQuery(sql);
6.遍历结果集
while(rs.next()) {
//每执行一轮循环,遍历出查询结果的一行数据
int empno = rs.getInt("empno"); //取得整数类型的数据
String ename = rs.getString("ename"); //取得文本类型的数据
Date birthday = rs.getDate("hiredate"); //取得日期类型的数据
System.out.println(empno+"\t"+ename+"\t"+birthday);
}
7.关闭数据库连接
//8.关闭数据库连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
完整示例
public class Test {
public static void main(String[] args) {
//1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2.配置连接字符串
String url ="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
//3.创建数据库连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4.声明SQL语句
String sql = "select empno,ename,hiredate from emp";
try {
//5.加载SQL语句
Statement s = conn.createStatement();
//6.执行SQL语句,得到查询结果(结果集对象)
ResultSet rs = s.executeQuery(sql);
//7.遍历结果集
while(rs.next()) {
//每执行一轮循环,遍历出查询结果的一行数据
int empno = rs.getInt("empno");
String ename = rs.getString("ename");
Date birthday = rs.getDate("hiredate");
System.out.println(empno+"\t"+ename+"\t"+birthday);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//8.关闭数据库连接
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2.3 DML的示例
DML操作不需要遍历结果集,使用executeUpdate(sql)方法来完成DML操作,其返回结果为int类型,表示DML操作影响的数据行数。
public class Test2 {
public static void main(String[] args) {
// 1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.配置连接字符串
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
// 3.创建数据库连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 4.声明SQL语句
String sql = "insert into emp values (1015,'HAHA','CLERK',null,null,5000,0,10)";
try {
// 5.加载SQL语句
Statement s = conn.createStatement();
// 6.执行SQL语句,得到的结果表示此次DML操作影响了a行数据
int a = s.executeUpdate(sql);//增删改
System.out.println(a);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 8.关闭数据库连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2.4 预加载的示例
因为用户可以输入,输入了一个SQL片段后可能对原有的SQL语句逻辑造成破坏,从而达到非法的目的
数据库表
登录程序
public class Test1 {
public static void main(String[] args) {
//1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2.连接字符串
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
//3.创建数据库连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4.声明SQL语句
System.out.println("请输入账号:");
Scanner sc1 = new Scanner(System.in);
String lname = sc1.nextLine();
System.out.println("请输入密码:");
Scanner sc2 = new Scanner(System.in);
String lpass = sc2.nextLine();
String sql = "select * from haha where lname = '"+lname+"' and lpass = '"+lpass+"'";
System.out.println(sql);
//5.加载和执行SQL语句
try {
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery(sql);
if(rs.next()) {
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
在输入admin/123或者haha/456的情况可以正常登录
如果输入错误的账号和密码是无法正确登录
但:如果用户输入的账号是:admin,密码是:' or 1=1 and lname='admin
执行的SQL语句变成了
select * from haha where lname = 'admin' and lpass = '' or 1=1 and lname='admin'
逻辑被破坏,也能够正常登录成功,这种情况就是SQL注入
SQL注入带来了很大的系统风险,也是经常被黑产所利用,在编写程序需要控制SQL注入的问题。
利用预加载的statement来解决SQL注入问题。
预加载的statement的特点(与Statement相比)
- 更安全:防止SQL注入
- 更高效:在SQL服务器提供了预先的编译,对于同种结构的SQL语句执行效率更高
将要输入至SQL的参数,使用英文"?"替代
将刚才有问题的程序更换如下
public class Test1 {
public static void main(String[] args) {
//1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2.连接字符串
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
//3.创建数据库连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4.声明SQL语句
System.out.println("请输入账号:");
Scanner sc1 = new Scanner(System.in);
String lname = sc1.nextLine();
System.out.println("请输入密码:");
Scanner sc2 = new Scanner(System.in);
String lpass = sc2.nextLine();
String sql = "select * from haha where lname = ? and lpass = ?";
System.out.println(sql);
//5.加载和执行SQL语句
try {
PreparedStatement pst = conn.prepareStatement(sql);
//参数绑定:根据实际的数据类型进行设置
pst.setString(1, lname);
pst.setString(2, lpass);
ResultSet rs = pst.executeQuery();
if(rs.next()) {
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
建议所有的JDBC统一使用PreparedStatement完成
2.5 批处理的示例(了解)
利用批处理,对一批SQL语句统一的执行,主要用于大量数据的新增操作中,节省时间。
Statement st = conn.createStatement();
for(int i = 0; i < 99; i++) {
st.addBatch(sql); //将sql添加到批处理中
}
st.executeBatch(); //执行批次
优势:执行的速度较快
劣势:执行同样或者类似SQL