01.JavaWeb之JDBC详解、SQL注入问题、SQL预编译和Druid数据库连接池

JDBC 就是使用Java语言操作关系型数据库的一套API,是一套标准的接口。

全称:( Java DataBase Connectivity ) Java 数据库连接

可以用同一套Java代码操作不同的关系型数据库。SUN公司制定了一套标准接口JDBC,接口是无法直接使用的,我们需要使用接口的实现类(驱动),不同的数据库厂商分别实现了对应的JDBC接口,即驱动。

  • 创建工程,导入MySQL的驱动jar包

    mysql-connector-java-5.1.48.jar

  • 注册驱动

    Class.forName("com.mysql.jdbc.Driver");
    
  • 获取连接

    Connection conn = DriverManager.getConnection(url, username, password);
    

    Java代码需要发送SQL给MySQL服务端,就需要先建立连接

  • 定义SQL语句

    String sql =update…” ;
    
  • 获取执行SQL对象

    执行SQL语句需要SQL执行对象,而这个执行对象就是Statement对象

    Statement stmt = conn.createStatement();
    
  • 执行SQL

    stmt.executeUpdate(sql);  
    
  • 处理返回结果

  • 释放资源

JDBC使用示例

准备工作:创建一个空的项目,然后项目下新建一个文件夹lib(名称可以随意),然后将驱动jar包放到该目录下进行管理,最后右键-Add as a Library,有三个选项:

Global Library 全局有效 Project Library 项目有效 Module Library 模块有效。 选择全局有效即可。

然后在src下创建类如下

public class JDBCDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 注册驱动类
        Class.forName("com.mysql.jdbc.Driver");
        // 2.数据库连接所需要的信息
        String url = "jdbc:mysql://127.0.0.1:3306/db_test";
        String username = "root";
        String password = "xxxxxx";
        // 3.获取连接
        final Connection connection = DriverManager.getConnection(url, username, password);
        // 4.编写sql语句
        String sql = "update tb_user set password=456 where username=\"zhangsan\"";
        // 5.获取执行sql的对象Statement
        final Statement statement = connection.createStatement();
        // 6.执行sql语句并得到返回结果
        final int count = statement.executeUpdate(sql);
        System.out.println(count);
        // 7.释放资源
        statement.close();
        connection.close();
    }
}

语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…

示例:jdbc:mysql://127.0.0.1:3306/db1

  • 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对,即省略了IP地址和端口号
  • 配置 useSSL=false 参数,禁用安全连接方式SSL,解决警告提示
  • MySQL5之后的驱动包,可以省略注册驱动的步骤。
  • Connection的作用: 获取执行Sql的对象,管理事务。
JDBC手动提交事务

JDBC的事务由Connection来进行管理:

  • JDBC是自动提交事务的,需要设置Connection.setAutoCommit(false)取消自动提交事务
  • 提交事务: Connection.commit()
  • 事务回滚: Connection.rollback()
public class JDBCDemo3_Connection {

    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql:///db1?useSSL=false";
        String username = "root";
        String password = "1234";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql1 = "update account set money = 3000 where id = 1";
        String sql2 = "update account set money = 3000 where id = 2";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();

        try {
        	//按住ctrl + alt + t 可以选择自动生成try catch包裹
            // ============开启事务==========
            conn.setAutoCommit(false);
            //5. 执行sql
            int count1 = stmt.executeUpdate(sql1);//受影响的行数
            //6. 处理结果
            System.out.println(count1);
            int i = 3/0;
            //5. 执行sql
            int count2 = stmt.executeUpdate(sql2);//受影响的行数
            //6. 处理结果
            System.out.println(count2);

            // ============提交事务==========
            //程序运行到此处,说明没有出现任何问题,则需求提交事务
            conn.commit();
        } catch (Exception e) {
            // ============回滚事务==========
            //程序在出现异常时会执行到这个地方,此时就需要回滚事务
            conn.rollback();
            e.printStackTrace();
        }

        //7. 释放资源
        stmt.close();
        conn.close();
    }
}
PreparedStatement预编译

预编译SQL语句并执行:预防SQL注入问题

下面演示一种SQL注入问题:它会导致用户输入的密码检查通过。

String pwd = "' or '1' = '1";
String sql = "select * from tb_user where username = '" + name + "' and password = '"  +pwd + "'";
select * from tb_user where username = 'sjdljfld' and password = '' or '1' = '1'; //永远都是true
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
//传入值,1和2表示第几个?
pstmt.setString(1,name);
pstmt.setString(2,pwd);

PreparedStatement的原理是将特殊字符进行了转义,转义的SQL如下

select * from tb_user where username = 'sjdljfld' and password = '\'or \'1\' = \'1'

进行转义之后就不会出现这种情况

'1' = '1' ; //因为'不代表原来的含义了,而是被转义成为了普通的字符。

