5/08day49_jdbc&连接池

jdbc&datasource.png

回顾

1. jdbc基础
    通过java语言操作数据库
    快速入门
        1.注册驱动
        2.建立连接
        3.编写sql
        4.获取sql执行对象
        5.执行sql并返回结果
        6.处理结果
        7.释放资源
    API详解
        DriverManager:驱动管理器
            1.注册驱动
            2.建立连接
        Connection
            1.获取sql执行对象
                Statement
                PreparedStatement
            2.事务管理
        Statement
            1.执行DML类型
                int executeUpdate(String sql);
            2.执行DQL类型
                ResultSet executeQuery(String sql);
        ResultSet
            1.指针下移
                boolean next();
            2.获取数据
                T getXxx(String 列名);
    CRUD案例
    JdbcUtils工具类:简化书写,提高效率
    事务管理
        try{
            1.关闭自动提交
            
            2. 处理业务逻辑
            
            3. 提交事务            
        }catch(Exception e){
            4.回滚事务
            
        }finally{
            5.释放资源
        }
2. 用户登录

JDBC&连接池

今日目标

1. PreparedStatement

2. 数据库连接池


3. 用户登录
    三层架构+连接池技术实现用户登录

一 PreparedStatement【重点】

1.1 概述

SQL注入问题

​ 我们让用户输入的信息和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。

-- 这条sql语句原有的含义是根据用户名和密码查询
-- 现在用户输入了一些特殊字符,改变了sql原有的含义,这种行为成为sql注入
SELECT * FROM USER WHERE username ='admin'#   ' and password =''

解决sql注入问题

我们就不能让用户输入的信息和SQL语句进行字符串拼接。需要使用PreparedSatement对象解决SQL注入。

-- 在java语言中修复sql注入问题,通过占位符代替实际参数
SELECT * FROM USER WHERE username = ? AND password = ?

-- 帅哥模仿sql解决思想(大家不要去较真...)
SELECT * FROM USER WHERE username = "admin'#" AND PASSWORD = ""

PreparedSatement基础语法

// 1.获取连接

// 2.编写sql【占位符代替实际参数】
String sql = "SELECT * FROM USER WHERE username = ? AND password = ?";

// 3.获取sql预编译执行对象,先发送给数据库进行预编译
PreparedStatement pstmt = connection.prepareStatement(sql);


// 4.设置占位符实际参数
pstmt.setString(1,"admin'#");
pstmt.setString(2,"");

// 5.执行sql语句,并返回结果【注意,不需要传递sql参数】
ResultSet resultSet = pstmt.executeQuery();

// 6.处理结果

// 7.释放资源

优点

  1. 防止sql注入,提高安全性
  2. 参数与sql分离,提高代码可读性
  3. 减少编译次数,提高性能
1588903353541.png

1.2 操作

完善用户登录案例

