JDBC
1.什么是JDBC?
JDBC:Java Database Connectivity的缩写,即Java数据库连接。
Java语言通过JDBC来操作数据库。
在没有JDBC这套接口之前,因为每个数据库的底层原理都是不一样的,所以每个数据库都要实现一套代码,这时候SUN公司就站出来了,写了一套JDBC的接口,各个数据库厂家,如MySQL、Oracle等等纷纷实现了这套接口,Java程序员就去调用各个数据库厂家实现的接口,想要哪个数据库,就调用哪个,而不需要管底层的实现原理。
SUN公司写了一套接口,java.sql.*;
各个数据库厂家实现这套接口,即数据库驱动,以jar包的形式存在,需要自己下载;
Java程序员通过反射获取需要的数据库对象,然后通过数据库对象来调用接口中的方法。
2.如何使用JDBC?
1.首先要注册驱动,即告诉JVM,要连接的数据库是哪套数据库;
2.获取连接,将JVM的进程和数据库的进程的通道打开;
3.获取数据库操作对象;
4.执行sql语句;
5.(如果是查询,则需要遍历处理结果集)
6.关闭资源(先判断,再从小到大的去关闭)
(1)注册驱动
在注册驱动前,需要先导入相关的jar包,jar包可以去网上下载。
在java.sql.*下有一个DriverManeger类,其中有一个方法:
static void registerDriver(Driver driver) :用于注册给定的驱动。
但是参数Driver是一个接口,所以我们需要使用其子类:com.mysql.jdbc.Driver,即com.mysql.jdbc包下的Driver类
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
但是我们点进去Driver的源码可以发现:
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
其源码已经实现了注册驱动的操作,而可以通过反射去实现只执行一个类中的静态代码:
package com.dh.jdbc;
public class JDBC01 {
public static void main(String[] args) {
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
(2)获取数据库连接
在DriverManager类中,有一个获取连接的方法:
static Connection getConnection(String url, String user, String password) ;
其中url为统一资源定位符,由协议+ip+端口+资源名组成,即:jdbc:mysql://localhost:3306/要连接的数据库名;
user和password分别为数据库的用户名和密码。
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/practice","root","root");
(3)获取数据库操作对象
Connection接口中有一个方法,可以用于发送sql语句:
Statement createStatement();
//3.获得执行者对象
Statement statement = conn.createStatement();
(4)执行sql语句
首先要书写sql语句,然后调用Statement接口中的方法去执行sql语句:
//4.执行sql
String sql = "";
//DCL
ResultSet resultSet = statement.executeQuery(sql);//返回查询结果集
//DML
int i = statement.executeUpdate(sql);//返回影响数据库中数据的条数
(5)处理结果集
当执行的语句是DQL时,就需要处理查询出来的结果集,在JDBC中,使用ResultSet接口来接收:
//5.处理结果集
while(resultSet.next()){//获取一行
resultSet.getInt("empno");//获取一行中的某一个字段值,可以是字段名(如果有别名,则应该是别名),以及字段所在的列数,列数从1开始
}
(6)关闭资源
在上述中有提到,需要打开JVM进程和数据库进程间的通道,这是重量级的,所以必须要关闭资源。
在关闭资源前,需要先判断是否需要关闭,即判断是否为null,关闭也要按照从小到大的原则去关闭;
在finally子句中关闭:
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
3.sql注入
假设现在有一个登陆系统,用户输入用户名和密码,连接数据库进行查询,如果存在该用户,并且密码也一致的话,就登陆成功:
假设有一张用户表user,里面有两个字段:用户名字段username和密码字段password;有一条记录:用户名为admin,密码为123
我们在执行sql语句时,应该先拿到用户名和密码两个参数,假设有不法分子知道了用户的用户名,但是他不知道用户的密码,但是该不法分子又懂得sql语句,那么此时他就可以想办法了:
输入密码为:111' or '1' = '1,那么sql语句就会变为:(111可为任意字符)
select * from user where username = 'admin' and password = '111' or '1' = '1';
而上述sql语句是永远成立的。
而根本的原因就算,用户输入的信息中含有sql语句的关键字,而这些关键字还参与了sql语句的编译。
解决sql注入的问题:
Statement有一个子接口,PreparedStatement,就可以解决sql注入的问题。
PreparedStatement采用预编译的方式,先向DBMS发送一个sql语句的框架,然后在sql语句中使用占位符,传递参数:
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,50);//第一个占位符为参数的顺序,从1开始,第二个参数为值
//DML
ps.executeUpdate();
//DCL
ps.executeQuery();
Statement和PreparedStatement的对比:
- Statement会产生sql注入的问题,PreparedStatement不会产生sql注入的问题;
- PreparedStatement效率更高,因为sql语句的框架是不变的,不必每次都重新编译sql语句,而Statement可能需要重新编译;
- PreparedStatement会做类型安全检查,setXXX,而Statement不会做类型安全检查,直到执行程序才知道类型是否有错误。
大多数的情况下都是选择PreparedStatement,但是也有一些需求是需要sql注入的,此时就需要使用Statement了。
如:实现升序和降序的功能
String sql = "select * from emp order by empno ?";
如果此时使用PreparedStatement的话,就需要setString(1,"desc"),那么sql语句就会变成:
String sql = "select * from emp order by empno "desc"";
就有sql语法错误了,此时就必须使用Statement了:
String sql = "select * from emp order by empno" + "参数";
传递一个字符串类型的参数,如"desc",就会变成字符串的拼接了,就是正确的sql语句了:
String sql = "select * from emp order by empno desc";
4.JDBC实现事务
在JDBC中,事务是默认开启的,所以我们需要先禁用自动提交,Connection接口中包含了对事务的操作:
conn.setAutoCommit(false);//关闭自动提交
conn.commit();//提交数据(在try的最后)
conn.rollback();//回滚数据(在catch中)
5.JDBC封装成工具类
可以发现,上述中有许多的操作是重复的,像注册驱动,获取数据库连接,以及关闭资源,并且其中的一些参数我们是可以放在配置文件中的,所以我们可以将这些操作封装起来:
配置文件:jdbc.properties
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/practice
username = root
password = root
JDBCUtils
package com.dh.util;
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCUtils {
private JDBCUtils() {
}
//注册驱动
static {
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConn() throws SQLException {
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String url = bundle.getString("url");
String username = bundle.getString("username");
String password = bundle.getString("password");
return DriverManager.getConnection(url, username, password);
}
//释放资源
public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试
package com.dh.jdbc;
import com.dh.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest05 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//调用JDBCUtils获取连接
conn = JDBCUtils.getConn();
conn.setAutoCommit(false);
String sql = "update count set money = money - 10000 where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,1);
ps.executeUpdate();
int i = 1/0;
String sql2 = "update count set money = money + 10000 where id = ?";
ps = conn.prepareStatement(sql2);
ps.setInt(1,2);
ps.executeUpdate();
conn.commit();
String sql3 = "select * from count";
ps = conn.prepareStatement(sql3);
rs = ps.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("id"));
System.out.println(rs.getDouble("money"));
}
} catch (SQLException e) {
if(conn != null){
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
//调用JDBCUtils释放资源
JDBCUtils.close(conn,ps,rs);
}
}
}