JDBC基础2


title: "Jdbc2"
date: 2019-08-15T10:20:46+08:00


本章内容为:《JDBC基础2》

作者:nuoccc

在上一篇文章了解完JDBC基础,这篇文章主要讲jdbc的使用以及如何封装成一个jdbc工具类。

不知道观看的朋友还有印象没,上一篇文章中Connection对象创建Statement对象时,用到了两种方法。

第一个是createStatement(); 另外一个是prepareStatement();

两个方法是把sql语句传参给数据库,但两个方法到底有什么区别呢?

1.SQL注入改用prepareStatement

​ 可能看到第一个小标题就知道了,就是prepareStatement可以防止SQL注入,那什么是SQL注入?

百度百科对于SQL注入的描速:

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。

JDBC的根本作用就是配合后面的Servlet部署网页实时的获取用户从网页上的输入,并对数据库数据进行CRUD,

所以我们的数据都是动态的,也会使用动态拼接SQL语句。

例如,我们有个登陆界面输入账号和密码,我们就会去数据库找是否存在一行数据包含这个账号和密码,

我们用Statement来使用SQL语句是这样的:

String sql = "select * from testtable where username='"+username+"' and password='"+password+"'";
-- 等号后面的username和password代表用户传递过来的数据
Statement.executeQuery(sql);

但是这里就有一个问题,就是SQL注入,例如恶意攻击的人在输入密码时,加一个"or "a"="a;

那我们的sql语句就会变成:

String sql = "select * from testtable where username = '"+username+"' and password = '+password+"or"a"="a'";
-- 这样a=a永远成立,所以就能不需要密码就能登录

那为什么使用prepareStatement可以防止SQL注入呢?

因为prepareStatement使用的是占位符,在SQL语句的体现中是这样的:

String sql =  "select * from testtable where username=? and password=?";

然后这时候会执行prepareStatement的预编译这也是防止SQL注入的原因,并且性能也会高一些。

preparedStatement = conn.prepareStatement(sql);

那为什么执行预编译能防止SQL注入呢?因为我们是预编译之后再进行赋值的,

例如同样的恶意攻击的人在输入密码时,加一个"or "a"="a;

这时候SQL语句就会变成:

String sql = "select * from testtable where username = ? and password = ''"or"a"="a'";

preparedStatement = conn.prepareStatement(sql);
-- 执行预编译

preparedStatement.setObject(1,username); 
preparedStatement.setObject(2,userpassword);
-- 执行预编译之后,可以直接对占位符的位置传递值,所以之后的SQL语句应该如下

String sql = "select * from testtable where username='"+username+"' and password='"+password+"'";
-- 覆盖了攻击者的SQL语句达到了防止SQL注入,实际应该是改变的是字节码的值,这里只是方便理解。

prepareStatement.execueteQuery();
-- 执行数据库的查询 不需要再传递sql了,因为之前已经 编译过一次了

所以不管是安全性还是效率上我们都更加偏向于使用prepareStatement来执行对数据库的传递。

2.完成JDBC的登录和注册操作

上一篇文章了解了jdbc最基础的操作,然后我们就在此基础上完成常用的注册和登录功能。

首先先创建一个用户表:

create table j_user(
id int(4) primary key auto_increment,
username varchar(20) not null,
password varchar(20) not null,
age int(3) not null
)engine=innodb default charset=uft8;

2.1 注册功能

public static void main(String[] args){
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入您的账号:");
    String username = sc.next();
    System.out.println("请输入您的密码:");
    int age = sc.nextInt();
    System.out.println("请输入您的年龄:");
    String password = sc.next();
    
    Connection con = null;
    PrepareStatement ps = null;
    //开始jdbc的初始化,创建两个空对象
    Class.forName("com.mysql.jdbc.Driver");//加载驱动
    con= conn=DriverManager.getConnection("jdbc:mysql:///testjdbc","root","123456");
    //本地127.0.0.1可以使用//代替
    String sql = "insert into values(null,?,?,?)";
    //第一个是id是自增的所以为空,另外三个后面传参先使用占位符。
    ps = con.prepareStatement(sql);
    //获得prepareStatement对象,并预编译sql语句。
    ps.setObject(1,username);
    ps.setObject(2,password);
    ps.setObject(3,age);
    //设置值,并且必须跟?一一对应!
    int count = ps.executeUpdate();
    //执行数据库语句,并返回一个值,为1成功,为0失败
    if(count>0){
        System.out.println("注册成功");
    }else{
        System.out.println("注册失败");
    }
    if(ps!=null){
        ps.close();
    }
    if(con!=null){
        con.close;
    }
    //最后关闭流,同样的整个代码我没抛异常,麻烦自行解决。  
}

完成了注册功能,我们再来完成登录功能。

2.2 登录功能

