1 JDBC

1.1 sql注入攻击

SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。
假设有登录案例SQL语句如下:
SELECT FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;
此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为:
SELECT
FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’;
此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。

1.2 sql注入攻击的解决方式-预编译

表中有一条数据,用户名叫jack,密码为abc

  1. import java.sql.Connection;
  2. import java.sql.PreparedStatement;
  3. import java.sql.ResultSet;
  4. import java.util.Scanner;
  5. import cn.itcast.utils.JDBCUtils;
  6. /*
  7. 如何解决sql注入攻击问题。
  8. 使用Statement的子接口叫做PreparedStatment接口,对sql语句进行预编译就可以了。
  9. PreparedStatment可以预先把sql语句存储起来,然后高效的执行。
  10. 之前获取sql语句执行者对象
  11. Statment st = con.createStatment();
  12. 现在要使用PreparedStatment,PreparedStatement要通过连接对象的prepareStatement()方法获取
  13. PreparedStatement pst = con.prepareStement(sql);//需要传递一个sql语句,表示要对这个sql语句进行预编译。
  14. sql语句中动态的部分叫做参数。并且在使用的使用动态的参数要使用问号占位符代替 ?
  15. 预编译的好处:
  16. 1. 高效。
  17. 2. 解决sql注入攻击问题。
  18. */
  19. public class Demo02JDBC {
  20. public static void main(String[] args) throws Exception {
  21. Scanner sc = new Scanner(System.in);
  22. System.out.println("请您键盘录入用户名");
  23. String username = sc.nextLine();
  24. System.out.println("请您键盘录入密码");
  25. String password = sc.nextLine();
  26. //获取连接
  27. Connection con = JDBCUtils.getConnection();
  28. //对sql语句进行预编译。 也是在获取sql语句执行者对象,因为PreparedStatement是Statement的子接口。
  29. PreparedStatement pst = con.prepareStatement("SELECT * FROM user WHERE username=? AND password=?");
  30. //设置参数。 使用实际的数据替换掉问号占位符
  31. //使用PreparedStatement里面的setObject方法进行替换。
  32. //void setObject(int parameterIndex, Object x)
  33. //参数parameterIndex: 表示要替换的是第几个问号。 从1开始的。
  34. //参数x: 使用什么具体的数据替换掉问号占位符。
  35. pst.setObject(1, username);// 使用jack替换掉第1个问号占位符。
  36. pst.setObject(2, password);// 表示使用abc替换掉第2个问号占位符
  37. //替换完所有的问号占位符之后,才能去执行sql
  38. ResultSet rs = pst.executeQuery();//不能传递sql语句,因为这个sql语句已经预先存储起来了
  39. //处理结果集
  40. if(rs.next()) {
  41. System.out.println("登录成功");
  42. } else {
  43. System.out.println("登录失败");
  44. }
  45. //释放资源
  46. JDBCUtils.close(rs, pst, con);
  47. }
  48. }

2 连接池

连接池.png
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池:DBCP、C3P0。

2.1 DBCP连接池

DBCP:Apache推出的Database Connection Pool

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbcp.BasicDataSource;

/*
    使用连接池改进之前的工具类。

    之前使用工具类获取连接,每次获取连接都会创建一个新的连接,这样既耗时又浪费内存。

    现在通过连接池获取连接,这样每次获取连接都不会去创建新连接,而是从连接池中获取。

    dbcp连接池是由第三方提供的,所以如果我们要用,需要dao入jar包

    核心API:
        BasicDataSource: 这个类表示连接池。

    构造方法
        BasicDataSource():使用空参构造创建对象。

    使用步骤:
        1. 创建一个连接池对象
        2. 设置数据库的四大信息(调用连接池的setXXX方法进行设置)。
            void setDriverClassName(String driverClassName):设置驱动类名
            void setUrl(String url):设置url
            void setUsername(String username):设置用户名
            void setPassword(String password):设置密码
        3. 通过连接池对象去获取连接。
            Connection    getConnection()
 */
