一、概述
-
JDBC:Java DataBase Connectivity(Java语言连接数据库)
-
本质:JDBC是SUN公司制定的一套接口(interface),接口都有调用者(程序员)和实现者(接口由数据库厂商进行实现),面向接口调用、面向接口写实现类(实现类具体如何实现不关心),这都属于面向接口编程。主要与java.sql.*;有关(这个软件包下有很多接口。)(程序只有接口无实现类无法运行)
-
意义:
- 面向接口编程(抽象,不要面向具体编程)有助于解耦合(类似于多态(父类引用接收子类对象),接口引用接收所有的实现类对象),降低程序的耦合度,提高程序的扩展力,让程序更加灵活。(不需要关心使用的具体数据库是什么)
- 每一个数据库产品的底层实现原理都不一样。通过JDBC可实现一套Java代码控制所有实现了JDBC接口的数据库
-
数据库驱动:就是JDBC接口的实现类,MySQL的驱动文件是mysql-connector-java(可在maven官网下载)
-
开发预备:首先从各大数据库厂商的官网下载对应的驱动jar包
- 非IDEA开发:将其配置到环境变量classpath(计算机->属性->高级系统设置->环境变量->系统变量->新建)当中。classpath=.;JDBC路径.jar
- IDEA:Java——JDBC连接数据库(步骤详解!!!)_张起灵-小哥的博客-CSDN博客_java数据库连接库jdbc,下载完对应版本的驱动jar包后,在项目根目录新建文件夹lib,将驱动jar包拖入,并使用右键鼠标添加为库
二、JDBC编程步骤
//JDBC.properties,该文件最好放置在src目录下
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/databasename?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
user=root
password=admin12345
import java.sql.*;
import java.util.ResourceBundle;
public class Test1 {
public static void main(String[] args) {
//使用资源绑定器绑定属性配置文件jdbc.properties
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver=bundle.getString("driver");
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
//提升作用域
Connection connection = null;//connection代表数据库
Statement statement = null;//sql语句发送器
ResultSet resultset=null;//结果集
try {
//第一步:注册驱动(告诉Java程序,即将要连接的是哪个厂商数据库)
//mysql8.0后,驱动名字从com.mysql.jdbc.Driver更改为com.mysql.cj.jdbc.Driver
//注册驱动的旧写法,驱动中的内静态代码块已实现,不推荐使用,使用反射机制加载驱动让驱动内的静态代码块执行即可
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
//Class.forName("com.mysql.cj.jdbc.Driver");//此方法,可以将参数字符串写于配置文件当中,无需接收返回值
//例如:通过修改配置文件即可改变所连接的数据库
Class.forName(driver);
//第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,属于进程通信,使用完后需要关闭)
//mysql数据库url的格式:jdbc:mysql://数据库服务器IP地址:端口号/数据库名?参数名1=参数值1&参数名2=参数值2
//oracle数据库url的格式:jdbc:oracle:thin:@数据库服务器IP地址:端口号:数据库名
//通信协议:是指通信之前就提前定好的数据传送格式,即数据包具体怎么传送数据。
//高版本的数据库才需要在数据库名后加上?时区,useUnicode代表使用安全编码,characterEncoding代表使用utf-8字符集,useSSL代表使用安全连接(当sql版本高于connect版本则设置为false)
connection = DriverManager.getConnection(url, user, password);
//此处是用了多态,相当于Connection connection=new com.mysql.cj.jdbc.ConnectionImpl();
//第三步:获取数据库操作对象(专门执行sql语句的对象,sql发送器)
//以下代码都是面向接口调用方法,而非某个具体的数据库,因此不需要修改
statement = connection.createStatement();
//第四步:执行sql语句(主要执行DQL与DML)
//String sql="insert into dept(deptno,dname,loc) values(70,'研发部','深圳')";
//executeUpdate专门执行DML语句(insert,delete,update)
//返回值count是影响数据库中的记录条数,可用于对操作是否成功进行判断
//int count = statement.executeUpdate(sql);
String sql1="select deptno,dname,loc from dept";
//executeUpdate专门执行DQL语句(select)
resultset=statement.executeQuery(sql1);
//第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有第五步处理查询结果集)
//光标从不指向任何行开始
while (resultset.next()){//每执行一次resultset.next(),则向下移动一行,有值则返回值为true,否则false
//光标指向的行有数据
//对结果集进行遍历,取数据
//getString()方法的特点是:不管数据库中的数据类型是什么,都以String形式取出。
//getString()方法的参数可为下标int,表示取第几列(第几个字段),也可以为字段名字符串(查询语句有重命名以重命名为准)
//JDBC中所有下标从1开始,而不是0
//String string1 = resultset.getString(1);
//String string2 = resultset.getString(2);
//String string3 = resultset.getString(3);
String string1 = resultset.getString("deptno");
String string2 = resultset.getString("dname");
String string3 = resultset.getString("loc");
System.out.println(string1+","+string2+","+string3);
//除了String类型取出外,还可以以特定的类型取出
//int string1=resultset.getInt("deptno");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启后需要关闭)
//为了保证资源能够在任何情况下完成释放,因此写于finally语句块中,并且遵循后开先关的原则
if (resultset != null) {
try {
resultset.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
- 遍历结果集图示:
-
可以将JDBC封装成工具类,便于开发使用
import java.sql.*; /** * JDBC工具类,简化JDBC编程 */ public class JDBCUtil { /** * 工具类中的构造方法都是私有的(无法new对象) * 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用 */ private JDBCUtil(){} //静态代码块在类加载时执行,并且只执行一次。 static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获取数据库连接对象 * @return 连接对象 * @throws SQLException */ public static Connection getConnection() throws SQLException { Connection connection= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/databasename?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC","root","admin12345"); return null; } /** * 关闭资源 * @param connection 数据库连接对象 * @param statement 数据库操作对象 * @param resultSet 数据库结果集 */ public static void close(Connection connection, Statement statement, ResultSet resultSet){ if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
三、使用JDBC完成用户登录
//数据库脚本
drop table if exists t_user;
/*==============================================================*/
/* Table: t_user */
/*==============================================================*/
create table t_user
(
id bigint not null,
loginName varchar(255),
loginPwd varchar(255),
realName varchar(255),
primary key (id)
);
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/*
实现功能
1.需求:
模拟用户登录功能的实现
2.业务描述
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
用户输入用户名和密码之后,提交信息,java程序收集到用户信息
Java程序连接数据库验证用户名和密码是否合法
合法:显示登录成功
不合法:显示登录失败
3.数据的准备:
在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner
使用PD工具来进行数据库表的设计。(user-login.sql脚本)
4.数据库配置文件JDBC.properties同上
*/
public class UserLoginDemo {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess=login(userLoginInfo);
//最后输出结果
System.out.println(loginSuccess?"登录成功":"登录失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
//打标记,先设置成失败
boolean loginSuccess =false;
//JDBC代码
//使用资源绑定器绑定属性配置文件jdbc.properties
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver=bundle.getString("driver");
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
//提升作用域
Connection connection = null;//connection代表数据库
Statement statement = null;//sql语句发送器
ResultSet resultset=null;//结果集
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection(url, user, password);
//3.获取数据库操作对象
statement = connection.createStatement();
//4.执行sql对象
String sql="select * from t_user where loginName='"+userLoginInfo.get("loginName")+"' and loginPwd='"+userLoginInfo.get("loginPwd")+"'";
ResultSet resultSet = statement.executeQuery(sql);
//5.处理结果集
if(resultSet.next()){
loginSuccess =true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.释放资源
if (resultset != null) {
try {
resultset.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String,String> initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo= new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
四、SQL注入
- SQL注入:即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
- 根本原因:用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的愿意被扭曲,进而达到SQL注入
//如上述"三"中的用户登录例程,则存在SQL注入,输入以下信息
用户名:abc
密码:abc' or '1'='1
//则sql语句变为
String sql="select * from t_user where loginName='"+"abc"+"' and loginPwd='"+"abc' or '1'='1"+"'";
//即(and的优先级比or的优先级高),此时因为'1'='1'恒为真,因此sql语句的条件整体为true
String sql="select * from t_user where loginName='abc' and loginPwd='abc' or '1'='1'";
-
SQL注入问题解决:用户提供的信息不参与SQL语句编译的过程,即可解决问题,可使用java.sql.PreparedStatement,该接口继承了java.sql.Statement。java.sql.PreparedStatement的原理是,预先对SQL语句的框架进行预编译,然后再给SQL语句传"值"。
-
//使用PreparedStatement防止SQL注入 //SQL脚本与配置文件保持不变 import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; import java.util.Scanner; /* 实现功能 1.需求: 模拟用户登录功能的实现 2.业务描述 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码 用户输入用户名和密码之后,提交信息,java程序收集到用户信息 Java程序连接数据库验证用户名和密码是否合法 合法:显示登录成功 不合法:显示登录失败 3.数据的准备: 在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner 使用PD工具来进行数据库表的设计。(user-login.sql脚本) */ public class UserLoginDemo { public static void main(String[] args) { //初始化一个界面 Map<String,String> userLoginInfo = initUI(); //验证用户名和密码 boolean loginSuccess=login(userLoginInfo); //最后输出结果 System.out.println(loginSuccess?"登录成功":"登录失败"); } /** * 用户登录 * @param userLoginInfo 用户登录信息 * @return false表示失败,true表示成功 */ private static boolean login(Map<String, String> userLoginInfo) { //打标记,先设置成失败 boolean loginSuccess =false; //JDBC代码 //使用资源绑定器绑定属性配置文件jdbc.properties ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); String driver=bundle.getString("driver"); String url=bundle.getString("url"); String user=bundle.getString("user"); String password=bundle.getString("password"); //提升作用域 Connection connection = null;//connection代表数据库 PreparedStatement preparedstatement = null;//使用PreparedStatement可以防止SQL注入 ResultSet resultset=null;//结果集 try { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 connection = DriverManager.getConnection(url, user, password); //3.获取预编译的数据库操作对象,并在括号中填入SQL语句,其中"?"是参数的占位符,无单引号包裹 String sql="select * from t_user where loginName= ? and loginPwd= ? "; preparedstatement = connection.prepareStatement(sql); //给占位符?传值,(第一个问号下标是1,第二个问号下标是2,JDBC中所有下标从1开始) preparedstatement.setString(1,userLoginInfo.get("loginName")); preparedstatement.setString(2,userLoginInfo.get("loginPwd")); //preparedstatement.setInt(2,userLoginInfo.get("loginPwd"));参数传值可以是各种类型 //4.执行sql对象 ResultSet resultSet = preparedstatement.executeQuery(); //int count=preparedstatement.executeUpdate(); //通过修改参数之后, //5.处理结果集 if(resultSet.next()){ loginSuccess =true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { //6.释放资源 if (resultset != null) { try { resultset.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedstatement != null) { try { preparedstatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } return loginSuccess; } /** * 初始化用户界面 * @return 用户输入的用户名和密码等登录信息 */ private static Map<String,String> initUI() { Scanner s = new Scanner(System.in); System.out.print("用户名:"); String loginName = s.nextLine(); System.out.print("密码:"); String loginPwd = s.nextLine(); Map<String,String> userLoginInfo= new HashMap<>(); userLoginInfo.put("loginName",loginName); userLoginInfo.put("loginPwd",loginPwd); return userLoginInfo; } }
-
对比Statement与PreparedStatement(最常用,传参数传值的时候)
- PreparedStatement解决了Statement存在的SQL注入问题
- Statement是编译一次SQL语句执行一次,PreparedStatement是编译一次SQL语句,可执行N次,效率更高(MySQL中,当SQL语句结构与上一次对比没有发生改变的时候,则不需要重新编译,直接运行即可)
- PreparedStatement会在编译阶段做类型安全检查
-
需要使用Statement的情况:业务要求必须支持SQL注入的时候,业务要求支持SQL拼接的时候(如升降序排列)
-
select * from t_user where loginName='abc and loginPwd=‘abc’ or ‘1’=‘1’";
五、JDBC事务(单机事务)
-
JDBC默认的事务行为:JDBC中的事务是自动提交的,只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为。(实际开发中需要多条DML同时执行成功)
-
将JDBC的自动提交机制改为手动提交(开启事务)
connection.setAutoCommit(false);
-
提交事务
connection.commit();
-
事务提交失败
//通常写于catch语句当中 catch (Exception e){ if(connection!=null){ try{ //回滚事务 connection.rollback(); }catch(SQLException e1){ e1.printStackTrace(); } } }
六、DAO封装
-
DAO(DataBase Access Object,数据库访问对象):数据库访问对象在开发时提供针对某张表的操作细节(增删改查)
-
优点:
- 在管理系统开发时,通过数据库访问对象可以避免反复的SQL命令书写
- 可以避免反复的JDBC开发步骤书写
-
DAO类:提供数据库访问对象的类
-
DAO类开发规则
- 一个DAO类封装的是一张表的操作细节
- DAO类命名规则:表名+Dao
- DAO类所在包命名规则:公司网站域名倒写.dao
-
使用时,则new 表名+Dao()。直接调对象方法对表进行增删查改
-
package Dao; import java.sql.ResultSet; /** * 增删查改 */ public class DeptDao { //每执行一个方法操作开关一次JDBC private JDBCUtil jdbcUtil=new JDBCUtil(); //添加数据行 public int add(String column1,String column2,String column3){ //①SQL语句的String //②JDBC步骤,JDBCUtil //③try catch包裹的SQL传参以及SQL语句执行,以及结果集处理 //④finally中关闭JDBC资源 return 1;//⑤返回执行结果 } //删除数据行 public int delete(String column1,String column2,String column3){ //①SQL语句的String //②JDBC步骤,JDBCUtil //③try catch包裹的SQL传参以及SQL语句执行,以及结果集处理 //④finally中关闭JDBC资源 return 1;//⑤返回执行结果 } //更新数据行 public int update(String column1,String column2,String column3){ //①SQL语句的String //②JDBC步骤,JDBCUtil //③try catch包裹的SQL传参以及SQL语句执行,以及结果集处理 //④finally中关闭JDBC资源 return 1;//⑤返回执行结果 } //查询数据行 public ResultSet findAll(){ //①SQL语句的String //②JDBC步骤,JDBCUtil //③try catch包裹的SQL传参以及SQL语句执行,以及结果集处理 //④使用实体类new对象接收结果集 //⑤数据过多可以使用集合将实体类对象打包 //⑥finally中关闭JDBC资源 } }
七、实体类
-
实体类:一个实体类用于描述一张表的结构,用于new对象接收结果集,因为结果集会被close销毁,因此需要使用实体类对象接收
-
实体类的类名应该与所关联的表名保持一致,但是可以忽略大小写
-
实体类的属性应该与所关联的表文件字段保持一致(属性private,使用getter,setter进行读取与存储,无参、有参构造方法)
-
实体类的一个实例对象用于在内存中存储对应的表文件中的一个数据行
-
数据对象数量较多时,则使用集合对象进行打包接收从ResultSet封装的实体类对象(一个实体类对象代表一条数据)
-
-
实体类包名:entity
-
用处:因为一个实体类对象,代表一条数据,因此在接收参数时,可以将参数用实体类对象进行封装;在查询数据表结果时,也可以将查询结果,封装成实体类对象