在连接数据库的时候使用useServerPrepStmts=true开启预编译功能:

String url = "jdbc:mysql:///db_test?useSSL=false&useServerPrepStmts=true";

预编译案例代码:

public class PrepareSQL {
    public static void main(String[] args) throws SQLException {
        // 数据库连接信息,开启预编译功能
        String url = "jdbc:mysql:///db_test?useSSL=false&useServerPrepStmts=true";
        String username = "root";
        String password = "root";
        // 获取Connection
        Connection connection = DriverManager.getConnection(url, username, password);
        // 编写sql语句,用?占位符表示参数
        String sql = "select * from tb_user where username=? and password=?";
        // 获取预编译执行
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 设置sql参数,用的是index,从1开始。表示第一个?
        preparedStatement.setString(1,"zhangsan");
        preparedStatement.setString(2,"456");
        // 执行sql,得到结果集合
        ResultSet resultSet = preparedStatement.executeQuery();
        System.out.println(resultSet);
    }
}

预编译执行和一般执行的区别在于:sql语句使用了占位符,相当于一个模板,Mysql对其进行编译之后,下次执行相同的语句只需要替换参数即可,而不用重新编译,一方面提高了性能,另一方面还能防止SQL注入问题。

sql语句执行流程:

  • 将sql语句发送到MYSQL服务端
  • MYSQL服务端:检查sql语句的语法是否正确; 编译sql语句称为可执行的函数;
  • 检查和编译花费的时间比执行sql的时间还要长,如果只是重新设置参数,那么检查和编译sql语句将不需要重复执行,提高了性能。
  • 执行sql语句
ResultSet

当查询之后,返回结果为ResultSet

boolean next()

  • 将光标从当前位置向前移动一行
  • 判断当前行是否为有效行

方法返回值说明:

  • true : 有效行,当前行有数据
  • false : 无效行,当前行没有数据

xxx getXxx(参数):获取数据

  • xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
  • 参数
    • int类型的参数:列的编号,从1开始
    • String类型的参数: 列的名称

get方法参数有两种形式:一种是列的编号,从1开始,另一种是列的名称为字符串

String sql = "select * from tb_user;";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
    int id = resultSet.getInt(1);//or getInt("id");
    String name = resultSet.getString("username");
    String pword = resultSet.getString("password");//or getString(3)
}
JDBC案例

需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中

首先在数据库中创建表

create table account(
id int primary key auto_increment,
name varchar(50) not null unique,
money double(7,2) default 0);

然后插入一些合法的数据

insert into account(name,money) values("zhangsan",100),("lisi",200);

封装一个Account对象

public class Account {
    public int id;
    public String name;
    public double money;

    public Account(int id, String name, double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }
... 省略了getter和setter方法
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

alt+inseret:可以自动插入getter setter toString 以及构造函数等。

String sql = "select * from account";
Statement statement = connection.createStatement();
ResultSet res = statement.executeQuery(sql);
//准备一个集合
ArrayList<Account> accounts = new ArrayList<>();
while(res.next()){
    int id = res.getInt("id");
    String name = res.getString("name");
    double money = res.getDouble("money");
    //构造一个Account对象
    Account account = new Account(id, name, money);
    //加入到集合中
    accounts.add(account);
}
//遍历集合访问
for(Account a:accounts){
    System.out.println(a);
}

输出结果(Account重写了toString方法):

Account{id=1, name='jack', money=1000.0}
Account{id=2, name='marry', money=99999.99}
Account{id=3, name='mike', money=0.0}
Account{id=4, name='zhangsan', money=100.0}
Account{id=5, name='lisi', money=200.0}
数据库连接池Druid
  • 数据库连接池是一个容器,负责分配,管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是需要的时候再重新建立一个
  • 好处:资源重用,提升响应速度,避免数据库连接遗漏(连接了但是一直没用,可以设置一个最大空闲时间释放该连接)

之前我们代码中使用连接是没有使用都创建一个Connection对象,使用完毕就会将其销毁。这样重复创建销毁的过程是特别耗费计算机的性能的及消耗时间的。

连接池是在一开始就创建好了一些连接(Connection)对象存储起来。用户需要连接数据库时,不需要自己创建连接,而只需要从连接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接所花费的时间,从而提升了系统响应的速度。

官方(SUN公司)提供了数据库连接池的标准接口DataSource,由第三方组织实现该接口,该接口提供获取连接的功能:Connection getConnection()

这样就不需要通过DriverManager对象获取Connection了,而是可以通过连接池DataSource获取Connection对象。

常见的数据库连接池有:DPCP,C3P0,Druid.