public class DBCPUtils {
    private static BasicDataSource dataSource;

    static {
        //在静态代码块中创建连接池,这样的话连接池只有一个。
        //创建一个连接池对象
        dataSource = new BasicDataSource();

        //设置数据库的四大信息
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
    }

    /*
     * 定义方法,用来获取数据库连接对象
     */
    public static Connection getConnection() throws SQLException {
        //通过连接池对象去获取连接。
        Connection con = dataSource.getConnection();
        //把这个连接返回
        return con;
    }
}

2.2 dbcp读取配置文件的方式

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

/*
    通过读取配置文件的方式来决定数据库的四大信息到底是什么,因为这样做会更加的灵活。
    DBCP 这个连接池里面可以自动的加载Properties集合中的键值对。不需要我们手动的调用getProperty去获取。

    核心类:
        BasicDataSourceFactory: 连接池工厂, 这个类生产连接池。 

    里面的方法可以创建一个连接池对象
        static DataSource createDataSource(Properties properties):创建一个连接池。
        参数是一个Properties集合,这个方法会自动把Properties集合中的键值对加载进来。
        Properties键值对一定要有数据库的四大信息。

    步骤:
        1. 创建一个properties配置文件,建议把这个配置文件放在src下。
               东西放在src下,同时这个东西也会在bin目录下出现,bin目录也叫作【类路径】
        2. 编写这个配置文件。 key为连接池的属性名, value是其对应的属性值。
            建议参考连接池的setXXX方法。
            比如setUsername,是在username成员变量赋值,比如setPassword是在给password成员变量赋值。

        3. 创建Properties集合,并且把配置文件中的数据读取到这个Properties集合中。
        4. 通过连接池工厂    BasicDataSourceFactory 调用 createDataSource方法传递Properties集合对象, 这个方法会得到一个连接池对象。
        5. 通过连接池对象获取连接

 */