public static void main(String[] args){
    Scanner sc = new Scanner(Syetem.in);
    System.out.println("请输入登录账号");
    String username = sc.next();
    System.out.println("请输入登录密码");
    String password = sc.next();
    
    Connection con = null;
    PrepareStatement ps = null;
    ResultSet rs = null;
    //初始化三个空对象。
    
    Class.forName("com.mysql.jdbc.Driver");
    con=DriverManager.getConnection("jdbc:mysql:///testjdbc","root","123456");
    //加载驱动,连接数据库获得Connection对象
    
    String sql = "select * from j_user where username=? and password=?";
    ps=con.prepareStatement(sql);
    //预编译并获得prepareStatement对象
    
    ps.setObject(1,username);
    ps.setObject(2,password);
    //把用户输入的值传到占位符。
    
    rs = ps.executeQuery();
    //执行数据库查询语句,并返回一个结果集
    
    if(rs.next()){
        //如果结果集里面有值,说明查询到有这一行数据,说明账号和密码正确
        System.out.println(username+"欢迎登录");
    }else{
        System.out.println("账号或者密码错误");
    }
    
    if(rs!=null){
         rs.close();
    }
    if(preparedStatement!=null){
         preparedStatement.close();
    }
    if(conn!=null){
         conn.close();
    }
    //关闭流,没抛异常自行抛。
}

这样用JDBC就完成了最基础的注册和登录功能,但是有没有发现,每次进行类似操作我们都需要输入这么大一行代码,如果我都要进行查询功能,但我就要写两个类似的代码,冗余度太高,所以我们能不能把这些功能封装成一个工具类,需要的时候我直接调用这个函数就行,于是我们下面就来实现。

3.JDBC工具类

public class BaseDao{
    
    private Connection con = null;
    private prepareStatement ps = null;
    private ResultSet rs = null;
    //因为我们这个工具类包含CRUD 所以要首先创建这三个 空对象
    
    public void openCon(){
        //第一个封装方法,用于建立与数据库的连接
        Class.forName("com.mysql.jdbc.Driver");
        //加载驱动
        con=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/testjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=true","root", "123456");
        //连接数据库并获得Connection对象
        
    }
    
    public void closeCon(){
        //第二个封装方法,用于关闭流
        if(rs!=null){
            rs.close();
        }
        if(ps!=null){
            ps.close();
        }
        if(conn!=null){
            conn.close();
        }
    }
    
    public int myExecuteUpdate(String sql , Object[] parms){
        /*第三个封装方法,用于执行CUD,这里除了传递sql语句,我们还把?符对象的所有值放在一个数组内,
        这样减少了耦合度,例如执行一个插入语句:
        insert into j_user values(null,?,?,?);
        Object[] parms = {'张三','123456',23};
        */
        openCon();//调用连接数据库方法
        ps=con.prepareStatement(sql);//执行预编译并获取prepareStatement对象
        if(parms!=null){
            //把数组里面的值遍历出来进行赋值
            for(int i=0;i

这样一个JDBC工具类就完成了,基本的连接,关闭,CRUD操作全部封装在工具类里面了。

但是还有个问题,我们这个既然是工具类,那么就任何环境下都适用,但是我们这个数据库是本地的,耦合度太高,所以我们还需要把数据库配置信息,单独放在一个文件内。

Java中这种配置文件格式一般有两种:

  • XML
  • properties 格式 key=value

这里我们采用第二种文件,创建一个jdbc.properties文件,放在跟工具类同目录下。

# jdbc.properties存放的内容

url=jdbc:mysql://127.0.0.1:3306/testjdbc?useUnicode=true&characterEncoding=UTF-8&useSSL=true
username=root
# 数据库用户名
password=123456
# 数据库密码
driverName=com.sql.jdbc.Driver
# 驱动

这样生成了配置文件之后,我们就需要修改一下我们的工具类了

import java.util.Properties;
//java给的一个对应Properties文件的类

Public class BaseDao{
    private Connection con=null;
    private preparedStatement ps = null;
    private ResultSet rs =null;
    
    private static String driverClass;
    private static String url;
    private static String username;
    private static String password;
    //对应jdbc.properties四个属性,弄成static静态属性,直接加载
    
    static{
        //静态属性,使用工具类时,直接加载并且只加载一次,效率高
        Properties p = new Properties();//创建一个Properties的对象
        p.load(BaseDao.class.getClassLoader().getResourceAsStream("jdbc.properties"));
        //BaseDao的类加载器去获得jdbc.properties文件流并加载给p对象
        url=p.getProperty("url");
        username=p.getProperty("username");
        password =p.getProperty("password");
        driverClass = p.getProperty("driverName");
        //通过jbdc.properties的属性来获取值
        Class.forName(driverClass);
        //加载驱动
    }
    
    public void openCon(){
        con=DriverManager.getConnection(url,username, password);
    }
    ...
    //剩下的跟前面一样就不写了
}

这样一个工具类就彻底完成了,然后本篇文章就到这里结束了。

你可能感兴趣的:(JDBC基础2)