day02

JDBC

数据库连接池

数据库连接池是管理并发访问数据库连接的理想解决方案.

DriverManager管理数据库连接适合单线程使用,在多线程并发情况下,为了能够重用数据库连接,控制并发连接总数,保护数据库避免连接过载,一定要使用数据库连接池

连接池原理:

2.png

使用DBCP连接池(DataBase Connection Pool)

数据库连接池的开源实现非常多,DBCP是常用的连接池之一。

导入DBCP pom.xml:

    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
    </dependency>

使用DBCP:

  1. 导入连接池jar
  2. 创建连接池对象
  3. 设置数据库必须的连接参数
  4. 设置可选的连接池管理策略参数
  5. 从连接池中获得活动的数据库连接
  6. 使用连接范围数据库
  7. 使用以后关闭数据库连接
  • 这个关闭不是真的关闭连接,而是将使用过的连接归还给连接池.

案例:

    public class Demo01 {
        public static void main(String[] args) throws SQLException {
            String driver = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/tedustore";
            String username = "root";
            String password = "";
            BasicDataSource ds = new BasicDataSource();

            //设置必须的参数
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);

            //设置连接池的管理策略参数
            ds.setInitialSize(2);
            ds.setMaxActive(100);

            //使用连接池中的数据库连接
            Connection conn = ds.getConnection();

            //执行SQL
            String sql = "select 'hello' as a from dual ";
            Statement st = conn.createStatement();
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                String str = rs.getString("a");
                System.out.println(str);
            }

            //归还连接到数据库连接池!!
            conn.close();
        }
    }

为了便捷的使用连接池,经常将连接池封装为一个连接管理工具类:

/**
 * 连接池版本的 数据库 连接管理工具类
 * 适合于并发场合
 */
public class DBUtils {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    private static int initSize;
    private static int maxActive;
    private static BasicDataSource ds;