public class DBCPUtils {
    private static DataSource dataSouce;
    static {
        try {
            //创建Properties集合,并且把配置文件中的数据读取到这个Properties集合中
            Properties p = new Properties();
            InputStream in = new FileInputStream("src\\db.properties");
            // getResourceAsStream会从类路径下面获取东西。 此时类路径是bin目录,所以会从bin目录下面获取东西
            //InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("db.properties");

            p.load(in);

            in.close();

            //通过连接池工厂获取连接池
            dataSouce = BasicDataSourceFactory.createDataSource(p);//会将参数Properties集合中的键值对加载到连接池中。
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /*
     * 用来从连接池中获取数据库连接
     */
    public static Connection getConnection() throws Exception {
        //通过连接池调用getConnection获取连接
        Connection con = dataSouce.getConnection();

        return con;
    }
}

db.properties文件放在src下(为的是以后出现在bin目录下)

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
username=root
password=root

2.3 C3P0连接池(重点)

C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件 c3p0-config.xml

package cn.itcast.c3p0utils;

import java.beans.PropertyVetoException;
import java.sql.Connection;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/*
       提供一个方法,用来获取连接,这个连接要通过c3p0连接池获取到
   c3p0连接池也是第三方提供的,所以也需要导入jar包。

   c3p0连接池中的核心类
   ComboPooledDataSource:这个类表示一个连接池。


    使用步骤
        1. 创建一个连接池对象。 
        2. 设置数据库的四大信息
            void setDriverClass(String driverClass):设置驱动类名
            void setJdbcUrl(String jdbcUrl): 设置url 
            void setUser(String user):设置用户名
            void setPassword(String password):设置密码
        3. 通过连接池获取连接。
            Connection getConnection():通过连接池获取一个数据库连接对象

    改进:通过读取配置文件的方式获取到数据库的四大信息

    步骤:
        1. 创建一个c3p0-config.xml【名字不能变】
        2. 把这个xml文件放入到src下【位置不能变】
        3. 编写xml配置文件的内容。(参考c3p0连接池的帮助文档)
 */
public class C3P0Utils {
    //创建一个连接池对象。 
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    /*
    static {
        try {
            //设置数据库的四大信息
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
            dataSource.setUser("root");
            dataSource.setPassword("root");
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
    }
    */

    /*
     * 定义方法,用来获取这个连接池对象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /*
     *  提供一个方法,用来获取连接,这个连接要通过c3p0连接池获取到
     */
    public static Connection getConnection() throws Exception {
        //通过连接池获取连接
        Connection con = dataSource.getConnection();
        return con;
    }

}

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
  <default-config>
    <!-- 
        property 元素的name属性,表示c3p0连接池的属性名
        property 元素的值,  表示对应的属性值

        name我们需要去c3p0连接池中查询对应的成员变量名。 建议通过setXXX方法去查。比如setPassword,就表示内部有一个属性叫做password,这个方法在给password赋值
     -->
     <!-- 配置驱动类名 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <!-- 配置的url -->
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>
    <!-- 配置用户名 -->
    <property name="user">root</property>
    <!-- 密码 -->
    <property name="password">root</property>
  </default-config>
</c3p0-config>

3 DBUtils

简化JDBC代码开发,本案例我们将采用apache commons组件一个成员:DBUtils。
DBUtils就是JDBC的简化开发工具包。需要使用技术:连接池(获得连接),SQL语句都没有少。

/*
    商品类

    javabean:
        1. 私有属性,提供get/set
        2. 提供空参数构造。
        3. 实现Serializable 接口, 一般省略
 */
public class Product {
    private int pid;
    private String pname;
    private int price;
    private String flag;


    @Override
    public String toString() {
        return "Product [pid=" + pid + ", pname=" + pname + ", price=" + price + ", flag=" + flag + "]";
    }
    public int getPid() {
        return pid;
    }
    public void setPid(int pid) {
        this.pid = pid;
    }
    public String getPname() {
        return pname;
    }
    public void setPname(String pname) {
        this.pname = pname;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    public String getFlag() {
        return flag;
    }
    public void setFlag(String flag) {
        this.flag = flag;
    }

}

3.1 增删改

package cn.itcast.dbutils;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;

import cn.itcast.c3p0utils.C3P0Utils;

/*
    DBUtils是由第三方提供的一个工具包,可以简化我们对jdbc的操作。

    使用前必须要到入jar

    DBUtils核心API
        DbUtils:提供了一些方法,用来处理释放资源相关的操作。
        QueryRunner:核心类, 用来执行sql语句。
        ResultSetHandler: 接口。 用来决定查询后的结果到底是什么类型的。 也叫作结果集处理方式。


    DBUtils执行增删改的操作:

    QueryRunner构造方法:
        QueryRunner(): 使用空参数构造创建对象。
        QueryRunner(DataSource ds): 需要传递一个连接池类型的参数。

    QueryRunner用来执行增删改操作的方法:
        update(Connection conn, String sql, Object... params):用来执行增删改操作。如果要使用这个方法,那么创建对象的时候一定要使用空参数构造方法创建
        参数conn: 表示数据库连接。
        参数sql: 表示sql语句。这个sql中可以使用?占位符
        参数params: 是一个可变参数。 表示用来替换?占位符的实际参数。

        update(String sql, Object... params): 用来执行增删改操作. 不需要传递数据库连接, 但是要求创建QueryRunner对象时,要传递一个连接池。
        参数sql: 表示sql语句。这个sql中可以使用?占位符
        参数params: 是一个可变参数。 表示用来替换?占位符的实际参数。
 */
public class Demo01DBUtils {
    public static void main(String[] args) throws Exception {
        //updateMethod();
        deleteMethod();
    }

    /*
     * 使用DBUtils执行删除操作
     */
    public static void deleteMethod() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法,执行sql
        String sql = "DELETE FROM product WHERE pid = ?";
        int row = qr.update(C3P0Utils.getConnection(), sql, 13);
        System.out.println(row);
    }


    /*
     * 使用DBUtils执行修改数据的操作
     */
    public static void updateMethod() throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

        //调用update方法,执行sql语句
        String sql = "UPDATE product SET pname=?, price=?, flag=? WHERE pid=?";
        Object[] params = {"臭豆腐", 10, "1", 13};
        int row = qr.update(sql, params);
        System.out.println("row:" + row);
    }

    /*
     * 用来执行添加操作
     */
    public static void addMethod() throws Exception {
        //创建一个QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //调用update方法执行sql
        //update(Connection conn, String sql, Object... params)
        Connection con = C3P0Utils.getConnection();
        String sql = "INSERT INTO product (pname, price, flag) VALUES (?,?,?)";
        Object[] params = {"土豆牛肉盖饭", 15, "1"};//表示用来替换问号占位符的实际参数

        int row = qr.update(con, sql, params);//返回值为受影响的行数
        System.out.println("row:" + row);

        //c3p0会自动回收空闲连接
        //DbUtils.close(con);//真正并不是真的把连接关了,而是又放回了连接池
    }
}

3.2 查

ResultSetHandler结果集处理类

ArrayHandler 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
BeanHandler 将结果集中第一条记录封装到一个指定的javaBean中。
BeanListHandler 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
ColumnListHandler 将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler 将结果集中每一条记录封装到Map,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。
MapHandler 将结果集中第一条记录封装到了Map集合中,key就是字段名称,value就是字段值
MapListHandler 将结果集中每一条记录封装到了Map集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。
ScalarHandler 它是用于单个数据。例如select count(*) from 表操作。


红色为重点掌握,其他了解。

import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import cn.itcast.bean.Product;
import cn.itcast.c3p0utils.C3P0Utils;

/*
    使用DBUtils执行查询操作。
    如果执行查询就不能再调用update方法,需要调用query方法。

    QueryRunner构造方法:
        QueryRunner(): 使用空参数构造创建对象。
        QueryRunner(DataSource ds): 需要传递一个连接池类型的参数。

    DBUtils用来执行查询的方法
    query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)。执行查询,需要传递连接
    参数conn: 表示数据库连接
    参数sql: sql语句
    参数rsh:是一个接口类型的参数。 需要传递实现类对象,根据传递的实现类的不同得到的查询结果也是不同的。 一般叫做结果集处理方式。
    参数params:替换问号占位符的实际参数。

    query(String sql, ResultSetHandler<T> rsh, Object... params)。 执行查询,不需要传递连接
    参数sql: sql语句
    参数rsh:是一个接口类型的参数。 需要传递实现类对象,根据传递的实现类的不同得到的查询结果也是不同的。 一般叫做结果集处理方式。
    参数params:替换问号占位符的实际参数。

    查询的结果集处理方式,一共有9种,这9种全部都是ResultSetHandler的实现类。

    ArrayHandler结果集:会将查询后的第一行数据封装到一个Object数组中。 返回值就是Object[]
    ArrayListHandler结果集: 会将查询后的每一行数据都封装到各自的Object数组,然后再把这些Object数组放入到一个list集合。 返回值List<Object[]>
    (重要)BeanHandler结果集:会将查询的第一行数据封装到一个javabean对象中。 
    (重要)BeanListHandler结果集: 将查询到的每一行数据都封装到各自的javabean对象中, 把这些javabean对象放入到一个List集合。返回值List<javabean的类型>
    MapHandler结果集: 将查询的第一行数据封装到一个Map集合中。 key是列名。 value是对应该列的值 
    MapListHandler结果集: 将查询后的每一行数据都封装各自的Map集合,再把这些Map集合放入到一个List集合。 返回值: List<Map<String, Object>>
    ColumnListHandler结果集: 将查询后指定列的数据封装到一个List集合。
    (重要)ScalarHandler结果集: 对于查询后只有一个结果的情况,可以使用这个结果集。 比如 select count(*) from product;
    KeyedHandler结果集: 得到结果是一个Map集合,这个Map集合中的key是指定列的数据。
                          value也是一个Map集合. key是每列的列名,value是对应的值。
 */
public class Demo02DBUtils {
    public static void main(String[] args) throws Exception {
        arrayHandler();
    }

