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<parms.length;i++){
                ps.setObject(i+1,parms[i]);
                //因为是从第一个开始赋值,不是从0开始,所以是i+1
            }
        }
        return ps.executeUpdate();
        //执行CUD语句并返回一个值,是1成功,是0失败
        closeCon();
        //关闭数据库
    }
    
    public ResuleSet myExecuteQuery(String sql,Object[] parms){
        openCon();
        ps=con.prepareStatement(sql);//预编译
        if(parms!=null){
            for(int i = 0;i<params.length;i++){
                ps.setObject(i+1,parms[i]);
            }
        }
        return ps.executeQuery();
        
    }

}

这样一个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);
    }
    ...
    //剩下的跟前面一样就不写了
}

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容