JDBC(三)(工具类,sql注入优化,事务自动机制,悲观锁,乐观锁)

工具类:

解决代码重复:

1.编写配置文件:

在src目录下创建config.properties配置文件
在这里插入图片描述

2.编写jdbc工具类:

在config.properties里面编写:

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/dbtest
username=root
password=123456

3.使用jdbc工具类优化student表CRUD操作

在项目包下面建一个utils包,然后新建一个类:把需要封装的方法写在里面

package com.www.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {
    //1.私有构造方法


    public JDBCUtils() {
    }

    //2.声明所需的配置文件
    private  static String driverClass;
    private  static String url;
    private  static String username;
    private  static String password;
    private static Connection con;
    //3.提供静态代码块,读取配置文件的信息为变量赋值,注册驱动
    static {
        try {
            //读取配置文件的信息为变量赋值
            //io流,自动从流信息中加载集合
           InputStream is =JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties");
            Properties prop = new Properties();//流对象
            prop.load(is);
            driverClass=prop.getProperty("driverClass");
            url=prop.getProperty("url");
            username=prop.getProperty("username");
            password=prop.getProperty("password");
            //注册驱动
            Class.forName(driverClass);
        }catch (Exception  e){
            e.printStackTrace();
        }
    }
    //4.提供获取数据库连接方法
    public  static Connection getconnection(){
        try {
            con= DriverManager.getConnection(url,username,password);
        }catch (SQLException e){
            e.printStackTrace();
        }
              return con;
    }
    //提供释放资源方法
    public static void close(Connection con, Statement stat, ResultSet rs){
        if (con != null) {//在进行判断一下是否为null
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stat != null) {
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

(一):提供静态代码块,读取配置文件的信息为变量赋值,注册驱动,静态代码块在该类初始化是只运行一遍;上述静态代码块类似与系统自带的注册驱动模式一样,从摸个文件中读取地址,密码信息。这样做到目的安全。其他人只需要把文件给使用者填写地址,把密码隐藏起来。

 InputStream is =JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties");
            Properties prop = new Properties();//流对象
            prop.load(is);
            driverClass=prop.getProperty("driverClass");
            url=prop.getProperty("url");
            username=prop.getProperty("username");
            password=prop.getProperty("password");

如果不写这一步:
就直接把地址,密码写在再上面:代码如下:

public  static Connection getconnection(){
        try {
            con= DriverManager.getConnection(url,username,password)con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/dbtest", "root", "123456
        }catch (SQLException e){
            e.printStackTrace();
        }
              return con;
    }

(二):使用静态写的方法可以直接用类名调用即可;

简化后代码:

package com.www;
import com.www.utils.JDBCUtils;
import java.beans.Statement;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;

public class JDBCDemo1 {

    public static void main(String[] args) throws Exception{
        Connection con=null;
//    第一步:注册驱动://调用JDBC工具自动加载静态代码块  
//    第二步:获取连接
       con=JDBCUtils.getconnection();
//    第三步:获取数据库操作对象
        java.sql.Statement  stat =  con.createStatement();
//    第四步:执行sql语句
        String sql ="SELECT * FROM employees";
        ResultSet rs= stat.executeQuery(sql);
//    第五步:处理查询结果集
        while(rs.next()){
  System.out.println(rs.getInt("employee_id")+"\t"+rs.getString("last_name"));
        }
    第六步:释放资源(使用完一定要关闭)
        JDBCUtils.close(con,stat,rs);
    }
}

sql注入优化

1.什么是sql注入:

例如:用户名乱填,密码为’or 1=1-,这种情况下点击登录按钮后竟然成功登录了。我们看一下最终的SQL就会找到原因:
SQL语句参数化的登录语句是这样的

Select * From  Where mytest UserName=xxx and Password=xxx

然后判断返回的行数,如果有返回行,证明账号和密码是正确的,即登录成功,而这样的语句的话,就很容易被注入代码,直接在密码框输入’or 1=1-,那么它的登录语句就会变成

"Select * From mytest Where UserName='xxx' and Password='aaa'or 1=1-'"

从代码可以看出,前一半单引号被闭合(‘aaa’),后一半单引号被 “–”给注释掉,中间多了一个永远成立的条件1=1,这就造成任何字符都能成功登录的结果。而Sql注入的危害却不仅仅是匿名登录。

**2.优化;

案例:**

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import static sun.security.jgss.GSSUtil.login;

public class JDBCtest  {
    public static void main(String[] args) {
        //初始化一个页面
       Map<String,String> userLoginInfo = initUI();
        //验证用户和密码
        boolean loginSuccess=login(userLoginInfo);
        //输出结构
        System.out.println(loginSuccess ? "登陆为真":"登陆失败");
    }
    /**
     * 用户登陆
     * @ruturn false 表示失败,ture 表示成功
     */
    @Test
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess =false;
        Connection con =null;
        Statement stat = null;
        ResultSet rs = null ;

        //注册驱动,也可以省略
        try {
        //获取连接
             con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
        //获取数据库作对象
                stat=con.createStatement();
        //执行sql
            String sql ="SELECT * FROM t_user WHERE loginName='"+userLoginInfo.get("username")+"'and loginPwd='"+userLoginInfo.get("password")+"'";
            rs =stat.executeQuery(sql);
            //处理结果
            if(rs.next()){
                //登陆成功
                loginSuccess =true;
            }
        //
        //
    }catch (Exception e){
        e.printStackTrace();
    }finally {
            if(con!=null){
                try {
                    con.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(stat!=null){
                try {
                    con.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(rs!=null){
                try {
                    con.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            return loginSuccess;
        }

    }


    /**
     * 初始化用户页面
     * @return 用户输入的用户名和密码等相关登陆信息
     */
    private static Map<String,String> initUI() {
        Scanner sc = new Scanner(System.in);
        System.out.println("用户名");
        String loginName= sc.nextLine();
        System.out.println("密码");
        String loginPwd= sc.nextLine();
        Map<String ,String> userLoginInfo =new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);
        return userLoginInfo;
    }
}

优化:


     * 用户登陆
     * @ruturn false 表示失败,ture 表示成功
     */
    @Test
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess =false;
        Connection con =null;
        PreparedStatement ps =null;//PreparedStatement是预编译数据库操作对象
        ResultSet rs = null ;
       String loginName= userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get(" loginPwd ");
        //注册驱动,也可以省略
        try {
        //获取连接
             con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
//        //获取预编译的数据库作对象

            //一个问号表示一个占位符,一个问号只接收一个值,占位符不能使用单引号扩起来
            String sql ="SELECT * FROM t_user WHERE loginName=? and  loginPwd=?";//PreparedStatement中sql框架
            //该出会发送sql框子给DBMS,然后DBMS进行sql语句的预先编译
            ps= con.prepareStatement(sql);
            //给占位符?传值,第一个问号是1,第二个问号是2
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            //执行sql
            rs=ps.executeQuery();
            //处理结果
            if(rs.next()){
                //登陆成功
                loginSuccess =true;
            }
      
    }catch (Exception e){
        e.printStackTrace();
    }finally {
            if(con!=null){
                try {
                    con.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(ps!=null){
                try {
                    ps.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(rs!=null){
                try {
                    con.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            return loginSuccess;
        }

    }


解决:只要用户提空信息不参与sql语句的编译过程就行,即使有sql语句关键字,不参与过程就不起作用。
PreparedStatement是预编译数据库操作对象,然后在传值;

3.Statement和PreparedStatement区别及应用:

关系:PreparedStatement继承自Statement,都是接口
**区别:**1.PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
详解:
2.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得,preparedstatement支持批处理

应用:

preparedstatement用于密码登陆,
statement应用于需要注入的,例如选择按照降序还是升序进行查询信息;

select id from emplees where order by id desc
select id from emplees where order by id ?
select id from emplees where order by id key

如果是占位符编译时会将?的内容变成select id from emplees where order by id ’ desc’,所以之前写?时不需要单引号;程序无法识别 ’ desc’是啥
故这里面只能应用statement,key就可以直接转换成desc

事务自动机制

在执行数据库更行的时候,都是默认的自动提交数据的,但是有很多时候,我们不能设置默认提交,生活中最常见就是,你给某人转钱,你的钱已经转出去了,但是对方在收款的时候却出现了某些问题,导致对方账户上的钱并没有增加,这个时候,为了防止问题的发生,我们就不能设置数据库默认提交。要手动提交并加锁,要不一次性成功,要不都不成功

  * 用户登陆
     * @ruturn false 表示失败,ture 表示成功
     */
    @Test
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess =false;
        Connection con =null;
        PreparedStatement ps =null;//PreparedStatement是预编译数据库操作对象
        ResultSet rs = null ;
       String loginName= userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get(" loginPwd ");
        //注册驱动,也可以省略
        try {
        //获取连接
             con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
//        //获取预编译的数据库作对象

            //一个问号表示一个占位符,一个问号只接收一个值,占位符不能使用单引号扩起来
            String sql ="SELECT * FROM t_user WHERE loginName=? and  loginPwd=?";//PreparedStatement中sql框架
            //该出会发送sql框子给DBMS,然后DBMS进行sql语句的预先编译
            ps= con.prepareStatement(sql);
            //给占位符?传值,第一个问号是1,第二个问号是2
            //一次传值;
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            
            //第二次传值
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            //执行sql
            rs=ps.executeQuery();
            //处理结果
            if(rs.next()){
                //登陆成功
                loginSuccess =true;
            } 
    }

上述代码中第一次传值默认的自动提交数据的,在等到第二次传值是,中间有个异常,第二个数据就没修改,就类似转账一样:

解决方案:

不让他自动提交,改为手动提交
在前面关闭获取预编译的数据库作对象添加con=setAtutoCommit(false),参数false为开启事务,所有传值结束后添加con.commit(),自己手动提交事务;con.close()改为回滚事务con.rollback()

悲观锁:

SELECT job,salary   FROM t_user WHERE job='aaa' for update;

这段语句后面加for update,表示某个事务结束前job为‘aaa’的数据不能修改。

**乐观锁:**可以并发运行,但是在数据后面有个标识版本1.0,原第一人改了数据就添加版本为号2.0,又来一个人修改数据,看见版本号为2.0,认为有人修改了数据,所有他就回滚数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gun_wqy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值