  • Druid(德鲁伊)
    • Druid连接池是阿里巴巴开源的数据库连接池项目
    • 功能强大,性能优秀,是Java语言最好的数据库连接池之一

使用方法:

导入jar包: druid-1.1.12.jar 并且Add as Library,这样就可以省略注册的过程: Class.forName(...)

首先写一个配置文件:druid.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db_test?useSSL=false&useServerPrepStmts=true
username=root
password=root
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000

使用过程

public class DruidDemo {
    public static void main(String[] args) throws Exception {
        // 1.加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("Jdbc-demo/druid.properties"));
        // 通过工厂静态方法获取DataSource
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
        // 获取Connection
        Connection connection = dataSource.getConnection();
        // 配置文件中已经开启了预编译功能
        PreparedStatement preparedStatement = connection.prepareStatement("select * from tb_user where username=? and password=?");
        // 设置sql语句的参数
        preparedStatement.setString(1,"jack");
        preparedStatement.setString(2,"1234");
        ResultSet resultSet = preparedStatement.executeQuery();
        System.out.println(resultSet);
    }
}
总结
  • JDBC和DataSource都是官方定义的接口,我们使用的时候需要第三方实现的接口类(即驱动)
  • JDBC的执行流程是:获取Connection,获取Statement,执行SQL语句
  • 如果开启了预编译,需要在数据库连接的url中添加参数useServerPrepStmts=true,并且使用预编译对象来执行sql语句
  • 预编译相当于sql语句是模板,执行之前给定参数,能够提高性能+防止SQL注入问题
  • 后面可以用maven来管理项目,就不用手动添加这些第三方包了。
JDBC练习

完成商品品牌数据的增删改查操作

  • 查询:查询所有数据
  • 添加:添加品牌
  • 修改:根据id修改
  • 删除:根据id删除

数据准备

-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand (
    -- id 主键
    id int primary key auto_increment,
    -- 品牌名称
    brand_name varchar(20),
    -- 企业名称
    company_name varchar(20),
    -- 排序字段
    ordered int,
    -- 描述信息
    description varchar(100),
    -- 状态:0:禁用  1:启用
    status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
       ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
       ('小米', '小米科技有限公司', 50, 'are you ok', 1);

Brand类

public class Brand {
    private Integer id;
    private String brandName;
    private String companyName;
    private Integer ordered;//排序字段
    private Integer status;//状态 1表示开启 0表示禁用
//alt+insert 自动生成
    public Brand(Integer id, String brandName, String companyName, Integer ordered, Integer status) {
        this.id = id;
        this.brandName = brandName;
        this.companyName = companyName;
        this.ordered = ordered;
        this.status = status;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", status=" + status +
                '}';
    }
}

查询所有

public class DruidDemo {
    public static void main(String[] args) throws Exception {
        final Properties properties = new Properties();//配置文件
        properties.load(new FileInputStream("jdbc/src/druid.properties"));//加载配置文件
        final DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);//获取连接池
        final Connection connection = dataSource.getConnection();//获取连接
        String sql = "select * from tb_brand";
        final PreparedStatement statement = connection.prepareStatement(sql);//获取statement用来执行sql
        final ResultSet res = statement.executeQuery();//执行sql,返回结果集
        final ArrayList<Brand> brands = new ArrayList<>();
        while(res.next()){
            final int id = res.getInt("id");
            final String brandName = res.getString("brand_name");
            final String companyName = res.getString("company_name");
            final int status = res.getInt("status");
            final int ordered = res.getInt("ordered");
            final Brand brand = new Brand(id, brandName, companyName, ordered, status);
            brands.add(brand);
        }
        System.out.println(brands);
        //释放资源
        res.close();
        statement.close();
        connection.close();
    }
}

插入数据

 public static void addBrand(Brand brand) throws Exception {
    String sql = "insert into tb_brand(id,brand_name,company_name,ordered,status) values(?,?,?,?,?)";
    final Properties properties = new Properties();
    properties.load(new FileInputStream("jdbc/src/druid.properties"));
    final DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    final Connection connection = dataSource.getConnection();
    final PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setInt(1,brand.getId());
    preparedStatement.setString(2,brand.getBrandName());
    preparedStatement.setString(3,brand.getCompanyName());
    preparedStatement.setInt(4,brand.getOrdered());
    preparedStatement.setInt(5,brand.getStatus());
    final int count = preparedStatement.executeUpdate();
    System.out.println(count);
    if(count>0){
        System.out.println("sucessfully insert.");
    }
    preparedStatement.close();
    connection.close();

}
reparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setInt(1,brand.getId());
    preparedStatement.setString(2,brand.getBrandName());
    preparedStatement.setString(3,brand.getCompanyName());
    preparedStatement.setInt(4,brand.getOrdered());
    preparedStatement.setInt(5,brand.getStatus());
    final int count = preparedStatement.executeUpdate();
    System.out.println(count);
    if(count>0){
        System.out.println("sucessfully insert.");
    }
    preparedStatement.close();
    connection.close();

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值