    public static void keyedHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";
        Map<Object, Map<String, Object>> map = qr.query(C3P0Utils.getConnection(), sql, new KeyedHandler<>("pname"));

        //遍历这个Map集合
        for(Object key : map.keySet()) {
            System.out.println(key + "------------" + map.get(key));
        }
    }

    //ScalarHandler结果集: 对于查询后只有一个结果的情况,可以使用这个结果集。 比如 select count(*) from product;
    public static void scalarHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT COUNT(*) FROM product";

        Object num = qr.query(C3P0Utils.getConnection(), sql, new ScalarHandler<>());
        System.out.println(num);

    }

    //ColumnListHandler结果集: 将查询后指定列的数据封装到一个List集合。
    public static void columnListHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";
        /*
         * ColumnListHandler有三个构造方法。
         * 空参数表示获取的是查询结果第一列的数据
         * 也可以在参数位置传递列名或者索引
         */
        List<Object> list = qr.query(C3P0Utils.getConnection(), sql, new ColumnListHandler<>("pname"));

        for(Object obj : list) {
            System.out.println(obj);
        }
    }

    //MapListHandler结果集: 将查询后的每一行数据都封装各自的Map集合,再把这些Map集合放入到一个List集合。 返回值: List<Map<String, Object>>
    public static void mapListHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";
        List<Map<String, Object>> list = qr.query(C3P0Utils.getConnection(), sql, new MapListHandler());

        //遍历List集合
        for(Map<String, Object> map : list) {
            System.out.println(map);
        }
    }

    //MapHandler结果集: 将查询的第一行数据封装到一个Map集合中。 key是列名。 value是对应该列的值 
    public static void mapHandler() throws  Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";
        Map<String, Object> map = qr.query(C3P0Utils.getConnection(), sql, new MapHandler());

        //打印
        System.out.println(map);
    }

    //BeanListHandler结果集: 将查询到的每一行数据都封装到各自的javabean对象中, 把这些javabean对象放入到一个List集合。返回值List<javabean>的类型>
    public static void beanListHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";

        List<Product> list = qr.query(C3P0Utils.getConnection(), sql, new BeanListHandler<>(Product.class));

        //遍历集合
        for(Product p : list) {
            System.out.println(p);
        }
    }

    //BeanHandler结果集:会将查询的第一行数据封装到一个javabean对象中。 
    public static void beanHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";

        //封装到什么javabean对象,在BeanHandler构造方法位置就传递哪个类的.class
        //new BeanHandler<>(类.class) 传递哪个类的class,就表示封装成什么javabean对象
        //Product 这个javabean对象中的字段一定要和数据库中的列名一致。 
        Product p = qr.query(C3P0Utils.getConnection(), sql, new BeanHandler<>(Product.class));

        System.out.println(p);
    }

    //ArrayListHandler结果集: 会将查询后的每一行数据都封装到各自的Object数组,然后再把这些Object数组放入到一个list集合。 返回值List<Object[]>
    public static void arrayListHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用query 方法执行查询
        String sql = "SELECT * FROM product";
        List<Object[]> list = qr.query(C3P0Utils.getConnection(), sql, new ArrayListHandler());

        //遍历集合
        for(Object[] objs : list) {
            System.out.println(Arrays.toString(objs));
        }
    }


    //ArrayHandler结果集:会将查询后的第一行数据封装到一个Object数组中。 
    public static void arrayHandler() throws Exception {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //调用query方法,去执行查询。
        String sql = "SELECT * FROM product";
        Object[] objs = qr.query(C3P0Utils.getConnection(), sql, new ArrayHandler());

        System.out.println(Arrays.toString(objs));
    }
}