当前位置:网站首页>JDBC知识点
JDBC知识点
2022-08-04 09:34:00 【Jm呀】
文章目录
1.JDBC是什么?
Java Database Connection(Java语言连接数据库)
具体来讲,就是通过java连接广泛的数据库,并对表中数据执行增删改查等操作的技术。
2.JDBC的本质是什么?
JDBC是SUN公司制定的一套接口(interface)
java.sql.*;(这个软件包下有很多接口)
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
建议:
Animal a=new Cat();
Animal a=new Dog();
//喂养的方法
public void feed(Animal a){ // 面向父类型编程
}
不建议:
Dog d=new Dog();
Cat c=new Cat();
思考:为什么SUN公司制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
Ms SqlServer数据库也有自己的原理。
.........
每一个数据库产品都有自己独特的实现原理。
JDBC的本质到底是什么?
一套接口。
3.JDBC技术相关接口
接口 | 作用 |
---|---|
Driver | 驱动接口,定义建立连接的方式 |
DriverManager | 工具类,用于管理驱动,可以获取数据库的链接 |
Connection | 表示Java与数据库建立的连接对象(接口) |
ResultSet | 结果集,用于获取查询语句的结果 |
Statement | 提供了执行语句和获取结果的基本方法 |
PreparedStatement | 发送SQL语句的工具 |
4.JDB编程六步(牢记)
第一步:注册驱动
告诉Java程序,即将要连接的是哪个品牌的数据库!
有两种方式:
第一种注册驱动的方式
/*DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());*/
第二种注册驱动的方式(类加载方式)常用!
Class.forName("com.mysql.cj.jdbc.Driver");
第二种方式常用,因为参数是一个字符串,将字符串可以写到xxx.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");
第二步:获取连接
表示JVM的进程和数据库进程之间的通道打开了
属于进程间的通信,重量级的,使用完一定要关闭通道
方式:
Connection conn=DriverManager.getConnection(url,user,password);
url解析:
url:统一资源定位器
https://www.baidu.com/ 这就是url
url包括哪几部分?
协议
IP
端口号
资源名
http://182.61.200.7:80/index.html
http:// 通信协议
182.61.200.7 服务器IP地址
80 服务器上软件的端口
index.html 是服务器上某个资源名
第三步:获取数据库操作对象
专门执行sql语句的对象
方式:
Statement stmt=conn.createStatement();
第四步:执行SQL语句
DML:
String sql="insert into dept2 values(50,'项目经理','山东')";
int count=stmt.executeUpdate(sql);
System.out.println(count==1 ?"插入成功" :"插入失败");
executeUpdate()返回值类型为int类型,为执行受到影响的行数
DQL:
String sql2="select * from dept2";
ResultSet rs=stmt.executeQuery(sql2);
executeQuery()返回值类型为ResultSet类型,为查询结果集
第五步:处理查询结果集
当第四步执行的是select语句的时候,才有第五步处理查询结果集
遍历方式:
while(rs.next()){
String deptno=rs.getString("deptno");
//int deptno=rs.getInt("deptno");
String dname=rs.getString("dname");
String loc=rs.getString("loc");
System.out.println(deptno+","+dname+","+loc);
}
第六步:释放资源
使用完资源之后一定要关闭资源。
Java和数据库属于进程间的通信,开启之后一定要关闭。
方式:
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) {
throw new RuntimeException(e);
}
}
释放规则:
打开时:Connection -> Statement -> ResultSet
关闭时:ResultSet-> Statement -> Connection
演示完整样例
import java.sql.*;
public class Main {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//1.注册驱动
//第一种注册驱动的方式
/*DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());*/
//第二种注册驱动的方式(类加载方式)
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
//统一资源定位器
String url="jdbc:mysql://localhost:3306/jmpower";
String user="root";
String password="123";
conn=DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象
stmt=conn.createStatement();
//4.执行SQL语句(DML,DQL)
//DML语句
/*String sql="insert into dept2 values(50,'项目经理','山东')"; int count=stmt.executeUpdate(sql); System.out.println(count==1 ?"插入成功" :"插入失败");*/
//DQL语句
String sql2="select * from dept2";
rs=stmt.executeQuery(sql2);
//5.处理查询结果集
while(rs.next()){
String deptno=rs.getString("deptno");
String dname=rs.getString("dname");
String loc=rs.getString("loc");
System.out.println(deptno+","+dname+","+loc);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//释放资源
//从后向前释放
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) {
throw new RuntimeException(e);
}
}
}
}
}
5.SQL注入问题
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/* 实现功能: 1.需求: 模拟用户登录功能的实现。 2.业务描述: 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码 用户输入用户名和密码之后,提交信息,java程序收集到用户信息 java程序连接数据库验证用户名和密码是否合法 合法:显示用户登陆成功 不合法:显示用户登陆失败 3.数据的准备: 在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具,PowerDesigner 使用PD工具来进行数据库表的设计。 4.当前程序存在的问题: 用户名:sdaf 密码:sdaf' or '1'='1 登陆成功 这种现象被称为SQL注入(存在安全隐患)(黑客常用) 5.导致SQL注入的根本原因是什么? 用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编写过程, 导致sql语句的原意被扭曲,进而达到sql注入。 */
public class JDBCTest01 {
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;
//单独定义变量
String loginName=userloginInfo.get("loginName");
String loginPwd=userloginInfo.get("loginPwd");
//JDBC代码
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","123");
//获取数据库操作对象
stmt=conn.createStatement();
//执行SQL语句
String sql="select * from t_user where loginName='"+loginName+"' and loginPwd='"+loginPwd+"'";
rs=stmt.executeQuery(sql);
//以上正好完成了sql与语句的拼接,以下代码的含义是,发送sql语句给DBMS,DBMS进行sql编译
//正好将用户提供的"非法信息"编译进去,导致了原sql语句的含义被扭曲了。
//处理查询结果集
if(rs.next()){
//登陆成功
loginSuccess=true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
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();
}
;
}
}
return loginSuccess;
}
/** * 初始化用户界面 * @return 用户输入的用户名和密码等登录信息 */
private static Map<String, String> initUI() {
Scanner sc=new Scanner(System.in);
System.out.print("用户名: ");
String loginName=sc.nextLine();
System.out.print("密码: ");
String loginPwd=sc.nextLine();
Map<String, String> userLoginInfo=new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
1)当前程序存在的问题:
用户名:sdaf
密码:sdaf’ or ‘1’='1
登陆成功
这种现象被称为SQL注入(存在安全隐患)(黑客常用)
2)导致SQL注入的根本原因是什么?
用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编写过程,
导致sql语句的原意被扭曲,进而达到sql注入。
6.解决SQL注入问题?
只要用户提供的信息不参与sql语句的编译过程,问题就解决了。
即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,不起作用。
要想用户信息不参与sql语句的编译,那么必须使用java.sql.PreparedStatement接口。
java.sql.PreparedStatement:
PreparedStatement是属于预编译的数据库操作对象。
PreparedStatement的原理是:预先对sql语句的框架进行编译,然后再给sql语句穿"值"。
关键:用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用。
演示代码:
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
//解决sql注入后的代码
/* 用户名:fdsa 密码:fdsa' or '1'='1 登陆失败 */
public class JDBCTest01 {
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;
//单独定义变量
String loginName=userloginInfo.get("loginName");
String loginPwd=userloginInfo.get("loginPwd");
//JDBC代码
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","123");
//获取预编译的数据库操作对象
String sql="select * from t_user where loginName=? and loginPwd=?";
//执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译
ps=conn.prepareStatement(sql);
//给占位符?传值(第1个问号下表是1,第2个问号下标是2,jdbc中所有下标从1开始)
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//执行SQL语句
rs=ps.executeQuery();
//处理查询结果集
if(rs.next()){
//登陆成功
loginSuccess=true;
}
} catch (Exception 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;
}
/** * 初始化用户界面 * @return 用户输入的用户名和密码等登录信息 */
private static Map<String, String> initUI() {
Scanner sc=new Scanner(System.in);
System.out.print("用户名: ");
String loginName=sc.nextLine();
System.out.print("密码: ");
String loginPwd=sc.nextLine();
Map<String, String> userLoginInfo=new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
7.Statement和PreparedStatement对比
1.对比
1)Statement存在sql注入问题,PreparedStatement解决了sql注入问题
2)Statement是编译一次执行一次,PreparedStatement是编译一次,可执行n次。PreparedStatement效率较高一些。
3)PreparedStatement会在编译阶段做类型的安全检查。
2.什么情况下必须使用Statement呢?
业务方面要求必须支持sql注入的时候。
Statement支持sql注入,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement。
why?
Statement进行sql语句拼接时,拼接的字符串不带左右两边的单引号,而
PreparedStatement语句中?set时,会传入字符串左右两边的单引号。
必须使用Statement的业务
import javax.swing.plaf.nimbus.State;
import java.sql.*;
import java.util.Scanner;
public class JDBCTest02 {
public static void main(String[] args) {
//用户输入
Scanner sc=new Scanner(System.in);
System.out.println("输入desc或asc,desc表示降序,asc表示升序");
System.out.print("请输入: ");
String keyWords=sc.nextLine();
//JDBC语句
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","021107");
//获取数据库操作对象
stmt=conn.createStatement();
//执行sql语句
String sql="select * from emp order by ename "+keyWords;
rs=stmt.executeQuery(sql);
//处理查询结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
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();
}
}
}
}
}
8.演示jdbc事务自动提交机制
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","123");
//获取预编译的数据库操作对象
String sql="update dept2 set dname=? where deptno=?";
ps=conn.prepareStatement(sql);
//第一次给占位符传值
ps.setString(1,"x部门");
ps.setString(2,"30");
int count=ps.executeUpdate();//执行第一条update语句
System.out.println(count);// 1
//第二次给占位符传值
ps.setString(1,"y部门");
ps.setString(2,"20");
count=ps.executeUpdate();//执行第二条update语句
System.out.println(count);// 1
} catch (Exception 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();
}
}
}
1)只要执行任意一条DML语句,则自动提交一次,这是jdbc默认的事务行为。但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
2)以上程序的测试结果: jdbc中只要执行任意一条DML语句,就提交一次。
9.账户转账演示事务(重点三行代码)
import java.sql.*;
/* sql脚本: drop table if exists t_act; create table t_act{ actno int, balance double(7,2) }; insert into t_act(actno,balance) values(111,20000); insert into t_act(actno,balance) values(222,0); commit; select * from t_user; 重点三行代码? conn.setAutoCommit(false); conn.commit(); conn.rollback(); */
public class JDBCTest04 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","123");
//将自动提交机制改为手动
conn.setAutoCommit(false);//开启事务
//获取预编译的数据库操作对象
String sql="update t_act set balance=? where actno=?";
ps=conn.prepareStatement(sql);
//给?传值
ps.setInt(1, 10000);
ps.setInt(2,111);
int count=ps.executeUpdate();//执行sql语句
//给?传值
ps.setInt(1,10000);
ps.setInt(2,222);
count+=ps.executeUpdate();//执行sql语句
System.out.println(count==2 ?"转账成功" :"转账失败");// 转账成功
//程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
conn.commit();
} catch (Exception e) {
//出现异常,回滚事务
if(conn!=null){
try {
conn.rollback();
} catch (SQLException ex) {
e.printStackTrace();
}
}
e.printStackTrace();
} finally {
//释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
10.JDBC工具类
工具类
import java.sql.*;
/* JDBC工具类 */
public class JDBCUtils {
/** *工具类中的构造方法都是私有的。 *因为工具类当中的方法是静态的,不需要new对象,直接采用类名调用。 */
private JDBCUtils(){
}
//静态代码块在类加载时执行,并且只执行一次
//只需要注册一次驱动
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/** * 获取数据库连接对象 * @return 连接对象 * @throws SQLException */
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/jmpower","root","123");
}
/** * 释放资源 * @param conn 连接对象 * @param ps 数据库操作对象 * @param rs 查询结果集 */
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();
}
}
}
}
测试工具类
import java.sql.*;
/* 这个程序完成两个任务: 1.测试JDBCUtils类 2.模糊查询 */
public class JDBCTest06 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//获取连接
conn=JDBCUtils.getConnection();
//获取预编译的数据库操作对象
String sql="select ename from emp where ename like ?";
ps=conn.prepareStatement(sql);
//给?传值,查询emp表中名字第二位是A的人
ps.setString(1,"_A%");
//执行sql语句
rs=ps.executeQuery();
//处理查询结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.close(conn,ps,rs);
}
}
}
11.悲观锁(行级锁)
悲观锁(Pessimistic Lock),指在应用程序中显示地为数据资源加锁。悲观锁,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。悲观锁假定当前事务操纵数据资源时,肯定还有其他事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源。
尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它会影响并发性能,因此应该很谨慎地使用悲观锁。
select * from emp where sal>2500 for update;
不加悲观锁
时间 | 取款事务 | 转账事务 |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | select * from ACCOUNT WHERE ID=1;查询结果显示存款余额为1000元 | |
T4 | select * from ACCOUNT WHERE ID=1;查询结果显示存款余额为1000元 | |
T5 | 取出100元,存款余额修改为900元 | |
T6 | 转入100元,1000+100=1100元,修改存款余额为1100元 | |
T7 | 提交事务 | |
T8 | 提交事务 |
加悲观锁
时间 | 取款事务 | 转账事务 |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | select * from ACCOUNT WHERE ID=1 for update;查询结果显示存款余额为1000元,这条记录被锁定 | |
T4 | select * from ACCOUNT WHERE ID=1 for update;执行该语句时,事务停下来等待取款事务解除对该记录的锁定 | |
T5 | 取出100元,存款余额修改为900元 | |
T6 | 提交事务 | |
T7 | 事务恢复运行,查询余额为900元,这条记录被锁定 | |
T8 | 转入100元,900+100=1000元,修改存款余额为1000元, | |
T9 | 提交事务 |
边栏推荐
猜你喜欢
cannot import name 'import_string' from 'werkzeug' [bug solution]
被Win11安全中心误删除的文件怎么恢复?
基于cRIO-904X搭建Simulink与Labview环境
开源一夏 | 查询分页不只有limit,这四种分页方法值得掌握
下午14:00面试,14:08低着头出来了 ,问的实在是太...
[Cloud Residency Co-Creation] HCSD Celebrity Live Streaming – Employment Guide
LeetCode中等题之设计循环队列
NAT/NAPT地址转换(内外网通信)技术详解【华为eNSP】
张朝阳对话俞敏洪:谈宇宙、谈焦虑、谈创业、谈退休、谈人生
2022年制冷与空调设备运行操作特种作业证考试题库及模拟考试
随机推荐
Redis 内存满了怎么办?这样置才正确!
渗透——信息收集
学习在php中将特大数字转成带有千/万/亿为单位的字符串
今日睡眠质量记录71分
我和 TiDB 的故事 | TiDB 对我不离不弃,我亦如此
冰蝎工具开发实现动态二进制加密WebShell
Detailed Explanation of Addresses Delivered by DHCP on Routing/Layer 3 Switches [Huawei eNSP]
No module named 'flask_misaka' has been resolved [BUG solution]
字符串与正则表达式(C#)
双指针方法
关于DSP驱动外挂flash
PD 源码分析- Checker: region 健康卫士
Four common methods of network attacks and their protection
Post-94 Byte P7 posted the salary slip: It's really good to make up for this...
Grafana9.0发布,Prometheus和Loki查询生成器、全新导航、热图面板等新功能!
MindSpore:Ascend运行出现问题
Acwing 3208. Z字形扫描 偏移量+扩展图
低代码应用开发的五大好处
外包干了四年,秋招终于上岸了
张朝阳对话俞敏洪:谈宇宙、谈焦虑、谈创业、谈退休、谈人生