修复day23_login的LoginServlet

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 统一编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // 1.接收请求
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        try {
            // 2.操作JDBC
            // 2.1 获取连接
            Connection connection = JdbcUitls.getConnection();

           // 修复用户案例
            // 2.2 编写sql语句
            String sql = "SELECT * FROM USER WHERE username = ? AND password = ?";
            System.out.println(sql);
            // 2.3获取sql预编译对象
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            // 2.4 设置实际参数
            preparedStatement.setString(1, username);
            preparedStatement.setObject(2, password);

            // 2.5执行sql语句
            ResultSet resultSet = preparedStatement.executeQuery();

            // 3.判断是否登录成功
            if (resultSet.next()) {// 成功
                String loginUsername = resultSet.getString("username");
                request.getSession().setAttribute("loginUsername", loginUsername);
                response.sendRedirect(request.getContextPath() + "/list.jsp");
            } else {// 失败
                request.setAttribute("error", "用户名或密码错误");
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }

            // 4.释放资源
            JdbcUitls.close(preparedStatement, connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

重写添加一条记录

// 新增
@Test
public void testInsert() throws Exception {
    // 1.获取连接
    Connection connection = JdbcUitls.getConnection();
    // 2.编写sql
    String sql = "insert into user values(null,?,?)";
    // 3.获取sql预编译执行对象
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // --------------- 插入多个用户
    // 4.设置实际参数
    preparedStatement.setString(1, "jerry");
    preparedStatement.setString(2, "999");
    // 5.执行sql,并返回结果
    int i = preparedStatement.executeUpdate();
    // 6.处理结果
    if (i > 0) {
        System.out.println("第一条记录添加成功....");
    }
    // 在插入一个用户(了解....)
    preparedStatement.setString(1, "xiaoming");
    preparedStatement.setString(2, "999");
    i = preparedStatement.executeUpdate();
    if (i > 0) {
        System.out.println("第二条记录添加成功....");
    }
    // -----------------
    // 7.释放资源
    JdbcUitls.close(preparedStatement, connection);
}

重写更新一条记录

// 修改
@Test
public void testUpdate() throws Exception {
    // 1.获取连接
    Connection connection = JdbcUitls.getConnection();
    // 2.编写sql
    String sql = "update user set username = ? where id = ?";
    // 3.获取预编译sql执行对象
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 4.设置实际参数
    preparedStatement.setString(1, "xiaoxiaoming");
    preparedStatement.setInt(2, 8);
    // 5.执行sql,并返回结果
    int i = preparedStatement.executeUpdate();
    // 6.处理结果
    if(i>0){
        System.out.println("修改成功.....");
    }
    // 7.释放资源
    JdbcUitls.close(preparedStatement, connection);
}

重写删除一条记录

// 删除
@Test
public void testDelete() throws Exception {
    // 1.获取连接
    Connection connection = JdbcUitls.getConnection();
    // 2.编写sql
    String sql = "delete from user where id = ?";
    // 3.获取sql预编译执行对象
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 4.设置实际参数
    preparedStatement.setInt(1, 8);
    // 5.执行sql并返回结果
    int i = preparedStatement.executeUpdate();
    // 6.处理结果
    if (i > 0) {
        System.out.println("删除成功....");
    }
    // 7.释放资源
    JdbcUitls.close(preparedStatement, connection);
}

重写查询一条记录【课下作业...】

如果是查询所有的话,可以省略...(实际参数这一步)


二 连接池【了解】

2.1 概述

连接池其实就是一个容器(集合),存放数据库连接的容器。

​ 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

1588906247784.png

优点

  • 节约资源,减轻服务器压力
  • 提高连接复用性,用户访问高效

常见连接池

* DBCP:Apache提供的数据库连接池技术。

* C3P0:数据库连接池技术,目前使用它的开源项目有Hibernate、Spring等。

* HikariCP:小日本开发的连接池技术,性能之王,目前使用它的开源项目有SpringBoot等。

* Druid(德鲁伊):阿里巴巴提供的数据库连接池技术,是目前最好的数据库连接池。
        600+项目中使用,支持sql日志监控

实现

Java为数据库连接池提供了公共的接口: DataSource ,各个连接池厂商去实现这套接口,提供jar包。

1. DataSource
    功能
        * 获取连接:
                Connection getConnection()

        * 归还连接:
                connection.close()
            如果连接对象是通过连接池获取的,那么执行 connection.close() 方法时,是归还到连接池,而不是销毁对象..
                底层通过动态代理技术对close()方法进行了增强
1588907036163.png

2.2 Druid连接池

2.2.1 快速入门

① 导入druid相关jar包

1588908276637.png

② 编写测试代码【硬编码有耦合】

public class DruidDemo1 {

    public static void main(String[] args) throws Exception {
        // 1.创建druid连接池对象
        DruidDataSource dataSource = new DruidDataSource();
        // 2.与mysql建立连接(初始化一些连接个数),设置数据库基本四项
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/day23");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        // 3.从连接池中获取连接
        Connection connection = dataSource.getConnection();
        // 4.处理业务
        System.out.println(connection);
        // 5.归还连接
        connection.close();// 注意:此方法执行完毕,不是销毁对象,而是归还到连接池容器中...
    }
}
1588909040957.png

2.2.2 配置文件

① 定义配置文件

1588909336567.png

② 编写代码

public class DruidDemo2 {

    public static void main(String[] args) throws Exception {

        // 通过类加载器读取src目录下配置文件,获取io流
        InputStream is = DruidDemo2.class.getClassLoader().getResourceAsStream("druid.properties");
        // 创建 Properties对象
        Properties properties = new Properties();
        properties.load(is);
        // druid连接池工厂对象,初始化连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        // 获取连接
        for (int i = 1; i <= 11; i++) {

            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            if(i== 10){
                connection.close();
            }
        }
        // 归还连接
        // connection.close();
    }
}

2.3 连接池工具类【课下抄一遍...】

​ 我们每次操作数据库都需要创建连接池,获取连接,关闭资源,都是重复的代码。我们可以将创建连接池和获取连接池的代码放到一个工具类中。

保证连接池对象,只创建一次

目的:简化书写,提高效率

public class JdbcUtils{
    
   // 1.初始化连接池对象(druid),一个项目只有一个 static{}
   // 2.提供获取连接池对象静态方法
   // 3.提供连接对象的静态方法
   // 4.提供释放资源的静态方法(connection是归还)
}
public class JdbcUtils {

    private static DataSource dataSource = null;

    // 1.初始化连接池对象(druid),一个项目只有一个 static{}
    static {
        try {
            // 通过类加载器读取src目录下配置文件,获取io流
            InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            // 创建 Properties对象
            Properties properties = new Properties();
            properties.load(is);
            // druid连接池工厂对象,初始化连接池
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 2.提供获取连接池对象静态方法
    public static DataSource getDataSource() {
        return dataSource;
    }
    // 3.提供连接对象的静态方法
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    // 4.提供释放资源的静态方法(connection是归还)
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 重载关闭方法
    public static void close(Statement statement, Connection connection) {
        close(null, statement, connection);
    }
}

三 案例:用户登录

需求

使用三层架构+JDBC连接池技术(面向接口编程),实现用户登录案例

我们在黑马旅游网课程中介绍面向接口编程....

3.1 需求分析

1588918660844.png
1588919056249.png

3.2 环境搭建

① 使用昨天的数据库和user表

1588919203635.png

② 创建web工程,导入相关jar包

1588919188858.png

③ 导入页面资源

1588919240861.png

④ 创建三层包目录结构

1588919292255.png

⑤ 导入JbdcUtils工具类

1588919359919.png

⑥ 编写User实体类

一个java实体(class类) 对应一张表

一个java对象对应一条记录

1588919472326.png

3.3 代码实现

1588920067501.png

① login.jsp

1588919789942.png
1588919810915.png

② LoginServlet

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // 1.接收请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 2.调用service
        UserService userService = new UserServiceImpl();
        User user = userService.login(username, password);
        // 3.判断
        if (user == null) {
            // 登录失败,友情提示
            request.setAttribute("error", "用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        } else {
            // 登录成功
            request.getSession().setAttribute("user", user);
            response.sendRedirect(request.getContextPath() + "/list.jsp");
        }
    }

}

③ UserService 接口

public interface UserService {

    // 我来定义规范(根据用户名和密码查询user对象)
    public User login(String username, String password);
}

④ UserServiceImpl 实现类

public class UserServiceImpl implements UserService {

    @Override
    public User login(String username, String password) {
        UserDao userDao = new UserDaoImpl();
        return userDao.login(username, password);
    }
}

⑤ UserDao 接口

public interface UserDao {

    // 我来定义规范(根据用户名和密码查询user对象)
    public User login(String username, String password);
}

⑥ UserDaoImpl 实现类

public class UserDaoImpl implements UserDao {

    @Override
    public User login(String username, String password) {

        Connection connection =null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet =null;
        try {
            // 1.获取连接【从连接池】
             connection = JdbcUtils.getConnection();
            // 2.编写sql
            String sql = "select * from user where username = ? and password = ?";
            // 3.获取sql预编译执行对象
             preparedStatement = connection.prepareStatement(sql);
            // 4.设置实际参数
            preparedStatement.setString(1, username);
            preparedStatement.setString(2, password);
            // 5.执行sql并返回结果
             resultSet = preparedStatement.executeQuery();
            // 6.处理结果
            User user = null;
            if (resultSet.next()) {
                // 获取 id 用户名、密码
                int id = resultSet.getInt("id");
                username = resultSet.getString("username");
                password = resultSet.getString("password");
                user = new User();
                user.setId(id);
                user.setUsername(username);
                user.setPassword(password);
            }
            return user;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 7.释放资源
            JdbcUtils.close(resultSet, preparedStatement, connection);
        }
        return null;
    }
}

⑦ list.jsp

1588921309085.png

老师晚上总结

疑惑|拓展

  1. PrePareStatement的好处

    • 防止sql注入
    • sql的参数与sql语句分离,增强阅读性
    • 如果一个sql语句执行多次的时候会提高效率
  2. PrePareStatement执行的流程与常见的异常

    package com.itheima.test;
    
    import com.itheima.utils.JdbcUitls;
    import org.junit.Test;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class Demo1 {
    
        @Test
        public void test01() throws SQLException {
            //1. 获取链接
            Connection connection = JdbcUitls.getConnection();
            //2. 准备sql语句,得到PreparedStatement
            String sql = "insert into account(name,balance) values(?,?)";
            PreparedStatement pst = connection.prepareStatement(sql);
            //3. 设置参数,需要根据列的设置参数,这样子就太繁琐了
            pst.setObject(1,"罗志祥");
            pst.setObject(2,1000);
    
            //4. 执行sql
            pst.executeUpdate();
    
            //5. 关闭链接
            pst.close();
            connection.close();
        }
    }
    
    

    ==常见的异常:实参的个数与?个数不匹配,或者是索引用错==

1588936634512.png
1588936700363.png
  1. 为什么需要使用连接池,连接池的工作原理

    • 使用连接池的原因: jdbc的操作中获取连接是比较耗时的,如果每次用户访问的时候采取获取连接,那么会导致用户体验不佳,因为用户访问访问需要等待问数据库获取连接。

    ==今晚简单的和大家自定义一个连接池,简单版的,只是为了让大家看连接池更加透彻==

    package com.itheima.utils;
    
    import javax.sql.DataSource;
    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.SQLFeatureNotSupportedException;
    import java.util.LinkedList;
    import java.util.logging.Logger;
    
    //自定义的一个连接池
    /*
     连接池sun公司要求一定要实现DataSource接口
     */
    public class MyDataSource implements DataSource {
    
        //定义一个集合保存链接
        private LinkedList list = new LinkedList<>();
    
        //初始化的链接个数
        private int initSize = 5;  //一旦创建连接池就要有五个链接在连接池中
    
    
        private int maxSize = 10; //连接池最大的链接个数
    
        private int curConnectionCount=0; //定义一个变量记录当前已经创建了几个链接
    
    
        //一旦创建连接池,那么连接池就必须有5个链接。
        public MyDataSource() {
            for (int i = 0; i 0){
                //取出链接(本质要从集合中删除)
                Connection connection = list.pop(); //出栈,删除并返回
                return connection;
            }
    
            //情况2: 连接池中没有链接,但是没有超过最大链接个数,还可以创建新的链接
            if(curConnectionCount T unwrap(Class iface) throws SQLException {
            return null;
        }
    
        @Override
        public boolean isWrapperFor(Class iface) throws SQLException {
            return false;
        }
    
        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return null;
        }
    
        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {
    
        }
    
        @Override
        public void setLoginTimeout(int seconds) throws SQLException {
    
        }
    
        @Override
        public int getLoginTimeout() throws SQLException {
            return 0;
        }
    
        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return null;
        }
    }
    
    
    
    
    
    

    测试代码

     @Test
        public void test02() throws SQLException {
            MyDataSource dataSource = new MyDataSource();
            for (int i = 0; i <10 ; i++) {
                Connection connection = dataSource.getConnection();
                System.out.println(i+" : "+ connection);
                if(i==9){
                    //把第10个用完放回连接池中
                    dataSource.close(connection);
                }
            }
            //第11个
            System.out.println(dataSource.getConnection());
        }
    
  1. DRUID参数的说明
1588939301504.png

创建了100个连接, 深夜的时候只有3个人在使用,那么空闲有97个连接, 连接需要关闭空闲连接,剩下20个人

minIdle : 最小的空闲个数

传智播客刚刚创立只有2位老师, 随着公司发展传智老板说最多公司人数可以达到1w人, 2020年

由于疫情开班没有那么多,有些老师空闲出来了, 那么我空闲的老师最大容量是1000人。

传智播客: 10000员工

在授课的老师: 8000人

空闲的老师有:2000人 , 裁员1000人 , 最多空闲1000人

今日重点

  1. PreparedStatement实现增删查改
  2. 连接池

答疑区

疑问 答复
resultset结果集需要先封装成对象或者json再返回还是直接返回再封装? 一般我们都是先封装,再返回
预编译和即时编译有什么区别? String sql = "select name,balance from account";
rs = st.executeQuery(sql); //即时编译,是在执行sql的时候发送给mysql服务器
String sql2 = "select name,balance from account";
PreparedStatement pst = connection.prepareStatement(sql2);//执行sql语句之前就发送给mysql服务器,该语句就发送给服务器了
以后全部用P reparedstatement 再也不使用statement 是的
为什么说pstmt.excuteQuery()进行了子接口方法的重载 PreparedStatement 是 Statement的子接口, 也拥有executeQuery(String sql)方法, 但是一般不用, 因为它预编译sql,并重载了executeQuery(无参)的方法
如果,占位符 有三个 我传入两个值 ,这样能执行吗 执行报错,实参与形参个数不对应
我的pstmt.setString(1, 1);传入参数,数据库接收到参数后就不编译了吗? 我传入参数后到数据库拿到参数后不是又执行一次吗? 这个数据库编译的概念是什么?是不是编译后就和java一样有个编译文件,而后我们把参数传入到编译后的文件中? 编译就是有点相当于把sql语句编译成一个类似函数的东西,传参执行sql,就相当于调用了函数
我如果把每个问号设置两次,他会覆盖还是新的一行sql代码,如果是新的sql代码执行的时候是只执行一条还是执行两条,还是怎么的执行? 连续设置两次会覆盖原来赋予的值。
我们一般对一次crud添加事务还是对多次? 执行单条sql语句不需要添加事务。
老师,可不可以理解成每一个connect就是一个事务,如果取消自动登录的话 每个Connection都可以控制开启或者不开启事务。但是你没有开启事务就没有事务
不对吧,不是单个sql语句不也是事务吗?conn.setAutoCommit(false)是关闭自动提交事务,如果不关默认自动提交事务,那么我们一个Connection连接就是一个事务,conn.setAutoCommit(false)不是开启事务的按钮,他是控制是否自动提交的按钮。 conn.setAutoCommint(false) 只是设置不自动提交事务,指定多条sql语句,需要设置。执行单条sql'语句不需要设置,默认的是自动提交事务。
一个Connection连接里面的全部sql语句就是一个事务,conn.setAutoCommit(false)只是设置自动提交的方法 如果一个Connection对象,不设置conn.setAutoCommit(false),那么每执行一个sql语句都是自动提交,那么就会存在多个事务。 所以如果需要保证多个sql语句执行是同一个事务的话,那么我们必须设置conn.setAutoCommit(false)
设置成员变量有什么好处 由于多个方法都需要使用DataSource.
定义一个接口的好处是什么呢 用到了Srping之后感受会比较深,现在暂时先写习惯。 后期会有详细的讲解与体验的
login方法的返回值用Boolean是不是不严谨?? 主要我们还需要把登陆成功的用户存储在session中,所以返回一个User对象,当然实现一个功能是有多种方式,没有哪种对错,只要功能实现了就好
抽象类扩张性好是吗 抽象类和接口可以用于实现多态, 扩展性好
直接返回一个resultset对象集给web层行吗?在web层里封装到user对象行吗 不方便,因为dao层查询完毕之后需要关闭连接,一旦关闭连接resultSet不能再遍历
getInt("id"),id带“”不太清楚 getXXX(String str) 参数是String类型, java中字符串要加 ""
老师写的getInt("id")得到的id不太明白 getId(String name) 形参要求是列名字符串。
改为手动添加事务直接添加吗?为什么我昨天直接添加了功能就实现不了了 如果是个人练习代码出现bug,那么可以找你们班的答疑老师帮助你调试,因为需要结合你的代码才能分析出具体的原因
是把数据库以字符串定义的列名id查询出来转成了intl类型是吗 是的

你可能感兴趣的:(5/08day49_jdbc&连接池)