【JDBC:连接MySQL数据库】出现SQL注入的解决办法、什么时候需要使用SQL注入、事务的使用、悲观锁乐观锁

news2024/11/28 14:12:07

JDBC

  1. JDBC是什么?

    Java Database Connectivity(java语言连接数据库)

    java.sql.*;(这个包下有很多接口)

  2. 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){}//只能喂猫
    
  3. 思考:为什么SUN制定一套JDBC接口呢?

    因为每一个数据库的底层实现原理都不一样。

    Oracle数据库有自己的原理、MySQL数据库也有自己的原理、MS SqlServer数据库也有自己的原理

    …每一个数据库都有自己的实现原理。

  4. 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有自己的配置方式

  5. JDBC编程六步(需要记住)

    • 第一步:注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
    • 第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,属于进程之间的通信,重量级的,使用完毕之后一定要关闭通道。)
    • 第三步:获取数据库操作对象(专门执行sql语句的对象)
    • 第四步:执行SQL语句(DQL、DML…)
    • 第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才执行处理查询结果)
    • 第六步:释放资源(使用完资源之后一定要关闭资源。java和数据库属于进程间的通信,开启之后一定要关闭)
  6. 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();
                    }
                }
                
            }
            
            
        }
    }
    
  7. 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();
                    }
                }
                
            }
    	}
    }
    
  8. 注册驱动的另一种方式(这种方式常用)

     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();
            }
            
    	}
    }
    
  9. 将连接数据库的所有信息配置到配置文件中

    /*
    	实际开发中不建议把连接数据库的信息写死到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
    
  10. 处理查询结果集(遍历结果集)

    在这里插入图片描述

    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();
                    }
                }
            }
    	}
    }
    
  11. 模拟用户登录实现的功能(出现了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;
        }
    }
    
    
  12. 解决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;
        }
    }
    
    
  13. 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();
                    }
                }
            }
        }
    }
    
  14. 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();
                    }
                }
            }
        }
    }
    
    
  15. 账户转账事务

    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();
            }
        }
    }
    
  16. 编写一个工具类

    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);
            }
        }
    }
    
    
  17. 悲观锁、乐观锁

    • 悲观锁:事务必须排队执行,数据锁住了,不允许并发(行级锁: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);
              }
          }
      }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】数据以及位运算

位和位运算 C语言中数据的表示方法各种数据类型可表示的数值范围位和CHAR_BITsizeof运算符整型的内部表示无符号整数的内部表示有符号整数的内部表示 位运算位运算符位与运算位或运算位异或运算位取反运算位左移运算符位右移运算符逻辑位移与算术位移 C语言中数据的表示方法 各…

HCIA-RS实验-路由配置-OSPF 单区域配置

OSPF&#xff08;Open Shortest Path First&#xff09;是一种基于链路状态的路由协议&#xff0c;常用于大型企业网络中。在一个单区域的OSPF网络中&#xff0c;所有的路由器都属于同一个区域&#xff0c;这种配置方式相对简单直观。本文将介绍OSPF单区域的配置方法。 这篇文章…

Vultr 简介和域名购买

1. 网络地址 Vultr: https://my.vultr.com/ Namecheap: Namecheap.com • Login 2. Vultr 简介&#xff1a; Vultr是一家知名的云计算服务提供商&#xff0c;成立于2014年&#xff0c;总部位于美国新泽西州。Vultr提供高性能的云服务器、存储、网络和应用服务&#xff0c;以…

如何把低像素图片转成高清,分享四个方法给大家!

当图片的像素较低时&#xff0c;通常会导致图片模糊不清。然而&#xff0c;我们可以采取一些方法来将低像素图片转变为高清。下面我将介绍几种简单易用的方法&#xff0c;帮助您快速提高照片的分辨率&#xff0c;还原照片的清晰度&#xff01; 方法一&#xff1a;记灵在线工具…

【attention|Tensorformer】从attention走向Transformer

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 概括 说明&#xff1a; 后续增补 1. 正文 1.0 通俗理解 人类视觉的注意力&#xff0c;简单说就第一眼会注意在一幅图像的重要位置上。 而在程序中&am…

vim实用功能汇总

文章目录 1. 读代码1.1 vim中文件跳转1.2 语法高亮模式1.3 Visual 模式 2. 配置vim成为python的IDE 1. 读代码 1.1 vim中文件跳转 vim中文件跳转 这个其实不是靠什么插件完成的&#xff0c;而是vim编辑器自带的功能把光标放在要跳转的文件上&#xff0c;按下gf&#xff0c;即…

YOLOv5改进系列(7)——添加SimAM注意力机制

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

delete 清空表之后,磁盘空间未发生变化?

上篇文章结尾和小伙伴们留了一个小问题&#xff0c;就是关于 optimize table 命令&#xff0c;今天我想花点时间再来和小伙伴们聊一聊这个话题。 1. 删除空洞 1.1 案例展示 首先我们先来看这样一个例子。 我现在有一个名为 sakila 的数据库&#xff0c;该库中有一个 film 表…

C++轻量级跨平台桌面GUI库FLTK的简单使用

C的跨平台桌面GUI库有很多&#xff0c;大体上分成两种流派&#xff1a;retained mode和retained mode。 其中前者是主流的桌面GUI机制框架&#xff0c;包括&#xff1a;Qt、wxwidgets、gtk、juce等后者是一些游戏引擎编辑器常用的GUI机制框架&#xff0c;包括&#xff1a;imgu…

C#常见技能_数组

前几天一个学员在学习C#与数组交互时,也不知道数组可以用来做什么 。下面我们就详细讲讲C# 和数组交互的相关知识。 在C#编程中&#xff0c;数组是一种非常重要的数据结构&#xff0c;它可以存储多个相同类型的数据&#xff0c;并且使用索引来访问这些数据。在实际应用中&…

5.2.7 因特网控制报文协议ICMP

5.2.7 因特网控制报文协议ICMP 我们知道因特网的技术核心是IP数据报&#xff0c;IP数据报的最大特点是无连接不可靠&#xff0c;但实际中因特网中也会存在通信线路或者是处理器的故障、路由器拥塞等等使得无法接收或者处理数据报&#xff0c;路由表也误导导致出现路由环路等原…

微软MFC技术运行机制

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下微软MFC技术运行机制。 很多初学者误以为VC开发必须使用MFC&#xff0c;其实不一定的。MFC的使用只能是提高程序在某些情况下的开发效率&#xff0c;而不能替代整个Win32程序设计。我认为我们有…

python grpc使用示例

1. grpc简介 1.1 概述 gRPC是搭建分布式应用接口和客户端的框架。在 gRPC 中&#xff0c;客户端应用程序可以直接调用不同机器上的服务器应用程序上的方法&#xff0c;就像它是本地对象一样&#xff0c;可以更容易创建分布式应用程序和服务。与许多 RPC 系统一样&#xff0c;gR…

AI绘画MidJourney 酷炫艺术风格效果,总有一款你喜欢

文 / 高扬 今天六一儿童节&#xff0c;祝各位大朋友节日快乐。 这次以儿童为主题&#xff0c;看看MidJourney的绘画风格&#xff0c;在这里&#xff0c;我使用的默认V5.1版本。 图画场景是一个男孩和一个女孩在田野玩耍&#xff0c;对应的英文是&#xff1a;A boy and a girl a…

使用PyQT实现模拟表盘时钟的显示效果

代码 class clockThread(QThread):update_ui_signal pyqtSignal(str)def __init__(self, window):super(clockThread, self).__init__()# 信号绑定槽函数self.update_ui_signal.connect(self.draw_time)self.hour 0self.minute 0self.second 0self.window windowself.win…

数据结构与算法·第6章【树】

基本操作 树的相关定义 树的深度&#xff08;高度&#xff09;&#xff1a;树中叶子结点所在的最大层次 森林&#xff1a; m m m棵互不相交的树的集合 二叉树 二叉树或为空树&#xff0c;或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成。 性质 二…

JavaCV音视频开发宝典:使用JavaCV读取海康平台或海康网络摄像头sdk回调视频TS码流并解析预览图像

《JavaCV音视频开发宝典》专栏目录导航 《JavaCV音视频开发宝典》专栏介绍和目录 ​ 前言 两年前博主写了如何利用JavaCV解析各种h264裸流,《JavaCV音视频开发宝典:使用javacv读取GB28181、海康大华平台和网络摄像头sdk回调视频码流并解析预览图像》,但是随着时间变化,各…

企业邮箱如何设置邮件审核

有的时候对外给客户或合作伙伴等发送邮件需要领导审核后再发&#xff0c;以前都是先发给领导&#xff0c;领导审核以后再重新发给客户&#xff0c; 这样的流程太过繁琐。 新的邮件审核功能既能满足审核需求&#xff0c;又避免了重复发信&#xff0c;可以极大提高工作效率。 设…

使用VitePress静态网站生成器创建组件库文档网站并部署到GitHub

Vue3TSVite开发组件库并发布到npm 网站在线预览&#xff1a; Vue Amazing UI | Amazing UI Components LibraryAmazing UI 组件库https://themusecatcher.github.io/vue-amazing-ui/参考文档&#xff1a; VitePress 目录 一、安装依赖及配置 1、安装 vitepress 2、在 p…

想要精通算法和SQL的成长之路 - 反转链表

想要精通算法和SQL的成长之路 - 反转链表 前言一. 反转链表 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 反转链表 原题链接 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 思路如下&#xff1a; 我们可以通过一次遍历&#xff…