目录
一、概述
1.1 简介
JDBC全称java database connectivity,Java数据库连接技术,就是使用java语言连接数据库
1.2本质
JDBC是一种规范,其本质是sun公司制定的一套接口。
那么sun公司为什么要制定这么一套接口呢?
因为数据库有很多种,如mysql,Oracle,SQLserver等,而每一种数据库的底层实现原理都不同,
如果不存在jdbc这套接口,那么作为java程序员要去连接数据库就要根据不同的数据库写不同的代码,
连接mysql要写一套,连接oracle又要重新写一套。。。。。。制定了一套接口后,各大数据库厂家都去实现这个接口,
之后我们要连接数据库就可以面向这套接口编程,不用关心不同数据库底层的实现原理,需要连接不同数据库,直接更换对应的驱动即可。
1.3 编码模拟本质
编码模拟sun公司制定接口,数据库厂家实现接口,然后我们面向接口调用
模拟sun公司
public interface JDBC {
/**
* 连接数据库的方法
*/
void getConnection();
}
模拟数据库厂家
/**
*实现类被称为驱动(mysql驱动)
*
* mysql数据库厂家的程序员负责实现jdbc接口
*/
public class Mysql implements JDBC {
@Override
public void getConnection() {
System.out.println("连接到mysql数据库");
}
}
/**
* oracle驱动
*
* oracle数据库厂家的程序员负责实现接口
*/
public class Oracle implements JDBC {
@Override
public void getConnection() {
// 具体如何连接,不需要我们关心,这是数据厂家的程序员的事儿
System.out.println("连接到Oracle数据库");
}
}
Java程序员
public class JavaProgram {
public static void main(String[] args) {
// 多态,父类型引用指向子类型对象,直接调用接口中的方法,就可以连接到你想要连接的数据库
JDBC jdbc = new Mysql();//new Oracle(); // 这个就相当于注册驱动,注册哪个数据库的驱动,就连接哪个数据库
jdbc.getConnection(); // 面向接口调用方法,不需要做修改
}
(注:代码发给大家,可以去看看然后自己运行一下)
二、编码
2.1 编程六步
jdbc编程六步:
1. 注册驱动(告诉java程序,即将要连接的是哪个品牌的数据库)
2. 获取连接(表示jvm的进程和数据库进程之间的通道打开了)
3. 获取数据库操作对象(专门执行sql语句的对象)
4. 执行sql语句
5. 处理查询结果集
6. 释放资源
2.2 上机实操
2.2.1 插入数据(删除,修改类似)
public class InsertDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2.获取连接
String url = "jdbc:mysql://localhost:3306/jdbc_train"; //数据库的url
String user = "root"; // 用户
String password = "root"; // 密码
conn = DriverManager.getConnection(url, user, password);
// 3.获取数据库操作对象
stmt = conn.createStatement();
// 4.执行sql
String sql = "insert into t_user(uname, upwd) values('zhaoliu', '123456')";
int result = stmt.executeUpdate(sql); // 执行sql,返回数据库中改变的记录条数
if(result == 1){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
2.2.2 查询
public class SelectDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_train", "root", "root");
//3. 获取数据库操作对象
stmt = conn.createStatement();
//4. 执行sql
String sql = "select id,uname,upwd from t_user";
rs = stmt.executeQuery(sql); // 执行sql语句,返回一个查询结果集ResultSet对象
//处理查询结果集
while(rs.next()){
// 取数据的第一种方法,使用位置下标,jdbc中所有位置下标都是从1开始的
/*
int id = rs.getInt(1);
String uname = rs.getString(2);
String password = rs.getString(3);
*/
// 取数据的第二种方法,
/*
* 注:括号里写的是查询结果的列名,而不是表的字段名,如果在查询时起了别名,那么括号里应该写别名
* */
int id = rs.getInt("id");
String uname = rs.getString("uname");
String password = rs.getString("upwd");
System.out.println(id + " " + uname + " " + password);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6. 释放资源
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
2.2.3 优化
2.2.3.1 注册驱动优化
看mysql驱动Driver类的源码
源码中,在静态代码块里面有一行代码
DriverManager.registerDriver(new Driver());
这行代码就是注册了mysql的驱动,所以我们注册驱动时,不用自己写这一行代码,只需要让对应的驱动类的这个静态代码块执行就可以。
那么,静态代码块是什么呢?怎么让他执行呢?
静态代码块就是一个初始化块,它会在类加载的时候执行一次。(关于静态代码块的详细内容,大家可以自行查阅相关博客,这里不做多的赘述)
那怎么让类加载呢?
链接: 大家可以看看这篇文章
在反射机制中,使用Class.forName(“完整类名”)获取某个类的字节码文件时,会进行该类的类加载,所以注册驱动的代码可以如下编写
Class.forName("com.mysql.jdbc.Driver"); // 类加载,静态代码块执行,注册驱动
注:大家可能还不了解反射机制,这是javase后面的内容,可以先去了解,当下大家记住这行代码就行。还有大家可能还不明白为什么要用这种方式来注册驱动,下面会解答这个疑惑。
2.2.3.2 属性配置文件优化
在上面代码中,我们把驱动的注册,获取连接中的url,用户名,密码都写死在了代码里面,如果后期要换一个数据库,是不是还得去改代码,改完代码就要重新进行编译,对于我们写的这种小demo没啥影响,那如果是一个已经上线的项目,那就很麻烦,不会允许轻易改源代码。所以,我们把这些需要经常更改的值,写入属性配置文件中,有更改需求时,在配置文件中更改就可以
优化后代码:
public class PropertiesSelectDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
try {
//1. 注册驱动
Class.forName(bundle.getString("driver"));
//2. 获取连接
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
conn = DriverManager.getConnection(url, user, password);
//获取数据库连接对象
stmt = conn.createStatement();
//4. 执行sql
String sql = "select id,uname,upwd from t_user";
rs = stmt.executeQuery(sql); // 执行sql语句,返回一个查询结果集ResultSet对象
//5. 处理查询结果集
while(rs.next()){
int id = rs.getInt("id");
String uname = rs.getString("uname");
String pwd = rs.getString("upwd");
System.out.println(id + " " + uname + " " + pwd);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
//6. 释放资源
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
jdbc.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_train
user=root
password=root
2.2.4 sql注入攻击
2.2.4.1 演示
编写了一个用户登录demo,演示sql攻击
t_user表
正确的输入用户名和密码:
错误的输入用户名或密码:
sql注入攻击:
2.2.4.2 分析
原因:
使用Statement执行sql语句时,使用字符串拼接的方式读取用户输入并 生成对应的sql语句,然后对sql语句进行编译。如果用户输入的信息中含有 sql语句的关键字,那么就会导致sql语句原意被扭曲,进而达到sql注入。
解决:
只要用户的输入信息不参与sql语句的编译过程,就不会造成sql注入了。而要想用户输入信息不参与sql语句的编译就必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对sql语句的框架进行编译,然后给sql语句传值
更改后(部分代码,完整代码大家看我发的源码):
private static boolean login(Map<String, String> userLoginInfo) {
boolean loginSuccess = false;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//用户名
String userName = userLoginInfo.get("userName");
//密码
String userPwd = userLoginInfo.get("userPwd");
//读取属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
try {
//注册驱动
Class.forName(bundle.getString("driver"));
//获取连接
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
conn = DriverManager.getConnection(url, user, password);
//获取预编译的数据库操作对象
//sql语句的框子,其中一个?,表示一个占位符,一个?将来接收一个值,注意:占位符不能用单引号括起来
String sql = "select id, uname, upwd from t_user where uname = ? and upwd = ?";
ps = conn.prepareStatement(sql);
//执行sql
// 给sql占位符传值
ps.setString(1, userName); // 1表示第一个占位符
ps.setString(2, userPwd);
rs = ps.executeQuery();
//处理查询结果
if(rs.next()){
loginSuccess = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
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();
}
}
}
return loginSuccess;
}
2.2.5 事务
jdbc事务默认自动提交,执行一条DML语句就提交一次,但是在实际应用场景中,一个业务常常需要很多条DML语联合完成,必须保证这些 DML语句同时成功或者同时失败。所以需要我们手动提交事务。
如何设置手动提交呢?
在java.sql.Connection接口中有一个setAutoCommit()方法
调用这个方法,传值false即可将自动提交改为手动提交。
需要提交和回滚事务时,也是调用这个接口中的对应方法。
提交事务:conn.commit();
回滚事务:conn.rollback();
例子:
public class ShiWuDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
try {
//1.注册驱动
Class.forName(bundle.getString("driver"));
//2.获取连接
conn = DriverManager.getConnection(
bundle.getString("url"),
bundle.getString("user"),
bundle.getString("password"));
// 改为手动提交
conn.setAutoCommit(false);
String sql1 = "insert into t_user(uname, upwd) values(?, ?)";
String sql2 = "insert into t_user(uname, upwd) values(?, ?)";
//3.获取预编译的操作对象
ps1 = conn.prepareStatement(sql1);
ps2 = conn.prepareStatement(sql2);
ps1.setString(1,"zhangsan11");
ps1.setString(2,"888");
ps2.setString(1,"zhangsan22");
ps2.setString(2,"888666");
//4. 执行sql
int result1 = ps1.executeUpdate();
// 这一行代码会抛出ArithmeticException异常,导致第二条insert语句无法执行
int tmp = 1/0;
int result2 = ps2.executeUpdate();
if(result1==1 && result2==1){
//代码能执行到这,并且sql执行结果正确,说明业务完成,可以提交事务
conn.commit();
} else {
// sql执行结果不正确,回滚
conn.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (ArithmeticException e) {
e.printStackTrace();
// 出现异常回滚
if(conn != null){
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} finally {
if(ps1 != null ){
try {
ps1.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps2 != null ){
try {
ps2.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null ){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
2.2.6 封装工具类
jdbc技术就是上面所讲到的六步,但是,如果每次访问数据库都编写完整的六步代码,重复代码多,代码冗余,作了很多无用功,所以可以自己写一个工具类,对jdbc六步编程进行封装。代码如下:
public class DBUtil {
private static ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
private static String driver = bundle.getString("driver");
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 工具类的构造方法一般都是私有
private DBUtil(){}
/**
* 获取数据库连接对象
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 关闭资源
* @param conn 数据库连接对象
* @param ps 数据库操作对象
* @param rs 结果集
*/
public static void close(Connection conn, Statement 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();
}
}
}
}
三、总结
jdbc是用来连接数据库,做增删改查操作的一门技术,它本质上是一套接口,我们操作数据库时就是面向这套接口进行编程。
jdbc编程分6步:
- 注册驱动
- 获取连接
- 获取数据库操作对象
- 执行sql
- 处理结果集
- 关闭资源
其中
- 注册驱动可以利用反射机制,使对应的驱动类类加载执行其中的静态代码块,从而注册驱动,这样做的目的是,可以将需要的驱动写在配置文件中,方便后续的对驱动的切换。
- 获取数据库操作对象时,我们通常都是获取预编译的数据库操作对象PreparedStatement,使用它可以使用户输入的信息不参与sql的编译过程,从而防止sql注入攻击
四、补充
idea连接数据库
-
点击界面右上角的Database
-
点击+号,并选择要连接的数据库,这里选择mysql
-
填写连接相关信息
-
测试连接,成功后,点击应用
-
右上角出现如下界面,连接成功