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

JDBC

  1. JDBC是什么?

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

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

  2. JDBC的本质是什么?

    JDBC的本质是SUN公司制定的一套接口(interface)

    接口都有调用者和实现者。面向接口调用、面向接口写实现类,这都属于面向接口编程。
    【JDBC:连接MySQL数据库】出现SQL注入的解决办法、什么时候需要使用SQL注入、事务的使用、悲观锁乐观锁_第1张图片

    为什么要面向接口编程?

    解耦合:降低程序的耦合度,提高程序的扩展力。
    多态机制就是非常典型的:面向抽象编程(不要面向具体编程)
        建议:
            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. 处理查询结果集(遍历结果集)

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

    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 userLoginInfo = initUI();
            //验证用户名和密码
            boolean loginSuccess = login(userLoginInfo);
            //最后输出结果
            System.out.println(loginSuccess ? "登陆成功":"登录失败");
        }
    
        /**
         * 用户登录
         * @param userLoginInfo 用户登录信息
         * @return  false表示登陆失败、true表示登录成功
         */
        private static boolean login(Map 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 initUI() {
            Scanner s = new Scanner(System.in);
    
            System.out.println("用户名:");
            String loginName = s.nextLine();
    
            System.out.println("密码:");
            String loginPwd = s.nextLine();
    
            Map 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 userLoginInfo = initUI();
            //验证用户名和密码
            boolean loginSuccess = login(userLoginInfo);
            //最后输出结果
            System.out.println(loginSuccess ? "登陆成功":"登录失败");
        }
    
        /**
         * 用户登录
         * @param userLoginInfo 用户登录信息
         * @return  false表示登陆失败、true表示登录成功
         */
        private static boolean login(Map 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 initUI() {
            Scanner s = new Scanner(System.in);
    
            System.out.print("用户名:");
            String loginName = s.nextLine();
    
            System.out.print("密码:");
            String loginPwd = s.nextLine();
    
            Map 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;
      注意:当这几条数据被锁住的时候,其他线程执行到此处想要修改时,必须等该线程/事务结束才能继续,在此期间,其他的线程不允许修改锁住的数据
      
    • 乐观锁:支持开发,事务也不需要排队,只不过需要一个版本号

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

    • 程序演示

      锁住:

      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);
              }
          }
      }

你可能感兴趣的:(JDBC,数据库,mysql,sql)