    static {
        ds = new BasicDataSource();
        Properties cfg = new Properties();
        try {
            InputStream in = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
            cfg.load(in);
            //初始化参数
            driver = cfg.getProperty("jdbc.driver");
            url = cfg.getProperty("jdbc.url");
            username = cfg.getProperty("jdbc.username");
            password = cfg.getProperty("jdbc.password");
            initSize = Integer.parseInt(cfg.getProperty("initSize"));
            maxActive = Integer.parseInt(cfg.getProperty("maxActive"));
            in.close();

            //初始化连接池
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            ds.setInitialSize(2);
            ds.setMaxActive(maxActive);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    public static Connection getConnection() {
        try {
            //getConnection()从连接池中获取重用
            //的连接,如果连接池满了,则等待.
            //如果有连接归还,则获取重用的连接
            Connection conn = ds.getConnection();
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public static void close(Connection conn) {
        if(conn!=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

其配套参数配置文件 db.properties:

# db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/tedustore
jdbc.username=root
jdbc.password=

# paramter for BasicDataSource
initSize=2
maxActive=2

测试:

public class Demo02 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "select 'Hello' as a from dual";
            Statement st = conn.createStatement();
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                String str = rs.getString("a");
                System.out.println(str);
            }
            rs.close();
            st.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

DBUtils 简化了数据库访问

连接池并发性测试:

测试原理:

测试案例:

/**
 * 连接池并发测试
 */
public class Demo03 {
    public static void main(String[] args) {
        Thread t1 = new DemoThread(5000);
        Thread t2 = new DemoThread(6000);
        Thread t3 = new DemoThread(1000);
        t1.start();
        t2.start();
        t3.start();
    }
}

class DemoThread extends Thread{
    int wait;

    public DemoThread(int wait) {
        this.wait=wait;
    }

    public void run() {
        Connection conn = null;
        try {
            //getConnection()方法在连接池中没有
            //连接可以使用时候,会阻塞等待
            conn = DBUtils.getConnection();
            System.out.println("获取了连接:"+conn);
            Thread.sleep(wait);

            String sql = "select 'Hello' as a from dual";
            Statement st = conn.createStatement();
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()){
                System.out.println(rs.getString("a"));
            }
            System.out.println(wait+"结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

PreparedStatement

PreparedStatement 对象用于执行带参数的预编译执行计划,其原理是:

3.png

关于执行计划:

  1. 任何SQL执行过程都是先编译“执行计划”,再执行“执行计划”

  2. 数据库为了优化性能,载SQL相同时候,会重用执行计划

    • 执行计划编译较慢
    • 重用执行计划可以可以提高数据库性能
  3. 数据库只有在SQL语句完全一样时候才重用相同的执行计划

    • 如果SQL语句中有一个字符的更改,也会执行不同的执行计划
    • SQL中一个空格或者一个大小写不同也会创建不同的执行计划

PreparedStatement 好处是可以重复使用执行计划,提过DB效率

使用步骤

  1. 将带参数的SQL发送到数据库创建执行计划
  2. 替换执行计划中参数
  3. 执行执行计划,得到结果

案例:

/**
 * 演示预编译的SQL执行计划
 * @author soft01
 *
 */
public class Demo04 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();

            //创建带参数的SQL语句.
            String sql = "insert into robin_demo (id,name) values (?,?)";

            //将SQL发送数据库,创建执行计划
            //返回值 ps 就代表执行计划
            PreparedStatement ps = conn.prepareStatement(sql);

            //替换执行计划中的参数,2个参数
            //按照序号发送参数
            ps.setInt(1, 9);
            ps.setString(2, "Andy");

            //执行“执行计划”
            int n = ps.executeUpdate();
            System.out.println(n);

            //再次重复使用执行计划
            ps.setInt(1, 100);
            ps.setString(2, "Wang");
            n = ps.executeUpdate();
            System.out.println(n);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}   

案例:

/**
 * 使用执行计划更新语句
 * @author soft01
 *
 */
public class Demo05 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();

            String sql = "update robin_demo set name=? where id=?";
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, "Lao wang");
            ps.setInt(2, 100);
            int n = ps.executeUpdate();
            System.out.println(n);//1

            ps.setString(1, "Jerry");
            ps.setInt(2,8);
            n = ps.executeUpdate();
            System.out.println(n);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn); 
        }
    }
}

案例: 带参数的查询功能

/**
 * 利用PS 实现查询功能
 */
public class Demo06 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "select id,name from robin_demo where name like ? ";
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, "%w%");
            ResultSet rs = ps.executeQuery();
            while(rs.next()) {
                //getInt(列的序号),利用序号获取值
                int id = rs.getInt(1);
                String name = rs.getString(2);
                System.out.println(id+","+name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

PreparedStatement 可以避免SQL注入攻击

何为注入攻击:

用户输入了 含有SQL成分的参数,参数在拼接SQL时候造成SQL语句的语意改变!
改变SQL语句的执行计划!最后的执行结果就完全变了!!!

如何避免:

  1. 拦截用户输入的SQL成分
  2. 固定执行计划,避免改变执行逻辑

原理:

4.png

一个注入攻击的例子:

create table robin_user(
    id int(10),
    name varchar(100),
    pwd varchar(100)
);
insert into robin_user (id,name,pwd) values (1,'tom','123');
insert into robin_user (id,name,pwd) values (2,'jreey','123');

案例代码:

/**

  • SQL 注入演示

  • /
    public class Demo07 {
    public static void main(String[] args) {
    //获取用户输入
    Scanner in = new Scanner(System.in);
    System.out.print("用户名:");
    String name = in.nextLine();
    System.out.print("密码:");
    String pwd = in.nextLine();
    //检查登陆情况
    boolean pass = login(name,pwd);
    if(pass) {
    System.out.println("欢迎你!"+name);
    } else {
    System.out.println("用户名或密码错误");
    }
    }

     //检查用户是否能够登陆
     public static boolean login(String name,String pwd) {
         String sql = "select count(*) as c from robin_user where name='"+name+"' and pwd='"+pwd+"' ";
         System.out.println(sql);
         Connection conn = null;
         try {
             conn = DBUtils.getConnection();
             Statement st = conn.createStatement();
             ResultSet rs = st.executeQuery(sql);
             while(rs.next()) {
                 int n = rs.getInt("c");
                 return n>=1;
             }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             DBUtils.close(conn);
         }
         return false;
     }
    

    }

测试:

在输入用户名 tom 和密码: 1‘ or '1'='1 时候出现注入攻击现象

使用PS 就可以避免注入攻击,更新login方法如下:

/**
 * 利用 PS 就可以避免注入攻击
 * @param name 
 * @param pwd
 * @return
 */
public static boolean login(String name,String pwd) {
    String sql = "select count(*) as c from robin_user where name=? and pwd=? ";
    Connection conn = DBUtils.getConnection();
    try {
        conn = DBUtils.getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, name);
        ps.setString(2, pwd);
        ResultSet rs = ps.executeQuery();
        while(rs.next()) {
            int n = rs.getInt("c");
            return n>=1;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        DBUtils.close(conn);
    }
    return false;
}

测试结果不会发生注入攻击


作业

实现课堂全部案例代码

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

推荐阅读更多精彩内容

  • 非本人总结的笔记,抄点笔记复习复习。感谢传智博客及黑马程序猿感谢传智博客及黑马程序猿 Spring整合web项目 ...
    键盘瞎阅读 619评论 0 1
  • JDBC概述 在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如...
    usopp阅读 3,534评论 3 75
  • 非本人总结的笔记,抄点笔记复习复习。感谢传智博客及黑马程序猿成长 关联查询 数据中的表结构 数据库的分析方法 第一...
    键盘瞎阅读 1,092评论 3 5
  • 一、自己学习后的五个收获 1.学会使用简书 2.为自己照了形象照,并打算今天中午更换头像 3.写完了特训营心态篇 ...
    旅途7阅读 127评论 0 0
  • 我有罪 一万年前,我的父亲成熟落地,与空气中的酵母欢爱 人类品尝到我,从此“酒后无德” 我有罪 7000年前我的同...
    鸿伟阅读 419评论 0 1