JDBC
-
JDBC是什么?
Java Database Connectivity(java语言连接数据库)
java.sql.*;(这个包下有很多接口)
-
JDBC的本质是什么?
JDBC的本质是SUN公司制定的一套接口(interface)
接口都有调用者和实现者。面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。 多态机制就是非常典型的:面向抽象编程(不要面向具体编程) 建议: Animal a = new Cat(); Animal b = new Dog(); //喂养的方法 public void feed(Animal a){//面向父类型编程 } 不建议: Dog d = new Dog(); Cat c = new Cat(); //喂养方法 public void feed(Dog d){}//只能喂狗 public void feed(Cat c){}//只能喂猫
-
思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理、MySQL数据库也有自己的原理、MS SqlServer数据库也有自己的原理
…每一个数据库都有自己的实现原理。
-
JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpass中
classpath=.;C:\Users\qishi\Desktop\typora markdown学习\JDBC\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
以上的配置是针对文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量。IDEA有自己的配置方式
-
JDBC编程六步(需要记住)
- 第一步:注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
- 第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,属于进程之间的通信,重量级的,使用完毕之后一定要关闭通道。)
- 第三步:获取数据库操作对象(专门执行sql语句的对象)
- 第四步:执行SQL语句(DQL、DML…)
- 第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才执行处理查询结果)
- 第六步:释放资源(使用完资源之后一定要关闭资源。java和数据库属于进程间的通信,开启之后一定要关闭)
-
JDBC完成数据的插入
import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Connection; import java.sql.Statement; /* JDBC编程六步: */ public class JDBCTest01 { public static void main(String[] args){ Connection conn = null; Statement stmt = null; try { //1、注册驱动 Driver driver = new com.mysql.cj.jdbc.Driver();//多态,父类型引用指向子类型对象 DriverManager.registerDriver(driver); //2、获取连接 /* url:统一资源定位符(网络中某个资源的绝对路径) https://www.baidu.com/ 这就是URL URL包括哪几部分? 协议 IP PORT 资源名 http://182.31.200.7:80/index.html http:// 通信协议 182.61.200.7 服务器IP地址 80 服务器上软件的端口 index.html 服务器上某个资源的端口名 jdbc:mysql://127.0.0.1:3306/datas jdbc:mysql:// 协议 127.0.0.1 IP地址 3306 mysql数据库的端口号 datas 具体的数据库名 注意:localhost和127.0.0.1都是本机IP地址 什么是通信协议,有什么用? 通信协议是指通信之间就是提前定好的 数据传送格式。数据包具体怎么传数据,格式提前订好的 */ String url = "jdbc:mysql://127.0.0.1:3306/datas"; String user="root"; String password="101"; conn = DriverManager.getConnection(url,user,password); System.out.println("数据库连接对象:"+conn); //3、获取数据库操作对象 stmt = conn.createStatement(); //4、执行sql String sql = "insert into dept() values(50,'人事部','北京')" //专门执行SML语句的方法(insert 、delete、update) //返回值是“响数据库中的记录条数” int count = stmt.executeUpdate(sql); System.out.println(count == 1?"success","fail") //5、处理查询结果集 } catch (SQLException e) { e.printStackTrace(); }finally{ //6、释放资源 //为了保证资源一定释放,在finally语句块中关闭资源 //并且要遵循从小到大依次关闭。分别对其try..catch try{ if(stmt != null){ stmt.close(); }catch(SQLException e){ e.printStackTrace(); } } try{ if(conn != null){ conn.close(); }catch(SQLException e){ e.printStackTrace(); } } } } }
-
JDBC完成delete
import java.sql.*; public class JDBCText02{ public static void main(){ Connection conn = null; Statement stmt = null; try{ //1、注册驱动 DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //2、获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas", "root","333"); //3、获取数据库操作对象 stmt = conn.createStatement(); //4、执行sql语句 //注意:jdbc的sql语句不需要分号结尾 //String sql = "delete from dept where deptno = 40 "; String sql = "wpdate dept set dname = "销售部",loc= "天津" where deptno=20 "; int count = stmt.executeUpdate(sql); System.out.println(count == 1 ? "删除成功":"删除失败"); }catch(SQLException e){ e.printStackTrace(); }finally{ //6、释放资源 if(stmt != null){ try{ stmt.close(); }catch(SQLException e){ e.printStackTreace(); } } if(conn != null){ try{ conn.close(); }catch(SQLException e){ e.printStackTreace(); } } } } }
-
注册驱动的另一种方式(这种方式常用)
import java.sql.*; public class JDBCText03{ public static void main(){ try{ //1、注册驱动 //这是注册驱动的第一种写法 //DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //注册驱动常用的方式: //为什么使用这种方式?因为参数是一个字符串,字符串可以写到xx.properties文件中。 //以下方法不需要接收返回值,因为我们只想用它的类加载的动作。 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","333"); }catch(SQLException e){ e.printStackTrace(); }class(ClassNotFoundException e){ e.printStackTrace(); } } }
-
将连接数据库的所有信息配置到配置文件中
/* 实际开发中不建议把连接数据库的信息写死到java程序中 */ import java.sql.*; import java.util.*; public class JDBCText04{ public static void main(){ //使用资源绑定器绑定属性配置文件 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 conn = null; Statement stmt = null; try{ //1、注册驱动 Class.forName(driver); //2、获取连接 conn = DriverManager.getConnection(url,user,password); //3、获取数据库操作对象 stmt = conn.createStatement(); //4、执行sql语句 String sql = "wpdate dept set dname = "销售部",loc= "天津" where deptno=20 "; int count = stmt.executeUpdate(sql); System.out.println(count == 1 ? "删除成功":"删除失败"); }catch(Exception e){ e.printStackTrace(); }finally{ //6、释放资源 if(stmt != null){ try{ stmt.close(); }catch(SQLException e){ e.printStackTreace(); } } if(conn != null){ try{ conn.close(); }catch(SQLException e){ e.printStackTreace(); } } } } }
配置文件jdbc.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/datas user=root password=333
-
处理查询结果集(遍历结果集)
import java.sql.*; public class JDBCTest05{ public static void main(String[] args){ Connection conn = null; Statement stmt = null; ResultSet rs = null; try{ //1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","333"); //3、获取数据库操作对象 stmt = conn.creatStatement(); //4、执行sql语句 String sql = "select empno as a,ename,sql from emp"; //int excuteUpdate(insert/delete/update) //ResultSet excuteQuery(select) rs = stmt.excuteQuery(sql); //专门执行DQL语句的方法 //5、处理查询结果集 /* boolean flag = rs.next(); //System.out.println(flag); //true if(flag){ //光标执行的行有数据 //取数据 //getString()方法的特点是:不管数据库中的数据类型是什么,都以String的形式取出 String empno = rs.getString(1); //jdbc中所有下标从1开始,不是从0开始 String ename = rs.getString(2); String sql = rs.getString(3); System.out.println(empno+","+ename+","+sql); } flag = rs.next(); if(flag){ String empno = rs.getString(1); String ename = rs.getString(2); String sql = rs.getString(3); System.out.println(empno+","+ename+","+sql); } */ //使用循环遍历 while(rs.next()){ /* String empno = rs.getString(1); String ename = rs.getString(2); String sql = rs.getString(3); System.out.println(empno+","+ename+","+sql); */ /* //下标可以用列名代替,使用数字的话不健壮 //String sql = "select empno as a,ename,sql from emp"; //这里的列名是最终查询结果的列名,如: //这里的empno重命名为:a。则获取时列名也使用a //String empno =rs.getString("empno"); String empno =rs.getString("a"); //重点注意:列名称不是表中的列名称,是查询结果集中的列名称 String ename =rs.getString("ename"); String sql =rs.getString("sql"); System.out.println(empno+","+ename+","+sql); */ //除了可以以String类型取出之外,还可以以特定的类型取出,需要与数据库底层数据的类型相对应 /* int empno = rs.getInt(1); String ename = rs.getString(2); double sql = rs.getDouble(3); System.out.println(empno+","+ename+","+sql+100); */ //用列名取 int empno = rs.getInt("a"); String ename = rs.getString("ename"); double sql = rs.getDouble("sal"); //如果是int类型等数据,还可以直接进行运算 System.out.println(empno+","+ename+","+sql+200); } }catch(Exception e){ e.printStackTrace }finally{ //6、释放资源 if(rs != null){ try{ rs.close(); }catch(Exception e){ e.printStackTrace(); } } if(stmt != null){ try{ stmt.close(); }catch(Exception e){ e.printStackTrace(); } } if(conn != null){ try{ conn.close(); }catch(Exception e){ e.printStackTrace(); } } } } }
-
模拟用户登录实现的功能(出现了sql注入问题)
import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; /* 实现功能: 1、需求:模拟用户登录实现的功能 2、业务描述:程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码 用户输入用户名和密码之后,提交信息,java程序收集到用户信息 java程序连接到数据库验证用户和密码是否合法。 合法:显示登录成功;不合法:显示登陆失败。 3、数据的准备: 实际的开发中,表的设计会使用专门的建模工具,我们这里安装一个建模工具:PowerDesigner 使用PD工具来进行数据库表的设计。(参见user-login-.sql脚本) 4、当前程序存在的问题: 用户名: fdsa 密码: fdsa' or '1'='1 登陆成功 这种现象被称为SQL注入(安全隐患)(黑客经常使用) 5、导致SQL注入的根本原因是什么? 用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程。导致sql语句的原意被扭曲,进而达到sql注入。 */ public class JDBCTest06 { 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{ //1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","1051976202"); //3、获取数据库操作对象 stmt = conn.createStatement(); //4、执行sql String sql = "select * from t_user where loginName='"+loginName+"' and loginPwd = '"+loginPwd+"' "; //以上正好完成了sql语句的拼接,以下代码的含义是:发送sql语句给DBMS,DBMS进行sql编译 //正好将用户提供的“非法信息”编译进去,导致了原sql语句的含义被扭曲。 rs = stmt.executeQuery(sql); //5、处理结果集 if(rs.next()){ //如果用户表里查到了这个用户的数据,就说明登陆成功 loginSuccess = true; } }catch (ClassNotFoundException | SQLException e){ e.printStackTrace(); }finally{ //6、释放资源 if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stmt != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } } return loginSuccess; } /** * 初始化用户界面 * @return 用户输入的用户名和密码等登录信息 */ private static Map<String, String> initUI() { Scanner s = new Scanner(System.in); System.out.println("用户名:"); String loginName = s.nextLine(); System.out.println("密码:"); String loginPwd = s.nextLine(); Map<String,String> userLoginInfo = new HashMap<>(); userLoginInfo.put("loginName",loginName); userLoginInfo.put("loginPwd",loginPwd); return userLoginInfo; } }
-
解决sql注入问题
import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; /** * 1、解决SQL注入问题 * 只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。即使用户提供的信息中含有sql语句的关键字, * 但是不参与编译,就不会起作用。要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement。 * PreparedStatement接口继承了java.sql.Statement。PreparedStatement是属于预编译的数据库操作对象 * PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值” * * 2、测试结果: * 用户名:fdsa * 密码:fdsa' or '1'='1 * 登录失败 * * 3、解决SQL注入的关键是什么? * 用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译。不起作用 * * 4、对比一下Statement和PreparedStatement * - Statement存在sql注入问题,PreparedStatement解决了SQL注入问题 * - Statement是编译一次,执行一次。PreparedStatement是编译一次,执行N次。PreparedStatement效率较高 * - PreparedStatement会在编译阶段做类型的安全检查 * * 综上所述:PreparedStatement使用较多,只有极少数的情况下需要使用Statement * * 5、什么情况下必须使用Statement? * 业务方面要求必须支持SQL注入的时候。 * Statement支持SQL注入,凡是业务方面要求是需要进行SQL语句拼接的,必须使用Statement */ public class JDBCTest07 { 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; //这里PreparedStatement(预编译的数据库操作对象) ResultSet rs = null; try{ //1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","1051976202"); //3、获取数据库操作对象 //SQl语句的框子,其中一个 ? 代表一个占位符,一个?将来接收一个 值 ,注意:占位符不能使用单引号括起来。 String sql = "select * from t_user where loginName=? and loginPwd=? "; //程序执行到这里,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。 ps = conn.prepareStatement(sql); //给占位符 ? 传值(第一个问号的下标是:1,第二个问号的下标:2.JDBC中所有下标从1开始) ps.setString(1,loginName); ps.setString(2,loginPwd); //4、执行sql //sql语句已经预编译过了,这里直接执行就可以,不用再把sql语句传递进入。 rs = ps.executeQuery(); //5、处理结果集 if(rs.next()){ //如果用户表里查到了这个用户的数据,就说明登陆成功 loginSuccess = true; } }catch (ClassNotFoundException | SQLException e){ e.printStackTrace(); }finally{ //6、释放资源 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 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的SQL注入在业务需要的时候需要进行sql语句的拼接
import java.sql.*; import java.util.Scanner; public class JDBCTest08 { public static void main(String[] args){ //用户在控制台输入desc就是降序,输入asc就是升序。 Scanner s = new Scanner(System.in); System.out.println("需要升序查看请输入:asc、降序查看请输入:desc"); System.out.print("请输入:"); String keyWords = s.nextLine(); //执行SQL Connection conn = null; Statement stmt = null; ResultSet rs = null; try{ Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","1051976202"); stmt = conn.createStatement(); String sql = "select ename from emp order by ename "+keyWords; rs = stmt.executeQuery(sql); //遍历结果集 while(rs.next()){ System.out.println(rs.getString("ename")); } } catch (ClassNotFoundException | SQLException 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(); } } } } }
-
PreparedStatement完成增删改
import java.sql.*; /* PreparedStatement完成Insert、Delete、Upate */ public class JDBCTest09 { public static void main(String[] args){ Connection conn= null; PreparedStatement ps = null; ResultSet rs = null; try { //1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取链接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","1051976202"); //3、获取预编译的数据库操作对象 //增 /* String sql ="insert into dept(deptno,dname,loc) values(?,?,?)"; ps = conn.prepareStatement(sql); //预编译 //给占位符?传值 ps.setInt(1,60); ps.setString(2,"销售部"); ps.setString(3,"上海");*/ //改 /* String sql ="update dept set dname=? ,loc=? where deptno=? "; ps = conn.prepareStatement(sql); ps.setString(1,"研发部"); ps.setString(2,"北京"); ps.setInt(3,60);*/ //删 String sql = "delete from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setInt(1,60); //4、执行SQL int count = ps.executeUpdate(); System.out.println(count); } 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(); } } } } }
-
账户转账事务
import java.sql.*; /* JDBC事务机制: 1、 JDBC中的事务是自动提交的,什么是自动提交? 只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。 但是在实际的业务当中,通常都是N条DML语句共同联合起来次啊能完成的, 必须保证这些DML语句在同一个事务中同时成功或者同时失败。 2、账户转账演示事务 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_act; 3、重点: conn.setAutoCommit(false); 关闭自动提交,即:开启事务 conn.commit(); 手动提交事务 conn.rollback(); 回滚事务 */ public class JDBCTest10 { public static void main(String[] args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/datas","root","1051976202"); //将自动提交机制修改为手动提交。 conn.setAutoCommit(false);//开启事务 //3、获取预编译的数据库操作对象 String sql = "update t_act set balance =? where actno =? "; ps = conn.prepareStatement(sql);//预编译 //给?传值 ps.setDouble(1,10000); ps.setInt(2,111); int count = ps.executeUpdate(); //继续给?传值 ps.setDouble(1,10000); ps.setInt(2,222); count += ps.executeUpdate(); //程序能够执行到这里,说明以上没有问题,事务结束,手动提交数据 conn.commit();//提交事务 System.out.println(count ==2 ? "转账成功":"转账失败"); } catch (ClassNotFoundException | SQLException e) { //如果以上程序出现异常,需要回滚事务,这样就不会丢失数据了 //回滚事务 if(conn != null){ try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } } }
-
编写一个工具类
package utils; import java.sql.*; /** * 工具类中的构造方法都是私有的,因为工具类中的方法都是静态的,不需要new对象,直接采用类名调用 */ public class DBUtil { private DBUtil(){} //静态代码块在类加载时执行,并且只执行一次 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/datas","root","1051976202"); } /** * 关闭资源 * @param coon 连接对象 * @param ps 数据库操作对象 * @param rs 结果集 */ public static void close(Connection coon, 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(coon != null){ try { coon.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
测试:
import utils.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * 此程序的两个任务: * 1、测试DBUtil是否好用 * 2、模糊查询怎么写 */ public class JDBCTest11 { public static void main(String[] args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接 conn = DBUtil.getConnection(); //获取预编译的数据库操作对象 //错误写法: /* String sql = "select ename from emp where ename like '_?%' "; ps = conn.prepareStatement(sql); ps.setString(1,"A");*/ String sql = "select ename from emp where ename like ? "; ps = conn.prepareStatement(sql); ps.setString(1,"_A%"); rs = ps.executeQuery(); //这里查询的时候就成为:select ename from emp wehre ename like '_A%'; //这个不是SQL注入,查询的是值,传进去的也是个值,不是SQL语句 while(rs.next()){ System.out.println(rs.getString("ename")); } } catch (SQLException e) { e.printStackTrace(); }finally { DBUtil.close(conn,ps,rs); } } }
-
悲观锁、乐观锁
-
悲观锁:事务必须排队执行,数据锁住了,不允许并发(行级锁:select后面添加for update)
select ename,job,sal from emp where job='MANAGER' for update; 注意:当这几条数据被锁住的时候,其他线程执行到此处想要修改时,必须等该线程/事务结束才能继续,在此期间,其他的线程不允许修改锁住的数据
-
乐观锁:支持开发,事务也不需要排队,只不过需要一个版本号
-
程序演示
锁住:
import utils.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /* 这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关的信息。 */ public class JDBCTest12 { public static void main(String[] args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //获取连接 conn = DBUtil.getConnection(); //开启事务 conn.setAutoCommit(false); //执行sql语句 String sql = "select ename,job,sal from emp where job =? for update "; ps = conn.prepareStatement(sql);//预编译 ps.setString(1,"MANAGER");//给?赋值 //遍历结果集 rs =ps.executeQuery(); while (rs.next()){ System.out.println(rs.getString("ename")+","+rs.getString("job") +","+rs.getString("sal")); } //提交事务(事务结束) conn.commit(); //在此处DeBug,卡在这里之后,执行JDBCTest13。然后JDBCTest13会卡住,等待此程序执行结束后才会继续执行 } catch (SQLException e) { if(conn != null){ try { //回滚事务(事务结束) conn.commit(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); }finally { //6、关闭资源 DBUtil.close(conn,ps,rs); } } }
锁住的同时另一个线程执行,并进入等待状态。
import utils.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; /* 这个程序负责修改被锁住的数据 JDBCTest12执行过程中,此程序执行,进入等待状态:等待12执行结束后才会继续执行 JDBCTest12DeBug继续执行,JDBCTest12执行结束后,JDBC13继续执行,最后输出改变的记录条数:3 */ public class JDBCTest13 { public static void main(String[] args){ Connection conn = null; PreparedStatement ps = null; try { DBUtil.getConnection(); conn.setAutoCommit(false); String sql = "update emp set sal= sal*1.1 where job = ?"; ps = conn.prepareStatement(sql); ps.setString(1,"MANAGER"); System.out.println(ps.executeUpdate()); conn.commit(); } catch (SQLException e) { if(conn != null){ try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); }finally { DBUtil.close(conn,ps,null); } } }
-