(javaweb基础

javaweb学习回顾

JDBC

1,JDBC概述

在开发中我们使用的是java语言,那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。

1.1 JDBC概念

JDBC 就是使用Java语言操作关系型数据库的一套API

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

1.2 JDBC本质

  • 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
  • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

1.3 JDBC好处

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

以后编写操作数据库的代码只需要面向JDBC(接口),操作哪儿个关系型数据库就需要导入该数据库的驱动包,如需要操作MySQL数据库,就需要再项目中导入MySQL数据库的驱动包。如下图就是MySQL驱动包

2.1 编写代码步骤

  • 创建工程,导入驱动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);  
    
  • 处理返回结果

  • 释放资源

  • 在添加为库文件的时候,有如下三个选项

    • Global Library : 全局有效
    • Project Library : 项目有效
    • Module Library : 模块有效
  • 编写代码如下

/**
 * JDBC快速入门
 */
public class JDBCDemo {

    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "1234";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql = "update account set money = 2000 where id = 1";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        //5. 执行sql
        int count = stmt.executeUpdate(sql);//受影响的行数
        //6. 处理结果
        System.out.println(count);
        //7. 释放资源
        stmt.close();
        conn.close();
    }
}

3,JDBC API详解

3.1 DriverManager

DriverManager(驱动管理类)作用:

  • 注册驱动

    在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。而 Class.forName("com.mysql.jdbc.Driver"); 就可以加载 Driver 类。

    提示:

    • MySQL 5之后的驱动包,可以省略注册驱动的步骤
    • 自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
  • 获取数据库连接

    参数说明:

    • url : 连接路径

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

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

      细节:

      • 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对

      • 配置 useSSL=false 参数,禁用安全连接方式,解决警告提示

    • user :用户名

    • poassword :密码

3.2 Connection

Connection(数据库连接对象)作用:

  • 获取执行 SQL 的对象
  • 管理事务
3.2.1 获取执行对象
  • 普通执行SQL对象

    Statement createStatement()
    

    入门案例中就是通过该方法获取的执行对象。

  • 预编译SQL的执行SQL对象:防止SQL注入

    PreparedStatement  prepareStatement(sql)
    

    通过这种方式获取的 PreparedStatement SQL语句执行对象是我们一会重点要进行讲解的,它可以防止SQL注入。

  • 执行存储过程的对象

    CallableStatement prepareCall(sql)
    

    通过这种方式获取的 CallableStatement 执行对象是用来执行存储过程的,而存储过程在MySQL中不常用,所以这个我们将不进行讲解。

3.2.2 事务管理

先回顾一下MySQL事务管理的操作:

  • 开启事务 : BEGIN; 或者 START TRANSACTION;
  • 提交事务 : COMMIT;
  • 回滚事务 : ROLLBACK;

MySQL默认是自动提交事务

接下来学习JDBC事务管理的方法。

Connection几口中定义了3个对应的方法:

  • 开启事务

    参与autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false。

  • 提交事务

  • 回滚事务

具体代码实现如下:

/**
 * JDBC API 详解:Connection
 */
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 {
            // ============开启事务==========
            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();
    }
}

3.3 Statement

3.3.1 概述

Statement对象的作用就是用来执行SQL语句。而针对不同类型的SQL语句使用的方法也不一样。

  • 执行DDL、DML语句

  • 执行DQL语句

    该方法涉及到了 ResultSet 对象,而这个对象我们还没有学习,一会再重点讲解。

3.3.2 代码实现
  • 执行DML语句

    /**
      * 执行DML语句
      * @throws Exception
      */
    @Test
    public void testDML() 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 sql = "update account set money = 3000 where id = 1";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        //5. 执行sql
        int count = stmt.executeUpdate(sql);//执行完DML语句,受影响的行数
        //6. 处理结果
        //System.out.println(count);
        if(count > 0){
            System.out.println("修改成功~");
        }else{
            System.out.println("修改失败~");
        }
        //7. 释放资源
        stmt.close();
        conn.close();
    }
    
  • 执行DDL语句

    /**
      * 执行DDL语句
      * @throws Exception
      */
    @Test
    public void testDDL() 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 sql = "drop database db2";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        //5. 执行sql
        int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0
        //6. 处理结果
        System.out.println(count);
    
        //7. 释放资源
        stmt.close();
        conn.close();
    }
    

    注意:

    • 以后开发很少使用java代码操作DDL语句

3.4 ResultSet

3.4.1 概述

ResultSet(结果集对象)作用:

  • 封装了SQL查询语句的结果。

而执行了DQL语句后就会返回该对象,对应执行DQL语句的方法如下:

ResultSet  executeQuery(sql):执行DQL 语句,返回 ResultSet 对象

那么我们就需要从 ResultSet 对象中获取我们想要的数据。ResultSet 对象提供了操作查询结果数据的方法,如下:

boolean next()

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

方法返回值说明:

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

xxx getXxx(参数):获取数据

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

如下图为执行SQL语句后的结果

一开始光标指定于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next() 方法后,光标就下移到第一行数据,并且方法返回true,此时就可以通过 getInt("id") 获取当前行id字段的值,也可以通过 getString("name") 获取当前行name字段的值。如果想获取下一行的数据,继续调用 next() 方法,以此类推。

3.4.2 代码实现
/**
  * 执行DQL
  * @throws Exception
  */
@Test
public void testResultSet() 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 sql = "select * from account";
    //4. 获取statement对象
    Statement stmt = conn.createStatement();
    //5. 执行sql
    ResultSet rs = stmt.executeQuery(sql);
    //6. 处理结果, 遍历rs中的所有数据
    /* // 6.1 光标向下移动一行,并且判断当前行是否有数据
        while (rs.next()){
            //6.2 获取数据  getXxx()
            int id = rs.getInt(1);
            String name = rs.getString(2);
            double money = rs.getDouble(3);

            System.out.println(id);
            System.out.println(name);
            System.out.println(money);

            System.out.println("--------------");

        }*/
    // 6.1 光标向下移动一行,并且判断当前行是否有数据
    while (rs.next()){
        //6.2 获取数据  getXxx()
        int id = rs.getInt("id");
        String name = rs.getString("name");
        double money = rs.getDouble("money");

        System.out.println(id);
        System.out.println(name);
        System.out.println(money);

        System.out.println("--------------");
    }

    //7. 释放资源
    rs.close();
    stmt.close();
    conn.close();
}

3.5 案例

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

    image-20210725182352433
  • 代码实现

    /**
      * 查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
      * 1. 定义实体类Account
      * 2. 查询数据,封装到Account对象中
      * 3. 将Account对象存入ArrayList集合中
      */
    @Test
    public void testResultSet2() 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 sql = "select * from account";
    
        //4. 获取statement对象
        Statement stmt = conn.createStatement();
    
        //5. 执行sql
        ResultSet rs = stmt.executeQuery(sql);
    
        // 创建集合
        List<Account> list = new ArrayList<>();
       
        // 6.1 光标向下移动一行,并且判断当前行是否有数据
        while (rs.next()){
            Account account = new Account();
    
            //6.2 获取数据  getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            double money = rs.getDouble("money");
    
            //赋值
            account.setId(id);
            account.setName(name);
            account.setMoney(money);
    
            // 存入集合
            list.add(account);
        }
    
        System.out.println(list);
    
        //7. 释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
    

3.6 PreparedStatement

PreparedStatement作用:

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

对上面的作用中SQL注入问题大家肯定不理解。那我们先对SQL注入进行说明.

3.6.1 SQL注入

SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。

文件中的用户名和密码,文件内容如下:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=1234

在MySQL中创建名为 test 的数据库

create database test;
3.6.2 代码模拟SQL注入问题
@Test
public void testLogin() throws  Exception {
    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
    String url = "jdbc:mysql:///db1?useSSL=false";
    String username = "root";
    String password = "1234";
    Connection conn = DriverManager.getConnection(url, username, password);

    // 接收用户输入 用户名和密码
    String name = "sjdljfld";
    String pwd = "' or '1' = '1";
    String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";
    // 获取stmt对象
    Statement stmt = conn.createStatement();
    // 执行sql
    ResultSet rs = stmt.executeQuery(sql);
    // 判断登录是否成功
    if(rs.next()){
        System.out.println("登录成功~");
    }else{
        System.out.println("登录失败~");
    }

    //7. 释放资源
    rs.close();
    stmt.close();
    conn.close();
}

上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如下

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

从上面语句可以看出条件 username = 'sjdljfld' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。

接下来我们来学习PreparedStatement对象.

3.6.3 PreparedStatement概述

PreparedStatement作用:

  • 预编译SQL语句并执行:预防SQL注入问题
  • 获取 PreparedStatement 对象

    // SQL语句中的参数值,使用?占位符替代
    String sql = "select * from user where username = ? and password = ?";
    // 通过Connection对象获取,并传入对应的sql语句
    PreparedStatement pstmt = conn.prepareStatement(sql);
    
  • 设置参数值

    上面的sql语句中参数使用 ? 进行占位,在之前之前肯定要设置这些 ? 的值。

    PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值

    • Xxx:数据类型 ; 如 setInt (参数1,参数2)

    • 参数:

      • 参数1: ?的位置编号,从1 开始

      • 参数2: ?的值

  • 执行SQL语句

    executeUpdate(); 执行DDL语句和DML语句

    executeQuery(); 执行DQL语句

    注意:

    • 调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。
3.6.4 使用PreparedStatement改进
 @Test
public void testPreparedStatement() throws  Exception {
    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
    String url = "jdbc:mysql:///db1?useSSL=false";
    String username = "root";
    String password = "1234";
    Connection conn = DriverManager.getConnection(url, username, password);

    // 接收用户输入 用户名和密码
    String name = "zhangsan";
    String pwd = "' or '1' = '1";

    // 定义sql
    String sql = "select * from tb_user where username = ? and password = ?";
    // 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);
    // 设置?的值
    pstmt.setString(1,name);
    pstmt.setString(2,pwd);
    // 执行sql
    ResultSet rs = pstmt.executeQuery();
    // 判断登录是否成功
    if(rs.next()){
        System.out.println("登录成功~");
    }else{
        System.out.println("登录失败~");
    }
    //7. 释放资源
    rs.close();
    pstmt.close();
    conn.close();
}

执行上面语句就可以发现不会出现SQL注入漏洞问题了。那么PreparedStatement又是如何解决的呢?它是将特殊字符进行了转义,转义的SQL如下:

select * from tb_user where username = 'sjdljfld' and password = '\'or \'1\' = \'1'
3.6.5 PreparedStatement原理

PreparedStatement 好处:

  • 预编译SQL,性能更高
  • 防止SQL注入:将敏感字符进行转义

Java代码操作数据库流程如图所示:

  • 将sql语句发送到MySQL服务器端

  • MySQL服务端会对sql语句进行如下操作

    • 检查SQL语句

      检查SQL语句的语法是否正确。

    • 编译SQL语句。将SQL语句编译成可执行的函数。

      检查SQL和编译SQL花费的时间比执行SQL的时间还要长。如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。

    • 执行SQL语句

接下来我们通过查询日志来看一下原理。

  • 开启预编译功能

    在代码中编写url时需要加上以下参数。而我们之前根本就没有开启预编译功能,只是解决了SQL注入漏洞。

    useServerPrepStmts=true
    
  • 配置MySQL执行日志(重启mysql服务后生效)

    在mysql配置文件(my.ini)中添加如下配置

    log-output=FILE
    general-log=1
    general_log_file="D:\mysql.log"
    slow-query-log=1
    slow_query_log_file="D:\mysql_slow.log"
    long_query_time=2
    
  • java测试代码如下:

     /**
       * PreparedStatement原理
       * @throws Exception
       */
    @Test
    public void testPreparedStatement2() throws  Exception {
    
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        // useServerPrepStmts=true 参数开启预编译功能
        String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";
        String username = "root";
        String password = "1234";
        Connection conn = DriverManager.getConnection(url, username, password);
    
        // 接收用户输入 用户名和密码
        String name = "zhangsan";
        String pwd = "' or '1' = '1";
    
        // 定义sql
        String sql = "select * from tb_user where username = ? and password = ?";
    
        // 获取pstmt对象
        PreparedStatement pstmt = conn.prepareStatement(sql);
    
        Thread.sleep(10000);
        // 设置?的值
        pstmt.setString(1,name);
        pstmt.setString(2,pwd);
        ResultSet rs = null;
        // 执行sql
        rs = pstmt.executeQuery();
    
        // 设置?的值
        pstmt.setString(1,"aaa");
        pstmt.setString(2,"bbb");
        // 执行sql
        rs = pstmt.executeQuery();
    
        // 判断登录是否成功
        if(rs.next()){
            System.out.println("登录成功~");
        }else{
            System.out.println("登录失败~");
        }
    
        //7. 释放资源
        rs.close();
        pstmt.close();
        conn.close();
    }
    
  • 执行SQL语句

4,数据库连接池

4.2 数据库连接池实现

  • 标准接口:DataSource

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

    Connection getConnection()
    

    那么以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象。

  • 常见的数据库连接池

    • DBCP
    • C3P0
    • Druid

    我们现在使用更多的是Druid,它的性能比其他两个会好一些。

  • Druid(德鲁伊)

    • Druid连接池是阿里巴巴开源的数据库连接池项目

    • 功能强大,性能优秀,是Java语言最好的数据库连接池之一

4.3 Driud使用

  • 导入jar包 druid-1.1.12.jar
  • 定义配置文件
  • 加载配置文件
  • 获取数据库连接池对象
  • 获取连接

现在通过代码实现,首先需要先将druid的jar包放到项目下的lib下并添加为库文件

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

使用druid的代码如下:

/**
 * Druid数据库连接池演示
 */
public class DruidDemo {

    public static void main(String[] args) throws Exception {
        //1.导入jar包
        //2.定义配置文件
        //3. 加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
        //4. 获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

        //5. 获取数据库连接 Connection
        Connection connection = dataSource.getConnection();
        System.out.println(connection); //获取到了连接后就可以继续做其他操作了

        //System.out.println(System.getProperty("user.dir"));
    }
}

5,JDBC练习

5.1 需求

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

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

5.2 案例实现

5.2.1 环境准备
  • 数据库表 tb_brand

    -- 删除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);
    
  • 在pojo包下实体类 Brand

    /**
     * 品牌
     * alt + 鼠标左键:整列编辑
     * 在实体类中,基本数据类型建议使用其对应的包装类型
     */
    public class Brand {
        // id 主键
        private Integer id;
        // 品牌名称
        private String brandName;
        // 企业名称
        private String companyName;
        // 排序字段
        private Integer ordered;
        // 描述信息
        private String description;
        // 状态:0:禁用  1:启用
        private Integer 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 String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        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 +
                    ", description='" + description + '\'' +
                    ", status=" + status +
                    '}';
        }
    }
    
5.2.2 查询所有
 /**
   * 查询所有
   * 1. SQL:select * from tb_brand;
   * 2. 参数:不需要
   * 3. 结果:List<Brand>
   */

@Test
public void testSelectAll() throws Exception {
    //1. 获取Connection
    //3. 加载配置文件
    Properties prop = new Properties();
    prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
    //4. 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

    //5. 获取数据库连接 Connection
    Connection conn = dataSource.getConnection();
    //2. 定义SQL
    String sql = "select * from tb_brand;";
    //3. 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);
    //4. 设置参数
    //5. 执行SQL
    ResultSet rs = pstmt.executeQuery();
    //6. 处理结果 List<Brand> 封装Brand对象,装载List集合
    Brand brand = null;
    List<Brand> brands = new ArrayList<>();
    while (rs.next()){
        //获取数据
        int id = rs.getInt("id");
        String brandName = rs.getString("brand_name");
        String companyName = rs.getString("company_name");
        int ordered = rs.getInt("ordered");
        String description = rs.getString("description");
        int status = rs.getInt("status");
        //封装Brand对象
        brand = new Brand();
        brand.setId(id);
        brand.setBrandName(brandName);
        brand.setCompanyName(companyName);
        brand.setOrdered(ordered);
        brand.setDescription(description);
        brand.setStatus(status);

        //装载集合
        brands.add(brand);
    }
    System.out.println(brands);
    //7. 释放资源
    rs.close();
    pstmt.close();
    conn.close();
}
5.2.3 添加数据
/**
  * 添加
  * 1. SQL:insert into tb_brand(brand_name, company_name, ordered, description, status) values(?,?,?,?,?);
  * 2. 参数:需要,除了id之外的所有参数信息
  * 3. 结果:boolean
  */
@Test
public void testAdd() throws Exception {
    // 接收页面提交的参数
    String brandName = "香飘飘";
    String companyName = "香飘飘";
    int ordered = 1;
    String description = "绕地球一圈";
    int status = 1;

    //1. 获取Connection
    //3. 加载配置文件
    Properties prop = new Properties();
    prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
    //4. 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
    //5. 获取数据库连接 Connection
    Connection conn = dataSource.getConnection();
    //2. 定义SQL
    String sql = "insert into tb_brand(brand_name, company_name, ordered, description, status) values(?,?,?,?,?);";
    //3. 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);
    //4. 设置参数
    pstmt.setString(1,brandName);
    pstmt.setString(2,companyName);
    pstmt.setInt(3,ordered);
    pstmt.setString(4,description);
    pstmt.setInt(5,status);

    //5. 执行SQL
    int count = pstmt.executeUpdate(); // 影响的行数
    //6. 处理结果
    System.out.println(count > 0);

    //7. 释放资源
    pstmt.close();
    conn.close();
}
5.2.4 修改数据
/**
  * 修改
  * 1. SQL:

     update tb_brand
         set brand_name  = ?,
         company_name= ?,
         ordered     = ?,
         description = ?,
         status      = ?
     where id = ?

   * 2. 参数:需要,所有数据
   * 3. 结果:boolean
   */

@Test
public void testUpdate() throws Exception {
    // 接收页面提交的参数
    String brandName = "香飘飘";
    String companyName = "香飘飘";
    int ordered = 1000;
    String description = "绕地球三圈";
    int status = 1;
    int id = 4;

    //1. 获取Connection
    //3. 加载配置文件
    Properties prop = new Properties();
    prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
    //4. 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
    //5. 获取数据库连接 Connection
    Connection conn = dataSource.getConnection();
    //2. 定义SQL
    String sql = " update tb_brand\n" +
        "         set brand_name  = ?,\n" +
        "         company_name= ?,\n" +
        "         ordered     = ?,\n" +
        "         description = ?,\n" +
        "         status      = ?\n" +
        "     where id = ?";

    //3. 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);

    //4. 设置参数
    pstmt.setString(1,brandName);
    pstmt.setString(2,companyName);
    pstmt.setInt(3,ordered);
    pstmt.setString(4,description);
    pstmt.setInt(5,status);
    pstmt.setInt(6,id);

    //5. 执行SQL
    int count = pstmt.executeUpdate(); // 影响的行数
    //6. 处理结果
    System.out.println(count > 0);

    //7. 释放资源
    pstmt.close();
    conn.close();
}
5.2.5 删除数据
/**
  * 删除
  * 1. SQL:
            delete from tb_brand where id = ?
  * 2. 参数:需要,id
  * 3. 结果:boolean
  */
@Test
public void testDeleteById() throws Exception {
    // 接收页面提交的参数
    int id = 4;
    //1. 获取Connection
    //3. 加载配置文件
    Properties prop = new Properties();
    prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
    //4. 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
    //5. 获取数据库连接 Connection
    Connection conn = dataSource.getConnection();
    //2. 定义SQL
    String sql = " delete from tb_brand where id = ?";
    //3. 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);
    //4. 设置参数
    pstmt.setInt(1,id);
    //5. 执行SQL
    int count = pstmt.executeUpdate(); // 影响的行数
    //6. 处理结果
    System.out.println(count > 0);

    //7. 释放资源
    pstmt.close();
    conn.close();
}

Servlet

访问服务器的某个URL

  • 在浏览器的地址栏中输入对应的URL,属于GET提交
  • 使用a标签,在href中输入对应的URL,属于GET提交
  • 使用form表单,在action中输入对应的URL,通过method修改提交方式为GET或POST

页面向服务端提交数据的方式

  • 使用form表单的name属性显示提交

    <form action="http://localhost:8080/day1/hero" method="get">
        <input type="text" name="username">
        <input type="submit">
    </form>
    

    提交的数据会暴露在浏览器的地址栏中

  • 使用form表单的name属性隐式提交

    <form action="http://localhost:8080/day1/hero" method="post">
        <input type="text" name="username">
        <input type="submit">
    </form>
    

    提交的数据不会暴露在浏览器的地址栏中

  • 通过"?参数名=值"方式提交

    • 在地址栏中输入URL的时候,末尾加入这部分

      https://www.baidu.com/s?wd=hello
      
    • 在a标签的href属性中加入这部分,如果有多个参数,通过&拼接

      <a href="https://www.baidu.com/s?wd=hello&a=1&b=2">访问</a>
      

服务器端获取页面传递的数据

以上任何方式提交到服务器的数据,都可以使用以下方式获取。

String str=request.getParameter("name名或?后的参数名");

class TestServlet extends HttpServlet{
	doGet(HttpServletRequest req,HttpServletResponse resp){
		//获取表单提交的数据req.getParameter("表单中某个表单元素的name值");
        String username = req.getParameter("username");
	}
	doPost(HttpServletRequest req,HttpServletResponse resp){
		doGet();
	}
}

表单提交数据注意事项

  • 表单通过action提交设置的路径,如果要在路径中传递参数,只能使用post方式提交

    <form action="xxxxx?参数=值" method="post">
        
    </form>
    
  • 使用get方式提交,无法识别action路径中的参数,如果要传递参数,使用隐藏域

    <form action="xxxxx" method="get">
        <input type="hidden" name="参数名" value="参数值">
    </form>
    

解决请求和响应的中文乱码

//在servlet的doGet或doPost所有代码之前中加入
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");

使用Servlet实现单表的增删改查

数据库脚本文件

/*
 Navicat Premium Data Transfer

 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : gamedb

 Target Server Type    : MySQL
 Target Server Version : 80029
 File Encoding         : 65001

 Date: 03/01/2023 14:46:16
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for hero
-- ----------------------------
DROP TABLE IF EXISTS `hero`;
CREATE TABLE `hero`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '姓名',
  `position` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '定位',
  `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `price` int NOT NULL DEFAULT 4800 COMMENT '价格',
  `shelf_date` date NULL DEFAULT NULL COMMENT '上架日期',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

实体类entity

实体的属性名保持和表的字段名一致,使用驼峰命名法

package com.hqyj.entity;

public class Hero {
    private int id;
    private String name;
    private String position;
    private String sex;
    private int price;
    private String shelfDate;

	/*
		全参构造方法用于查询
	*/
    public Hero(int id, String name, String position, String sex, int price, String shelfDate) {
        this.id = id;
        this.name = name;
        this.position = position;
        this.sex = sex;
        this.price = price;
        this.shelfDate = shelfDate;
    }

    /*
    	不带id的构造方法用于添加
    */
    public Hero(String name, String position, String sex, int price, String shelfDate) {
        this.name = name;
        this.position = position;
        this.sex = sex;
        this.price = price;
        this.shelfDate = shelfDate;
    }

    //省略get/set/toString
}

数据操作类dao

package com.hqyj.dao;

import com.hqyj.entity.Hero;
import com.hqyj.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class HeroDao {
    Connection conn;
    PreparedStatement pst;
    ResultSet rs;

    /*
     * 查询所有
     * */
    public List<Hero> queryAll() {
        ArrayList<Hero> list = new ArrayList<>();
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("select * from hero");
            rs = pst.executeQuery();
            while (rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                String position = rs.getString(3);
                String sex = rs.getString(4);
                int price = rs.getInt(5);
                String shelfDate = rs.getString(6);
                Hero hero = new Hero(id, name, position, sex, price, shelfDate);
                list.add(hero);
            }
        } catch (Exception e) {
            System.out.println("查询所有异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return list;
    }

    /*
     * 添加
     * */
    public boolean addHero(Hero hero) {
        conn = DBUtil.getConn();
        String sql = "insert into hero values(null,?,?,?,?,?)";
        try {
            pst = conn.prepareStatement(sql);
            pst.setString(1, hero.getName());
            pst.setString(2, hero.getPosition());
            pst.setString(3, hero.getSex());
            pst.setInt(4, hero.getPrice());
            pst.setString(5, hero.getShelfDate());
            return pst.executeUpdate() > 0;
        } catch (SQLException e) {
            System.out.println("添加异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return false;
    }

    /*
     * 删除
     * */
    public boolean delete(int id) {
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("delete from hero where id=?");
            pst.setInt(1, id);
            return pst.executeUpdate() > 0;
        } catch (SQLException e) {
            System.out.println("删除异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return false;
    }

    /*
     * 根据id查询
     * */
    public Hero findById(int id) {
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("select * from hero where id=?");
            pst.setInt(1, id);
            rs = pst.executeQuery();
            if (rs.next()) {
                String name = rs.getString(2);
                String position = rs.getString(3);
                String sex = rs.getString(4);
                int price = rs.getInt(5);
                String shelfDate = rs.getString(6);
                Hero hero = new Hero(id, name, position, sex, price, shelfDate);
                return hero;
            }
        } catch (Exception e) {
            System.out.println("根据id查询异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return null;
    }

    /*
     * 修改
     * */
    public boolean update(Hero updateHero) {
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("update hero set name=?,position=?,sex=?,price=?,shelf_date=? where id=?");
            pst.setString(1, updateHero.getName());
            pst.setString(2, updateHero.getPosition());
            pst.setString(3, updateHero.getSex());
            pst.setInt(4, updateHero.getPrice());
            pst.setString(5, updateHero.getShelfDate());
            pst.setInt(6, updateHero.getId());
            return pst.executeUpdate() > 0;
        } catch (SQLException e) {
            System.out.println("修改异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return false;
    }
}

控制层/表现层servlet

package com.hqyj.servlet;

import com.hqyj.dao.HeroDao;
import com.hqyj.entity.Hero;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

public class HeroServlet extends HttpServlet {
    //当前Servlet中需要访问Hero表中的数据,所以加入HeroDao对象
    HeroDao heroDao = new HeroDao();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //解决请求和响应的中文乱码
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");

        //获取op,用一个Servlet,判断不同的op值来执行不同的操作
        String op = req.getParameter("op");
        switch (op) {
            case "queryAll":
                //调用查询
                List<Hero> list = heroDao.queryAll();
                //通过resp响应对象调用getWriter()方法,获取字符输出流对象writer,通过writer打印页面
                PrintWriter writer = resp.getWriter();
                writer.println("<html>");
                writer.println("<body>");
                writer.println("<table border='1'>");
                writer.println("<tr><td>编号</td><td>姓名</td><td>定位</td><td>性别</td><td>价格</td><td>上架时间</td><td colspan=2>操作</td></tr>");
                for (Hero hero : list) {
                    writer.println("<tr>");
                    writer.println("<td>" + hero.getId() + "</td>");
                    writer.println("<td>" + hero.getName() + "</td>");
                    writer.println("<td>" + hero.getPosition() + "</td>");
                    writer.println("<td>" + hero.getSex() + "</td>");
                    writer.println("<td>" + hero.getPrice() + "</td>");
                    writer.println("<td>" + hero.getShelfDate() + "</td>");
                    //修改的步骤:1.根据id查询,打印详情页   2.在详情页中修改
                    writer.println("<td><a href='http://localhost:8080/day1/hero?op=findById&id=" + hero.getId() + "'>修改</a></td>");
                    //访问某个URL时传递多个参数:  URL?参数1=值&参数2=值..
                    writer.println("<td><a href='http://localhost:8080/day1/hero?op=delete&id=" + hero.getId() + "'>删除</a></td>");
                    writer.println("</tr>");
                }
                writer.println("</table>");
                writer.println("</body>");
                writer.println("</html>");
                writer.close();
                break;
            case "delete":
                int id = Integer.parseInt(req.getParameter("id"));
                if (heroDao.delete(id)) {
                    //跳转到查询所有
                    resp.sendRedirect("http://localhost:8080/day1/hero?op=queryAll");
                }
                break;
            case "findById":
                //获取要修改的id
                int findId = Integer.parseInt(req.getParameter("id"));
                //调用查询
                Hero byId = heroDao.findById(findId);
                //打印详情页
                PrintWriter pw = resp.getWriter();
                pw.println("<html>");
                pw.println("<body>");
                //如果表单要在action中传递数据,只能使用post方式提交
                //pw.println("<form action='http://localhost:8080/day1/hero?op=update' method='post'> ");
                //如果表单使用get方式提交,通过隐藏域提交op
                pw.println("<form action='http://localhost:8080/day1/hero'> ");
                pw.println("<input type='hidden' name='op' value='update' >");
                //使用隐藏域提交id
                pw.println("<input type='hidden' name='id' value='" + byId.getId() + "' >");
                pw.println("姓名:<input type='text' name='name' value='" + byId.getName() + "'><br>");
                pw.println("定位:<input type='text' name='position' value='" + byId.getPosition() + "'><br>");
                 /*if("男".equals(byId.getSex())){
                   pw.println("<input type='radio' checked>男");
                   pw.println("<input type='radio' >女");
               }else{
                   pw.println("<input type='radio' >男");
                   pw.println("<input type='radio' checked>女");
               }*/
                pw.println("性别:<input type='radio' name='sex' value='男' " + ("男".equals(byId.getSex()) ? "checked" : "") + ">男");
                pw.println("<input type='radio' name='sex' value='女' " + ("女".equals(byId.getSex()) ? "checked" : "") + ">女<br>");
                pw.println("价格:<input type='num' name='price' value='" + byId.getPrice() + "'><br>");
                pw.println("上架时间:<input type='date' name='shelfDate' value='" + byId.getShelfDate() + "'><br>");
                pw.println("<input type='submit' value='修改'><br>");
                pw.println("</form>");
                pw.println("</body>");
                pw.println("</html>");
                break;
            case "update":
                //获取参数
                int updateId = Integer.parseInt(req.getParameter("id"));
                String updateName = req.getParameter("name");
                String updateSex = req.getParameter("sex");
                String updatePosition = req.getParameter("position");
                int updatePrice = Integer.parseInt(req.getParameter("price"));
                String updateShelfDate = req.getParameter("shelfDate");
                //创建待修改的对象,调用修改,跳转到查询所有页面
                Hero updateHero = new Hero(updateId, updateName, updatePosition, updateSex, updatePrice, updateShelfDate);
                heroDao.update(updateHero);
                //跳转到查询所有
                resp.sendRedirect("http://localhost:8080/day1/hero?op=queryAll");
                break;
            case "addHero":
                //获取页面提交的数据
                //request.getParameter("name名")
                String name=req.getParameter("name");
                String position=req.getParameter("position");
                String sex=req.getParameter("sex");
                int price=Integer.parseInt(req.getParameter("price"));
                String shelfDate=req.getParameter("shelfDate");
                //创建添加对象,调用添加方法
                Hero hero = new Hero(name, position, sex, price, shelfDate);
                if (heroDao.addHero(hero)) {
                    resp.sendRedirect("http://localhost:8080/day1/hero?op=queryAll");
                }
                break;
        }
    }
}

配置Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>hero</servlet-name>
        <servlet-class>com.hqyj.servlet.HeroServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>hero</servlet-name>
        <url-pattern>/hero</url-pattern>
    </servlet-mapping>

</web-app>

添加页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="http://localhost:8080/day1/hero">
    <!--通过隐藏域提交op-->
    <input type="hidden" name="op" value="addHero">
    角色名:<input type="text" name="name" required><br>
    定位:<input type="text" name="position" required><br>
    性别:<input type="radio" name="sex" value="" checked><input type="radio" name="sex" value=""><br>
    价格:<input type="number" min="1" name="price" required><br>
    上架日期:<input type="date" name="shelfDate" required><br>
    <input type="submit" value="添加">
</form>

</body>
</html>

主页

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <a href="http://localhost:8080/day1/hero?op=queryAll">查看所有hero</a>
        <a href="http://localhost:8080/day1/pages/addHero.html">添加</a>
    </body>
</html>

web.xml文件中的常用标签

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--设置项目启动的欢迎页-->
    <welcome-file-list>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>

    <!--设置错误页面-->
    <error-page>
        <!--错误码-->
        <error-code>404</error-code>
        <!--页面路径-->
        <location>/404.html</location>
    </error-page>
    <error-page>
        <!--异常类型-->
        <exception-type>java.lang.NullPointerException</exception-type>
        <location>/error.html</location>
    </error-page>

    <!--上下文参数-->
    <context-param>
        <!--参数名-->
        <param-name>contentParam</param-name>
        <!--参数值-->
        <param-value>全局参数</param-value>
    </context-param>
    
    <!--servlet标签-->
    <!--servlet-mapping标签-->
    
    
    <!--filter标签-->
    <!--filter-mapping标签-->
    
    <!--session-config标签-->

</web-app>

Servlet的生命周期

构造方法**–>init()–>service()/doGet()/doPost()–>**destory()

在访问某servlet时

1.执行构造方法一次

2.初始化一次,调用init()方法

3.调用service()方法,之后每次访问都会调用该方法。有该方法时,doGet和doPost失效。

如果没有该方法,会根据请求方式试图调用doGet或doPost,如果没有相应的方法,会出现405状态码,表示请求方式不允许

4.在当前servlet所在项目从tomcat中停止时,销毁一次,调用destory()方法

使用注解开发Servlet

/*
 * 使用注解开发Servlet
 * 注解:@特定单词  如@Override
 *
 * 定义且配置Servlet的注解:@WebServlet("/请求映射")
 * */
@WebServlet("/sysAdmin")
public class SysAdminServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)  {
        //访问该Servlet时要执行的内容
    }
}
//@WebServlet("/sysAdmin")相当于在web.xml中进行配置servlet映射

绝对路径

<a href="localhost:8080/system/pages/hello.html">跳转</a>

相对路径问题

  • /

    表示从根目录(域名+ip)出发

  • ./

    表示从当前位置出发

  • …/

    表示跳向上一层

如有index页面所在路径为
localhost:8080/system/pages/index.html

<a href="/hello.html">跳转</a>
这种方式,从根目录(localhost:8080)出发,会跳转到localhost:8080/hello.html

<a href="./hello.html">跳转</a>
这种方式,从当前位置(localhost:8080/system/pages)出发,会跳转到localhost:8080/system/pages/hello.html

<a href="../hello.html">跳转</a>
这种方式,从当前位置跳向上一层,会跳转到localhost:8080/system/hello.html
  • 在jsp页面中,可以使用**${pageContex.request.contextPath}**表示页面上下文路径。

如项目默认上下文访问路径为localhost:8080/system

<a href="${pageContex.request.contextPath}/pages/hello.html">跳转</a>

以上路径相当于/system/pages/hello.html,即从根目录出发localhost:8080/system/pages/hello.html

如果在jsp页面中无法识别${},在<%@ page%>中加入isELIgnored=“false”

<%@ page contentType="text/html;charset=UTF-8" language="java"  isELIgnored="false" %>

四大作用域对象

作用域:共享数据的区域

pageContext

当前页面对象。共享数据区域范围为当前页面。

如果不在同一个页面,数据无法读取。

request

请求对象。共享数据区域范围为一次请求。

如果跳转中途使用了重定向,数据无法读取。

session

会话对象。会话是用户访问服务器时的某个时间段。

共享数据区域范围在这个时间段内,默认30分钟。

如果在指定时间内没有操作或销毁会话时,数据无法读取。

application

项目对象。共享数据区域范围为整个项目。

作用域范围

application > session > request > pageContext

以上四个作用域对象,都有这几个方法

//将某个对象obj保存到作用域中,命名为str
作用域对象.setAttribute(String str,Object obj);
//从某个作用域中获取保存的某个对象
Object obj = 作用域对象.getAttribute(String str);
//从某个作用域中移除某个保存的对象
作用域对象.removeAttribute(String str);

作用域对象的使用

在JSP页面中

作用域对象也称为内置对象,直接通过对应的单词使用

p1.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    //在jsp中使用pageContext页面上下文对象,跳转到p2时不能使用
    pageContext.setAttribute("str","保存在pageContext作用域中的字符串");
    //在jsp中使用request请求对象,请求转发到p2时可以使用,重定向到p2时不能使用
    request.setAttribute("str","保存在request中的字符串");
    //在jsp中使用session会话对象,在默认的30分钟内,没有销毁,哪种跳转都能在p2中使用
    session.setAttribute("str","保存在session中的字符串");
    //在jsp中使用application应用程序对象,整个项目中任何页面都能使用
    application.setAttribute("str","保存在application中的字符串");
    //以上四个作用域对象,也是jsp中的内置对象,无需定义

    //销毁会话
    //session.invalidate();

    //使用请求转发跳转到p2.jsp
    //request.getRequestDispatcher("p2.jsp").forward(request,response);
    //使用重定向跳转到p2.jsp
    response.sendRedirect("p2.jsp");
%>
<h1><%=pageContext.getAttribute("str")%></h1>

</body>
</html>

p2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h3><%=pageContext.getAttribute("str")%></h3>
        <h3><%=request.getAttribute("str")%></h3>
        <h3><%=session.getAttribute("str")%></h3>
        <h3><%=application.getAttribute("str")%></h3>
    </body>
</html>

在servlet中使用

  • pageContext

    servlet本身就是一个java类,在类中定义成员变量,就能在当前类中使用。

    所以在servlet中不会使用pageContext对象

  • request

    使用doGet/doPost/service方法中的HttpServletRequest参数req

  • session

    //在servlet中使用session,需要通过请求对象request调用getSession()方法
    HttpSession session= req.getSession();
    
  • application

    //通过getServletContext()方法获取的ServletContext类型对象,就是当前项目对象
    ServletContext application = getServletContext();
    

总结

  • 在jsp页面中使用pageContext保存的数据,只能共享于当前页面
  • 通常在servlet中查询后的数据保存在request中,使用请求转发跳转到其他页面,在对应的页面中数据数据
  • 通常在登录后,将登录的用户保存在session中,无论用哪种方式跳转,都能从session中获取当时登录的用户。
  • 在application中保存一些共享于整个项目中的数据

购物车

添加商品时,如果不存在,直接添加,如果存在,修改数量。

采用HashMap作为核心容器。将商品作为键,将购买数量作为值。

使用Hash相关集合,需要重写实体类中的hashcode和equals方法。

实现过程

1.BookInfo和BookType类重写equals和hashcode方法,选择所有属性

2.创建购物车类Cart类

package com.hqyj.bookShop.util;

import com.hqyj.bookShop.entity.BookInfo;

import java.util.HashMap;

/*
 * 购物车
 * 使用HashMap作为购物车容器
 * 添加商品方法
 * 移除商品方法
 * 查看购物车
 * 清空购物车
 *
 * */
public class Cart {
    /*
     * 商品作为键,购买数量作为值
     * */
    private HashMap<BookInfo, Integer> hm;

    /*
     * 创建购物车对象时,初始化HashMap
     * */
    public Cart() {
        hm = new HashMap<>();
    }

    /*
     * 添加到购物车
     * */
    public void addToCart(BookInfo bi, int buyNum) {
        //判断键是否存在
        if (hm.containsKey(bi)) {//如果存在,修改数量
            //获取现有数量
            Integer oldNum = hm.get(bi);
            //覆盖原本的值
            hm.put(bi,oldNum+buyNum);
        }else{//如果不存在,直接添加
            hm.put(bi,buyNum);
        }
    }

    /*
    * 获取购物车
    * */
    public HashMap<BookInfo, Integer> getCart() {
        return hm;
    }
    
    /*
    	移除商品
    */
    
    /*
    	清空购物车
    */
}

3.在登录成功后,创建购物车Cart对象,将其保存到session中

case "login":
    //获取登录信息
    String phone = req.getParameter("phone");
    String password = req.getParameter("password");
    //调用登录
    Customer login = dao.login(phone, password);
    if (login != null) {
        //登录成功后,创建购物车对象
        Cart cart = new Cart();
        //将购物车保存到session中
        session.setAttribute("cart",cart);
        //将登录成功的对象,保存到session中
        session.setAttribute("customer", login);
        //使用重定向跳转到查询所有图书的servlet
        resp.sendRedirect("./bookInfo?op=search");
    } else {
        System.out.println("登录失败");
    }
break;

4.在图书详情页中,创建添加商品到购物车的表单

<%--提交到图书servlet--%>
<form action="./bookInfo">
    <%--op、图书编号、购买数量--%>
    <input type="hidden" name="op" value="addToCart">
    <input type="hidden" name="id" value="<%=bi.getBookId()%>">
    <input type="number" name="buyNum" value="1" min="1" max="<%=bi.getBookNum()%>" ><br>
    <input type="submit" value="添加到购物车">
</form>

5.在图书servlet中添加新op判断,获取要购买的数据

case "addToCart":
//图书编号
String buyId = req.getParameter("id");
//调用查询
BookInfo byId = biDao.findById(Integer.parseInt(buyId));
//获取购买数量
int buyNum = Integer.parseInt(req.getParameter("buyNum"));
//从session中获取购物车
Cart cart = (Cart) req.getSession().getAttribute("cart");
//调用添加
cart.addToCart(byId, buyNum);
//跳转购物车页面
resp.sendRedirect("./pages/cart.jsp");
break;

6.购物车页面

<%@ page import="com.hqyj.bookShop.util.Cart" %>
<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <style>
            * {
                margin: 0;
                padding: 0;
            }

            body {
                background-color: #f5f5f5;
            }

            .wrap {
                width: 900px;
                height: 660px;
                margin: 100px auto;
                background-color: #fff;
            }


            a {
                text-decoration: none;
                color: deepskyblue;
            }

            .wrap table {
                width: 100%;
                border: 1px solid skyblue;
                border-collapse: collapse;
            }

            .wrap table th, td {
                width: 120px;
                height: 20px;
                border: 1px solid skyblue;

            }
        </style>
    </head>
    <body>

        <%--防止在未登录的情况下访问该页面--%>
        <%--
    如果登录成功,session中保存登录的对象customer,
    如果没有登录或者退出,customer对象就会消失。
    所以判断customer对象是否存在,决定是否显示该页面
    --%>
        <%
        //如果从session中无法获取customer对象,跳转到登录页面
        if (session.getAttribute("customer") == null) {
            response.sendRedirect("login.html");
            return;
        }
        //获取session中的购物车对象
        Cart cart = (Cart) session.getAttribute("cart");


        %>

        <jsp:include page="top.jsp"></jsp:include>

        <div class="wrap">
            <table>
                <tr>
                    <th>图书编号</th>
                    <th>图书名称</th>
                    <th>图书作者</th>
                    <th>图书单价</th>
                    <th>购买数量</th>
                    <th>小计</th>
                </tr>

                <%
                double sumPrice = 0;
                for (BookInfo bookInfo : cart.getCart().keySet()) {
                    sumPrice += cart.getCart().get(bookInfo) * bookInfo.getBookPrice();
                    %>

                <tr>
                    <td><%=bookInfo.getBookId()%>
                    </td>
                    <td><%=bookInfo.getBookName()%>
                    </td>
                    <td><%=bookInfo.getBookAuthor()%>
                    </td>
                    <td><%=bookInfo.getBookPrice()%>
                    </td>
                    <td><%=cart.getCart().get(bookInfo)%>
                    </td>
                    <td><%=cart.getCart().get(bookInfo) * bookInfo.getBookPrice()%>
                    </td>
                </tr>
                <%}%>
                <tr>
                    <td colspan="5">总计</td>
                    <td><%=sumPrice%>
                    </td>
                </tr>
            </table>
        </div>
    </body>
</html>

JSP

Java Server Page

使用Java开发、运行在服务器上的页面。

jsp文件的后缀名为".jsp"。

由于最初由servlet渲染页面,在Java代码中加入大量html的内容,使用极不方便。所以Sun公司推出了JSP,可以在页面中加入java代码,让页面成为动态页面。

JSP页面的本质是一个java文件(servlet)

在访问某个jsp页面时,会让该页面重新编译为.java文件–>.class文件,所以第一次访问某个JSP页面时会慢一些。

JSP的组成

1.HTML元素

2.脚本(java代码)

<%java代码;%>

3.表达式

用于在页面中嵌入变量的值

<%=变量%>

4.指令

<%@ 指令名 属性="值" %>

page指令 用于设置当前页面的属性

include指令 用于引入其他页面

taglib指令 用于引入其他标签库

5.注释

<%-- jsp注释 --%>

在浏览器中可以查看html的注释,无法查看jsp的注释

6.声明

<%!  定义方法  %>

在<%%>中无法定义方法,如果非要在jsp页面中定义方法,需要使用声明。不建议在jsp页面中定义方法。

7.动作

jsp中定义了一些标签,可以代替某些java代码

<jsp:动作名></jsp:动作名>

跳转

页面与页面之间跳转

<a href="另一个页面的地址">超链接</a>

<form action="另一个页面的地址">
    <input type="submit">
</form>

<button id="btn">跳转</button>
<script>
	$("#btn").click(function(){
        location.href="另一个页面的地址";
        
        location.assign("另一个页面的地址");
    });
</script>

页面跳转至Servlet

<a href="servlet映射名">超链接</a>

<form action="servlet映射名">
    <input type="submit">
</form>

Servlet跳转到页面或另一个Servlet

请求转发(内部跳转)

request.getRequestDispatcher("跳转的地址").forward(request,response);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTb9F9HR-1673432610013)(D:\JavaWeb\JavaWebDay3.assets\image-20230104143342895.png)]

如A同学问B同学问题,B同学自己去问C同学后得到了答案,将答案告诉给A同学。

  • 跳转到目的地时,浏览器的地址栏中的内容是访问时的地址

  • 如果在某个Servlet做完增删改的操作后,不要使用请求转发。因为当重新刷新页面时,会重复提交

  • 如果在request中保存了数据,只能通过请求转发才能读取request中保存的数据

重定向(外部跳转)

response.sendRedirect("跳转的地址");

如A同学问B同学问题,B同学告诉A同学去问C同学,A同学重新问C同学后得到答案。

  • 跳转到目的地时,浏览器的地址栏中的内容是最终的目的路径
  • 在做完增删改的操作后,使用重定向,可以保证最终页面与之前页面无关,刷新时不会重新提交
  • 如果在request中保存了数据,使用重定向,保存的数据就会丢失

跳转时传递数据

保存

作用域对象.setAttribute(String str,Object obj);
//将一个名为str的对象obj保存到某个作用域中。request就是一个作用域。


List<泛型> 集合 = dao.查询();
//将查询到的集合保存到请求中,命名为list
request.setAttribute("list",集合);

获取

Object obj = 作用域对象.getAttribute(String str);
//获取到的数据是Object类型,通常需要转型

List<泛型> list =(List<泛型>) request.getAttribute("list");

带有外键字段的实体类设计

图书类型book_type表(主表)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pM0DakHo-1673432610014)(D:\JavaWeb\JavaWebDay3.assets\image-20230104173450135.png)]

图书详情book_info表(从表)

1.创建主表的实体类

BookType类

package com.hqyj.bookShop.entity;
/*
* 创建主表对应的实体类
* */
public class BookType {
    private int typeId;
    private String typeName;

    //省略get/set/构造方法/toString
}

2.创建从表的实体类

BookInfo类

遇到外键字段,额外添加一个外键对应的主表实体对象属性

package com.hqyj.bookShop.entity;

/*
 * 创建带有外键字段的表(从表)对应的实体类
 * 1.写出所有字段对应的属性
 * 2.写出外键对应的主表实体对象
 * */
public class BookInfo {
    private int bookId;
    private int typeId;
    private String bookName;
    private String bookAuthor;
    private int bookPrice;
    private int bookNum;
    private String publisherDate;
    //额外添加属性:外键对应的主表实体对象
    private BookType bookType;

     /*
    * 用于查询的构造方法
    * */
    public BookInfo(int bookId, int typeId, String bookName, String bookAuthor, int bookPrice, int bookNum, String publisherDate, BookType bookType) {
        this.bookId = bookId;
        this.typeId = typeId;
        this.bookName = bookName;
        this.bookAuthor = bookAuthor;
        this.bookPrice = bookPrice;
        this.bookNum = bookNum;
        this.publisherDate = publisherDate;
        this.bookType = bookType;
    }

    //省略get/set/toString
  
}

3.创建两个实体类的数据访问层对象

BookTypeDao类

package com.hqyj.bookShop.dao;

import com.hqyj.bookShop.entity.BookType;
import com.hqyj.bookShop.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BookTypeDao {

    Connection conn;
    PreparedStatement pst;
    ResultSet rs;

    /*
    * 根据类型编号查询类型对象
    * */
    public BookType findById(int id) {
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("select * from book_type where type_id=?");
            pst.setInt(1, id);
            rs = pst.executeQuery();
            if (rs.next()) {
                BookType bookType = new BookType(rs.getInt(1), rs.getString(2));
                return bookType;
            }
        } catch (SQLException e) {
            System.out.println("根据ID查询异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return null;
    }

}

BookInfoDao类

package com.hqyj.bookShop.dao;

import com.hqyj.bookShop.entity.BookInfo;
import com.hqyj.bookShop.entity.BookType;
import com.hqyj.bookShop.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class BookInfoDao {

    BookTypeDao btDao = new BookTypeDao();

    /*
     * 查询所有类型
     * */
    Connection conn;
    PreparedStatement pst;
    ResultSet rs;

    /*
     * 查询所有
     * */
    public List<BookInfo> queryAll() {
        ArrayList<BookInfo> list = new ArrayList<>();
        conn = DBUtil.getConn();
        try {
            pst = conn.prepareStatement("select * from book_info");
            rs = pst.executeQuery();
            while (rs.next()) {
                int bookId = rs.getInt(1);
                int typeId = rs.getInt(2);
                String bookName = rs.getString(3);
                String bookAuthor = rs.getString(4);
                int bookPrice = rs.getInt(5);
                int bookNum = rs.getInt(6);
                String publisherDate = rs.getString(7);
                //参数中所需的主表对象实体,需要通过dao对象查询
                BookInfo bookInfo = new BookInfo(bookId, typeId, bookName, bookAuthor, bookPrice, bookNum, publisherDate, btDao.findById(typeId));
                list.add(bookInfo);
            }
        } catch (SQLException e) {
            System.out.println("查询所有异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return list;
    }


}

4.创建Servlet

这里创建BookInfo的Servlet即可

BookInfoServlet类

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.BookInfoDao;
import com.hqyj.bookShop.entity.BookInfo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/bookInfo")
public class BookInfoServlet extends HttpServlet {
    //创建数据访问层对象
    BookInfoDao biDao= new BookInfoDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        switch (op) {
            case "queryAll":
                //查询所有图书,保存到请求中,跳转到页面
                List<BookInfo> list = biDao.queryAll();
                req.setAttribute("list",list);
                req.getRequestDispatcher("./pages/bookList.jsp").forward(req,resp);
                break;
        }
    }
}

5.创建页面

列表页面位于pages目录下,遍历数据

<!--样式略-->

<%for (BookInfo bookInfo : list) {%>
<div>
    <img src="图片路径">
    <p class="title"><%=bookInfo.getBookName()%>
    </p>
    <p class="desc"><%=bookInfo.getBookAuthor()%>|<%=bookInfo.getBookType().getTypeName()%>
    </p>
    <p class="price"><%=bookInfo.getBookPrice()%>元起</p>
</div>
<% }%>

MySQL分页查询

原理

select * from;
-- 查询前N条记录
select * fromlimit N;
-- 从第N条记录开始查询M条记录
select * fromlimit N,M;

-- 如每页显示8条,第一页
select * fromlimit 0,8
-- 第二页
select * fromlimit 8,8

-- 公式 size表示每页显示的数量 page表示页数
select * fromlimit (page-1)*size,size

dao层中分页相关方法

/*
     * 查询总记录数
     * */
public int getSumCount() {
    conn = DBUtil.getConn();
    String sql = "select count(book_id) from book_info ";
    try {
        pst = conn.prepareStatement(sql);
        rs = pst.executeQuery();
        if (rs.next()) {
            return rs.getInt(1);
        }
    } catch (SQLException e) {
        System.out.println("查询总记录数异常" + e);
    } finally {
        DBUtil.release(conn, pst, rs);
    }
    return 0;
}

/*
     * 分页查询
     * */
public List<BookInfo> queryByPage(int page, int size) {
    ArrayList<BookInfo> list = new ArrayList<>();
    conn = DBUtil.getConn();
    String sql = "select * from book_info limit ?,?";
    try {
        pst = conn.prepareStatement(sql);
        pst.setInt(1, (page - 1) * size);
        pst.setInt(2, size);
        rs = pst.executeQuery();
        while (rs.next()) {
            int bookId = rs.getInt(1);
            int typeId = rs.getInt(2);
            String bookName = rs.getString(3);
            String bookAuthor = rs.getString(4);
            int bookPrice = rs.getInt(5);
            int bookNum = rs.getInt(6);
            String publisherDate = rs.getString(7);
            String bookImg = rs.getString(8);
            //参数中所需的主表对象实体,需要通过dao对象查询
            BookInfo bookInfo = new BookInfo(bookId, typeId, bookName, bookAuthor, bookPrice, bookNum, publisherDate, bookImg, btDao.findById(typeId));
            list.add(bookInfo);
        }
    } catch (SQLException e) {
        System.out.println("分页查询异常" + e);
    } finally {
        DBUtil.release(conn, pst, rs);
    }
    return list;
}

servlet中加入分页请求判断

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.BookInfoDao;
import com.hqyj.bookShop.entity.BookInfo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/bookInfo")
public class BookInfoServlet extends HttpServlet {
    //创建数据访问层对象
    BookInfoDao biDao = new BookInfoDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        switch (op) {
    		//分页查询
            case "queryByPage":
                //得到总记录数
                int sumCount = biDao.getSumCount();
                //将总记录数保存到请求中
                req.setAttribute("sumCount",sumCount);
                //初始第一页
                int page=1;
                int size=8;
                //获取要查询的页数
                if (req.getParameter("page")!=null) {
                    page=Integer.parseInt(req.getParameter("page"));
                }
                //调用分页查询
                List<BookInfo> list2 = biDao.queryByPage(page,size);
                //将查询的结果保存、跳转
                req.setAttribute("list", list2);
                req.getRequestDispatcher("./pages/bookList.jsp").forward(req, resp);
                break;
        }
    }
}

页面

<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
<%
    List<BookInfo> list = (List<BookInfo>) request.getAttribute("list");
%>
<div class="product_list">
    <div class="left"></div>
    <div class="right">
        <%for (BookInfo bookInfo : list) {%>
        <div>
            <img alt="暂无图片" src="./img/<%=bookInfo.getBookImg()%>">
            <p class="title"><%=bookInfo.getBookName()%>
            </p>
            <p class="desc"><%=bookInfo.getBookType().getTypeName()%>|<%=bookInfo.getBookAuthor()%>
            </p>
            <p class="price">¥<%=bookInfo.getBookPrice()%>
            </p>
        </div>
        <% }%>
    </div>
</div>
<%
    /*pno默认1*/
    int pno = 1;
    /*从请求中获取当前页数*/
    if (request.getParameter("page") != null) {
        pno = Integer.parseInt(request.getParameter("page"));
    }
    /*获取总记录数*/
    int sumCount = (Integer) request.getAttribute("sumCount");

    //计算最大页数
    int maxPage=(int)Math.ceil(sumCount/8.0);
%>
<div class="pageTool">
    <%--在请求分页的servlet时,传递page参数表示当前页--%>
    <a href="http://localhost:8080/Web03/bookInfo?op=queryByPage&page=<%=(pno-1==0)?1:pno-1%>">上一页</a>
    <span>第<%=pno%>页</span>
    <span>共<%=maxPage%>页</span>
    <a href="http://localhost:8080/Web03/bookInfo?op=queryByPage&page=<%=pno+1>maxPage?maxPage:pno+1%>">下一页</a>
</div>
</body>
</html>

条件分页(关键字搜索)

原理

select * fromwhere 字段 like concat('%',keyword,'%') limit (page-1)*size,size 

dao

package com.hqyj.bookShop.dao;

import com.hqyj.bookShop.entity.BookInfo;
import com.hqyj.bookShop.entity.BookType;
import com.hqyj.bookShop.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class BookInfoDao {

    BookTypeDao btDao = new BookTypeDao();

    /*
     * 查询所有类型
     * */
    Connection conn;
    PreparedStatement pst;
    ResultSet rs;

    
    /*
     * 根据关键字查询总记录数
     * */
    public int getSumCount(String keyword) {
        conn = DBUtil.getConn();
        String sql = "select count(book_id) from book_info where book_name like concat('%',?,'%')";
        try {
            pst = conn.prepareStatement(sql);
            pst.setString(1,keyword);
            rs = pst.executeQuery();
            if (rs.next()) {
                return rs.getInt(1);
            }
        } catch (SQLException e) {
            System.out.println("查询总记录数异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return 0;
    }


    /*
    * 条件查询(关键字分页)
    * */
    public List<BookInfo> queryByCondition(int page,int size,String keyword){
        ArrayList<BookInfo> list = new ArrayList<>();
        conn = DBUtil.getConn();
        String sql = "select * from book_info where book_name like concat('%',?,'%') limit ?,?";
        try {
            pst = conn.prepareStatement(sql);
            pst.setString(1, keyword);
            pst.setInt(2, (page-1)*size);
            pst.setInt(3, size);
            rs = pst.executeQuery();
            while (rs.next()) {
                int bookId = rs.getInt(1);
                int typeId = rs.getInt(2);
                String bookName = rs.getString(3);
                String bookAuthor = rs.getString(4);
                int bookPrice = rs.getInt(5);
                int bookNum = rs.getInt(6);
                String publisherDate = rs.getString(7);
                String bookImg = rs.getString(8);
                //参数中所需的主表对象实体,需要通过dao对象查询
                BookInfo bookInfo = new BookInfo(bookId, typeId, bookName, bookAuthor, bookPrice, bookNum, publisherDate, bookImg, btDao.findById(typeId));
                list.add(bookInfo);
            }
        } catch (SQLException e) {
            System.out.println("关键字分页查询异常" + e);
        } finally {
            DBUtil.release(conn, pst, rs);
        }
        return list;
    }

}

servlet

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.BookInfoDao;
import com.hqyj.bookShop.entity.BookInfo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/bookInfo")
public class BookInfoServlet extends HttpServlet {
    //创建数据访问层对象
    BookInfoDao biDao = new BookInfoDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        switch (op) {
            case "search":
                //获取搜索关键字,第一次访问时没有关键字,使用""查询
                String keyword = req.getParameter("keyword")==null?"":req.getParameter("keyword");
                //得到总记录数
                int sumCount = biDao.getSumCount(keyword);
                //将总记录数保存到请求中
                req.setAttribute("sumCount", sumCount);
                //初始第一页
                int page = 1;
                int size = 8;
                //获取要查询的页数
                if (req.getParameter("page") != null) {
                    page = Integer.parseInt(req.getParameter("page"));
                }
                //调用条件查询,保存集合,跳转页面
                List<BookInfo> list = biDao.queryByCondition(page, size, keyword);
                req.setAttribute("list",list);
                req.getRequestDispatcher("./pages/bookList.jsp").forward(req, resp);
                break;
        }
    }
}

页面

<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #f5f5f5;
        }

        .product_list {
            width: 1240px;
            height: 614px;
            margin: 0px auto;
        }

        .product_list > div {
            float: left;
        }

        .left {
            width: 234px;
            height: 614px;
            background-image: url(./img/left.jpg);
            background-size: 100%;

        }

        .right {
            width: 992px;
            height: 614px;
        }

        .right > div {
            width: 234px;
            height: 300px;
            background-color: #fff;
            float: left;
            margin-left: 14px;
            margin-bottom: 14px;
            position: relative;
            transition-duration: 0.2s;
        }

        .right > div img {
            width: 160px;
            height: 160px;
            display: block;
            margin: 20px auto;
        }

        .right .title {
            font-size: 14px;
            font-weight: 400;
            text-align: center;
            color: #333;
        }

        .right .desc {
            width: 214px;
            height: 18px;
            margin: 2px auto 10px;
            font-size: 12px;
            color: #b0b0b0;
            overflow: hidden;
            text-align: center;
        }

        .right .price {
            text-align: center;
            color: #ff6700;
            font-size: 14px;
        }


        .right > div:hover {
            /* 向上平移2px */
            transform: translate(0, -3px);
            box-shadow: rgba(0, 0, 0, 0.2) 0 0 20px;
        }

        .header {
            width: 100%;
            height: 80px;
            border-bottom: 2px solid #eee;
            background-color: #fff;
        }

        .header .logo {
            width: 240px;
            height: 80px;
            float: left;
            margin-left: 100px;
            background-image: url("./img/logo.png");
            background-size: cover;
        }

        .header .customer {
            float: right;
            width: 300px;
            height: 80px;
            text-align: center;
            line-height: 80px;
            color: deepskyblue;
            font-size: 14px;
            margin-right: 100px;
        }

        a {
            text-decoration: none;
            color: deepskyblue;
        }

        .search {
            width: 600px;
            height: 80px;
            margin: 0 auto;
        }

        .search input[type="text"] {
            width: 300px;
            height: 40px;
            margin-top: 20px;
            margin-left: 100px;
            font-size: 20px;
            outline: none;
            border: none;
            border: 1px solid skyblue;
        }

        .search input[type="submit"] {
            width: 80px;
            height: 40px;
            border: none;
            background-color: #fff;
            color: deepskyblue;
            font-size: 20px;
        }

        .pageTool {
            width: 200px;
            margin: 0 auto 100px;
        }
    </style>
</head>
<body>
<%
    List<BookInfo> list = (List<BookInfo>) request.getAttribute("list");
%>

<div class="header">
    <div class="logo"></div>
    <div class="customer">
        当前客户:xxx
        <a href="#">我的购物车</a>
        <a href="#">安全退出</a>
    </div>
</div>
<div class="search">
    <form action="./bookInfo">
        <input type="hidden" name="op" value="search">
        <input type="text" placeholder="请输入关键字查询" value="<%=request.getParameter("keyword")==null?"": request.getParameter("keyword")%>"  name="keyword">
        <input type="submit" value="搜索">
    </form>
</div>
<div class="product_list">
    <div class="left"></div>
    <div class="right">
        <%for (BookInfo bookInfo : list) {%>
        <div>
            <img alt="暂无图片" src="./img/<%=bookInfo.getBookImg()%>">
            <p class="title"><%=bookInfo.getBookName()%>
            </p>
            <p class="desc"><%=bookInfo.getBookType().getTypeName()%>|<%=bookInfo.getBookAuthor()%>
            </p>
            <p class="price">¥<%=bookInfo.getBookPrice()%>
            </p>
        </div>
        <% }%>
    </div>
</div>

<%

    /*pno默认1*/
    int pno = 1;
    /*从请求中获取当前页数*/
    if (request.getParameter("page") != null) {
        pno = Integer.parseInt(request.getParameter("page"));
    }
    /*获取总记录数*/
    int sumCount = (Integer) request.getAttribute("sumCount");

    //计算最大页数
    int maxPage=(int)Math.ceil(sumCount/8.0);
    //获取请求中的关键字,如果没有搜索过,使用空白字符串
    String keyword= request.getParameter("keyword")==null?"": request.getParameter("keyword");

%>
<div class="pageTool">
    <%--在请求分页的servlet时,传递page参数表示当前页--%>
    <a href="http://localhost:8080/Web03/bookInfo?op=search&keyword=<%=keyword%>&page=<%=(pno-1==0)?1:pno-1%>">上一页</a>
    <span>第<%=pno%>页</span>
    <span>共<%=maxPage%>页</span>
    <a href="http://localhost:8080/Web03/bookInfo?op=search&keyword=<%=keyword%>&page=<%=pno+1>maxPage?maxPage:pno+1%>">下一页</a>
</div>
</body>
</html>

首页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Title</title>
    </head>
    <body>

        <%
        /*访问该页面时,跳转至分页查询*/
        response.sendRedirect("http://localhost:8080/Web03/bookInfo?op=search");
        %>
    </body>
</html>

EL

Expression Language 表达式语言

是为了使JSP写起来更加简便,替换JSP中的<%=%>,简化了JSP页面中输出数据的操作。

主要输出保存在某个作用域中的数据。

特点

如果通过"某个作用域对象.setAttribute(“cus”,customer)"方法保存的对象,

在JSP页面中如果用表达式,使用<%=cus%>,如果用EL,使用**${cus}**输出。

会依次从pageContext–>reqeust–>session–>application中获取指定对象,

如果一旦从某个作用域中获取到了指定对象,就不再判断后续作用域。

也可以输出指定作用域中的对象。

  • 只能输出保存在作用域中的对象

  • 减少代码(省去了获取对象、转换的过程)

  • 免去非空判断

    • 如果某个要输出的对象不存在,不会输出null,而是输出空字符串""。

使用

在页面中输出保存在作用域中的对象

  • 从作用域中依次查询并输出对象

    ${对象名}
    
  • 从指定作用域中输出对象

    作用域对应作用域代码
    pageScope当前页pageContex${pageScope.对象}
    requestScope请求request${requestScope.对象}
    sessionScope会话session${sessionScope.对象}
    applicationScope项目application${applicationScope.对象}
  • 输出对象的属性

    ${对象名.属性名}
    ${对象名["属性名"]}
    
  • 输出对象的方法返回值

    ${对象名.方法名()}
    

如在servlet中

Person p = new Person("admin","男",20);
request.setAttribute("p",p);

跳转到某个页面中

<html>
    <head></head>
    <body>
        <%-- 如果不用EL,先获取对象,向下转型 --%>
        <% Person p =(Person) request.getAttribute("p");%>
        <%-- 如果p为null,会报错,如果name没有赋值,会输出null --%>
        <h3>
            <%=p.getName()%>;
        </h3>

        <%--如果使用EL,无需获取对象,无需转型,直接通过保存的对象名.属性名即可--%>
        <h3>
            ${p.name}
        </h3>
        <%--使用EL输出对象的属性时,该对象必须要有getXX()方法--%>
        <%--如果没有在任何作用域中获取到对象p,或对象p没有name属性,不会保存,输出空字符串--%>
        <h3>
            ${p["name"]}
        </h3>
    </body>
</html>

在页面中获取请求中的参数

用于获取表单提交的数据或超链接?后传递的数据。

使用${param.参数名}替换request.getParameter(“参数”)。

如有表单或超链接

<form action="page.jsp">
    <input type="text" name="username">
    <input type="submit">
</form>

<a href="page.jsp?username=admin">跳转</a>

在page.jsp中获取

<html>
    <head></head>
    <body>
        <%-- 传统写法--%>
        <% String username =  request.getParameter("username");%>
        <h3>
            <%=username%>;
        </h3>

        <%--如果使用EL--%>
        <h3>
            ${param.username}
        </h3>
    </body>
</html>

用于获取当前项目上下文(根目录+项目名)路径

如http://localhost:8080/Web03/就是一个项目上下文路径,

在JSP中使用**${pageContext.request.contextPath}**获取项目上下文路径

<form action="${pageContext.request.contextPath}/servlet映射">
    
</form>

<a href="${pageContext.request.contextPath}/页面路径">超链接</a>

注意

  • web.xml版本在4.0之后,在JSP中使用EL时,默认可以识别。

  • 如果JSP无法识别EL,在指令(<%@ %>)中加入 isELIgnored="false"表示不忽略EL。

    <%@ page contentType="text/html;charset=UTF-8" language="java"  isELIgnored="false"%>
    
  • 如果在使用EL过程中,出现PropertyNotFoundException异常,表示未发现指定属性,原因有

    • 缺少指定属性
    • 指定属性没有对应的get方法

JSTL

Java Server Page Standarded Tag Library JSP标准标签库

可以使用JSTL中的特定标签,来替换JSP中常见的Java代码。如循环判断等,减少Java代码,提高页面的可读性。

使用

1.导入JSTL对应的依赖

https://mvnrepository.com/artifact/javax.servlet/jstl/1.2

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

2.在JSP页面中,加入标签库指令

<%--在当前页面中使用jstl,加入以下指令--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

这句话可以不写,在使用循环遍历时会自动生成。

3.具体用法

  • 定义变量或给变量赋值

    <c:set var="变量名" value="值"></c:set>
    

    <c:set var="num" value="123123"></c:set>
    
  • if判断

    <c:if test="判断条件">
    	满足条件时的内容
    </c:if>
    

    如在servlet中

    request.setAttribute("person",new Person("ez","男"));
    

    在Jsp中

    <c:if test="${person.sex=='男'}">
    	<input type="raido" name="sex" checked>男
        <input type="raido" name="sex" >女
    </c:if>
    
  • 遍历List集合

    <c:forEach items="要遍历的集合" var="遍历出的对象名"></c:forEach>
    

    如servlet中保存了集合

    List<BookInfo> list = dao.queryAll();
    request.setAttribute("list",list);
    

    在jsp页面中

      <%--判断集合为空--%>
      <c:if test="${empty list}">
      	无数据
      </c:if>
      
      <c:forEach items="${list}" var="bi">
          <tr>
              <td>${bi.bookName}</td>
            <td>${bi.bookAuthor}</td>
          </tr>
      </c:forEach>
    
  • 遍历Map集合

    <c:forEach items="要遍历的集合名" var="遍历出的键值对的名称">
    	${键值对名.key.属性}
        ${键值对名.value.属性}
    </c:forEach>
    

    <%
    	HashMap<String,String> hm=new HashMap();
    	hm.put("yyds","永远单身");
    	hm.put("xswl","吓死我了");
    	hm.put("pyq","朋友圈");
    	session.setAttribute("hm",hm);
    %>
    <c:forEach items="${hm}" var="kv">
        <!--遍历键-->
    	<h3>${kv.key}</h3>
         <!--遍历值-->
    	<h3>${kv.value}</h3>
    </c:forEach>
    

使用EL和JSTL实现商城首页

客户servlet

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.CustomerDao;
import com.hqyj.bookShop.entity.Customer;
import com.hqyj.bookShop.util.Cart;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/customer")
public class CustomerServlet extends HttpServlet {

    CustomerDao dao = new CustomerDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        //获取session对象,默认30分钟有效
        HttpSession session = req.getSession();
        //设置session有效时间,单位为秒
        //session.setMaxInactiveInterval(10);
        switch (op) {
            case "login":
                //获取登录信息
                String phone = req.getParameter("phone");
                String password = req.getParameter("password");
                //调用登录
                Customer login = dao.login(phone, password);
                if (login != null) {
                    //登录成功后,创建购物车对象
                    Cart cart = new Cart();
                    //将购物车保存到session中
                    session.setAttribute("cart",cart);
                    //将登录成功的对象,保存到session中
                    session.setAttribute("customer", login);
                    //使用重定向跳转到查询所有图书的servlet
                    resp.sendRedirect("./bookInfo?op=search");
                } else {
                    System.out.println("登录失败");
                }
                break;
            case "logout":
                //从session中移除登录的对象
                //session.removeAttribute("customer");
                //销毁session
                session.invalidate();
                resp.sendRedirect("./bookInfo?op=search");
                break;
        }
    }
}

商品servlet

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.BookInfoDao;
import com.hqyj.bookShop.entity.BookInfo;
import com.hqyj.bookShop.util.Cart;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet("/bookInfo")
public class BookInfoServlet extends HttpServlet {
    //创建数据访问层对象
    BookInfoDao biDao = new BookInfoDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        switch (op) {
            case "search":
                //获取搜索关键字,第一次访问时没有关键字,使用""查询
                String keyword = req.getParameter("keyword") == null ? "" : req.getParameter("keyword");
                //得到总记录数
                int sumCount = biDao.getSumCount(keyword);
                //将总记录数保存到请求中
                req.setAttribute("sumCount", sumCount);
                //初始第一页
                int page = 1;
                int size = 8;
                //获取要查询的页数
                if (req.getParameter("page") != null) {
                    page = Integer.parseInt(req.getParameter("page"));
                }
                //调用条件查询,保存集合,跳转页面
                List<BookInfo> list = biDao.queryByCondition(page, size, keyword);
                req.setAttribute("list", list);
                req.getRequestDispatcher("./pages/bookList.jsp").forward(req, resp);
                break;
            case "findById":
                //获取要查询的id,调用方法,保存查询的结果,跳转详情页面
                int id = Integer.parseInt(req.getParameter("id"));
                BookInfo bi = biDao.findById(id);
                req.setAttribute("bi", bi);
                req.getRequestDispatcher("./pages/bookInfo.jsp").forward(req, resp);
                break;
                //    添加到购物车
            case "addToCart":
                //图书编号
                String buyId = req.getParameter("id");
                //调用查询
                BookInfo byId = biDao.findById(Integer.parseInt(buyId));
                //获取购买数量
                int buyNum = Integer.parseInt(req.getParameter("buyNum"));
                //从session中获取购物车
                Cart cart = (Cart) req.getSession().getAttribute("cart");
                //调用添加
                cart.addToCart(byId, buyNum);
                //跳转购物车页面
                resp.sendRedirect("./pages/cart.jsp");
                break;
                //移除商品
            case "remove":
                //图书编号
                String removeId = req.getParameter("id");
                //调用查询
                BookInfo removeBook = biDao.findById(Integer.parseInt(removeId));
                //调用移除
                ((Cart) req.getSession().getAttribute("cart")).removeFromCart(removeBook);
                //跳转购物车页面
                resp.sendRedirect("./pages/cart.jsp");
                break;
            case "clear":
                //调用移除
                ((Cart) req.getSession().getAttribute("cart")).clearCart();
                //跳转购物车页面
                resp.sendRedirect("./pages/cart.jsp");
                break;
        }
    }
}

顶部页面

<%@ page import="com.hqyj.bookShop.entity.Customer" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
    <head>
        <title>Title</title>
        <style>
           样式略
        </style>
    </head>
    <body>
        <div class="header">
            <a href="${pageContext.request.contextPath}/bookInfo?op=search">
                <div class="logo"></div>
            </a>
            <div class="customer">
                <%--这个判断表示customer是否为Null--%>
                <c:if test="${empty customer}">
                    <a href="${pageContext.request.contextPath}/pages/login.html">请登录</a>
                </c:if>
                <%--这个判断表示customer是否不为Null--%>
                <c:if test="${!empty customer}">
                    当前客户:${customer.phone}
                    <a href="${pageContext.request.contextPath}/pages/cart.jsp">我的购物车</a>
                    <a href="${pageContext.request.contextPath}/customer?op=logout">安全退出</a>
                </c:if>

            </div>
        </div>
    </body>
</html>

所有商品页面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        样式略
    </style>
</head>
<body>

<%--通过jsp动作:jsp:include导入某个页面--%>
<jsp:include page="top.jsp"></jsp:include>

<div class="search">
    <form action="${pageContext.request.contextPath}/bookInfo">
        <input type="hidden" name="op" value="search">
        <input type="text" placeholder="请输入关键字查询" value="${param.keyword}" name="keyword">
        <input type="submit" value="搜索">
    </form>
</div>
<div class="product_list">
    <div class="left"></div>

    <%--判断集合为空--%>
    <%--    <c:if test="${list.size()==0}">--%>
    <c:if test="${empty list}">
        <div class="right">未查询到相应图书</div>
    </c:if>

    <div class="right">
    
        <c:forEach items="${list}" var="book">
            <div>
                <a href="${pageContext.request.contextPath}/bookInfo?op=findById&id=${book.bookId}">
                    <img alt="暂无图片" src="${pageContext.request.contextPath}/img/${book.bookImg}"></a>
                <p class="title">${book.bookName}</p>
                <p class="desc">${book.bookType.typeName}|${book.bookAuthor}</p>
                <p class="price">¥${book.bookPrice}</p>
            </div>
        </c:forEach>
    </div>
</div>

<%

    /*pno默认1*/
    int pno = 1;
    /*从请求中获取当前页数*/
    if (request.getParameter("page") != null) {
        pno = Integer.parseInt(request.getParameter("page"));
    }
    /*获取总记录数*/
    int sumCount = (Integer) request.getAttribute("sumCount");

    //计算最大页数
    int maxPage = (int) Math.ceil(sumCount / 8.0);
    //获取请求中的关键字,如果没有搜索过,使用空白字符串
    

%>
<div class="pageTool">
    <%--在请求分页的servlet时,传递page参数表示当前页--%>
    <a href="${pageContext.request.contextPath}/bookInfo?op=search&keyword=${param.keyword}&page=<%=(pno-1==0)?1:pno-1%>">上一页</a>
    <span>第<%=pno%>页</span>
    <span>共<%=maxPage%>页</span>
    <a href="${pageContext.request.contextPath}/bookInfo?op=search&keyword=${param.keyword}&page=<%=pno+1>maxPage?maxPage:pno+1%>">下一页</a>
</div>
</body>
</html>

详情页面

<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <style>
       样式略
    </style>
</head>
<body>

<%
    //获取查询出的详情对象
    //BookInfo bi = (BookInfo) request.getAttribute("bi");

%>
<jsp:include page="top.jsp"></jsp:include>
<div class="wrap">
    <div class="book" style="background-image: url(${pageContext.request.contextPath}/img/${bi.bookImg})"></div>
    <div class="info">
        <p style="font-size: 24px;font-weight: bold">《${bi.bookName}》</p>
        <p style="font-size: 16px;color: #aaa">"${bi.bookAuthor}"作品</p>
        <p style="font-size: 24px;color: #f00;">¥${bi.bookPrice}</p>
        <%if (session.getAttribute("customer") != null) {%>
        <%--提交到图书servlet--%>
        <form action="${pageContext.request.contextPath}/bookInfo">
            <%--op、图书编号、购买数量--%>
            <input type="hidden" name="op" value="addToCart">
            <input type="hidden" name="id" value="${bi.bookId}">
            <input type="number" value="1" min="1" max="${bi.bookNum}" name="buyNum"><br>
            <input type="submit" value="添加到购物车">
        </form>
        <%} else {%>
        <h3>请登录后购买</h3>
        <%}%>
    </div>
</div>


</body>
</html>

购物车页面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="com.hqyj.bookShop.util.Cart" %>
<%@ page import="com.hqyj.bookShop.entity.BookInfo" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <style>
            样式略
        </style>
    </head>
    <body>

        <%--防止在未登录的情况下访问该页面--%>
        <%--
    如果登录成功,session中保存登录的对象customer,
    如果没有登录或者退出,customer对象就会消失。
    所以判断customer对象是否存在,决定是否显示该页面
    --%>
        <%
        //如果从session中无法获取customer对象,跳转到登录页面
        if (session.getAttribute("customer") == null) {
            response.sendRedirect("login.html");
            return;
        }
        %>

        <jsp:include page="top.jsp"></jsp:include>

        <div class="wrap">
            <table>
                <tr>
                    <th>图书编号</th>
                    <th>图书名称</th>
                    <th>图书作者</th>
                    <th>图书单价</th>
                    <th>购买数量</th>
                    <th>小计</th>
                    <th>操作</th>
                </tr>
                <%--定义总价--%>
                <c:set var="sum" value="0"></c:set>

                <%--cart是登录成功后保存在session中的购物车Cart类对象--%>
                <%--hm是Cart类中的属性:HashMap对象--%>
                <%--hm必须要有getHm()方法--%>
                <c:forEach items="${cart.hm}" var="kv">
                    <tr>
                        <%--HashMap中,图书对象是键,购买数量是值--%>
                        <%--kv.key即是遍历出的图书对象--%>
                        <td>${kv.key.bookId}</td>
                        <td>${kv.key.bookName}</td>
                        <td>${kv.key.bookAuthor}</td>
                        <td>${kv.key.bookPrice}</td>
                        <%--kv.value即是遍历出的购买数量--%>
                        <td>${kv.value}</td>
                        <%--EL中可以进行计算--%>
                        <td>¥${kv.value * kv.key.bookPrice}</td>
                        <td><a href="${pageContext.request.contextPath}/bookInfo?op=remove&id=${kv.key.bookId}">移除</a></td>
                    </tr>
                    <%--将小计累加--%>
                    <c:set var="sum" value="${sum+kv.value * kv.key.bookPrice}"></c:set>
                </c:forEach>
                <tr>
                    <td colspan="5">总计</td>
                    <td>¥${sum}</td>
                    <td><a href="${pageContext.request.contextPath}/bookInfo?op=clear">清空购物车</a></td>
                </tr>
            </table>
        </div>
    </body>
</html>

Ajax

Asynchronous Javascript And XML

异步JavaScript和XML

一种数据交互方式,请求和响应是异步的。

使用ajax能实现在整个页面不重新加载的情况下,更新局部内容。

使用

浏览器都是支持异步提交,原生的JavaScript就能实现ajax,但使用极不方便,所以都是使用jquery封装后的** . a j a x ( ) ∗ ∗ 或 .ajax()**或 .ajax().get() $.post()等函数。

1.在页面中导入jquery文件

<!--使用Ajax,需要导入jquery-->
<script src="jquery文件路径"></script>

2.在script标签中写ajax

<script>
	某个节点.事件(function(){
        //使用ajax异步提交数据
        $.ajax({
            //访问的URL地址
            url:"servlet映射或具体url"//提交的数据
            data:{
            	//键:值
            	"形参":,
            	"形参":},
            //提交方式
            type:"get/post/put/delete",
            //成功访问URL后的回调函数
            success:function(res){//res表示访问URL后返回的数据
            
        	},
            //访问URL失败时的回调函数
            error:function(){
                
            }
        });
    });
</script>

具体案例

servlet

package com.hqyj.bookShop.servlet;

import com.hqyj.bookShop.dao.CustomerDao;
import com.hqyj.bookShop.entity.Customer;
import com.hqyj.bookShop.util.Cart;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/customer")
public class CustomerServlet extends HttpServlet {

    CustomerDao dao = new CustomerDao();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String op = req.getParameter("op");
        switch (op) {
            case "login":
                //获取登录信息
                String phone = req.getParameter("phone");
                String password = req.getParameter("password");
                //调用登录
                Customer login = dao.login(phone, password);
                if (login != null) {
                    //将登录成功的对象,保存到session中
                    session.setAttribute("customer", login);
                    //使用输出流对象输出一个字符串"ok"
                    resp.getWriter().print("ok");
                } else {
                    //使用输出流对象输出一个字符串"ok"
                    resp.getWriter().print("error");
                }
                break;
        }
    }
}

页面

<html>
    <head></head>
    <body>
        <input type="text" name="phone"  placeholder="请输入手机号"/><br><br>
        <input type="password" name="password"  placeholder="请输入密码"/><br><br>
        <input id="submit" type="button" value="登录"/>
    </body>
    <script src="./../system/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script>
    <script>
        //如果是普通按钮,需要写单击事件
        $("#submit").click(function () {
            //获取输入的信息
            let phone = $("input[name=phone]").val();
            let password = $("input[name=password]").val();
            if (phone == "" || password == "") {
                alert("输入不能为空");
                return;
            }
            //使用ajax提交登录信息
            $.ajax({
                //请求地址
                url: "http://localhost:8080/Web03/customer",
                //提交方式
                type: "post",
                //提交数据
                data: {
                    "op":"login",
                    "phone": phone,
                    "password": password
                },
                //访问成功(能正常访问指定地址)后的回调函数。
                success: function (res) {
                    if(res=="error"){
                        alert("用户名或密码错误");
                    }
                    if(res=="ok") {
                        location.href="http://localhost:8080/Web03/bookInfo?op=search";
                    }
                }
            });
        });
        //如果是表单,需要写表单提交事件
        // $("form:eq(0)").submit();
    </script>
</html>

JSP内置对象

在jsp页面中有一些对象是已经定义好了可以直接使用的,称为内置对象。

一共有9个内置对象。

“rrppsoace”

  • request
    • 请求作用域对象
  • response
    • 响应对象
  • pageContext
    • 当前页作用域对象
  • session
    • 会话作用域对象
  • page
    • 当前jsp页面对象
  • out
    • 输出对象
  • application
    • 项目作用域对象
  • config
    • 配置对象
  • exception
    • 异常对象

Session和Cookie

这两个都是用于保存数据的对象。

session是一个作用域对象,在servlet中通过request.getSession()获取,在JSP中直接使用内置对象session获取。

cookie是一个对象,也是一个文件,保存在本地。

Cookie

cookie通常用于更长时间地保存一些信息,即便关闭浏览器,也能保存。

cookie的创建

//创建cookie
Cookie cookie = new Cookie("username", "保存在cookie中的用户名");
//设置有效时长,单位为秒,这里表示7天有效
cookie.setMaxAge(60*60*24*7);
//通过响应对象response保存cookie对象到本地
response.addCookie(cookie);

cookie的获取

//读取cookie时是获取当前站点的所有cookie数组
Cookie[] cks = request.getCookies();
//遍历
for(Cookie ck :cks){
    System.out.println( ck.getName()+"--"+ck.getValue());
}

Session

session是一个作用域对象,在访问任意jsp页面时,默认就会创建一个session对象(可以通过设置取消自动创建)。

通常使用session保存一些信息,用于在同一站点的各个页面之间共享数据。

原理:

  • 1.当访问的jsp页面或servlet中使用了session,会创建一个JSESSIONID(session编号),这是一个字符串,保存在一个cookie中。

    • 默认访问某个jsp页面时,该页面中没有使用session,也会自动创建session,因为

      <%--默认每个jsp页面都有这句话,表示访问该页面时,自动使用session--%>
      <%@ page session="true"%>
      

      如果将其设置为false,访问该jsp时则不会自动创建session。

  • 2.再次访问该页面时,会查询该JSESSIONID是否存在,如果存在,直接使用,如果不存在,创建新的JSESSIONID

  • 3.保存该JSESSIONID的cookie会随着浏览器的关闭自动销毁,所以关闭浏览器,session就会失效。

session对象的常用方法

常用方法作用
session.setAttribute(String str,Object obj)将obj对象保存在session中,命名为str
session.getAttribute(String str)获取保存在session中的对象
session.removeAttribute(String str)移除保存在session中的对象
session.invalidate()销毁session
session.getCreationTime()获取session创建时间对应的毫秒数
session.getId()获取JSESSIONID
session.getMaxInactiveInterval()获取session有效时长(默认1800秒)
session.setMaxInactiveInterval(int seconds)设置session有效时长,参数为秒
ServletContext app = session.getServletContext();获取当前application对象

设置全局session有效时长

在指定时间内,打开浏览器但对session无操作,就会自动销毁session。

  • 通过session.setMaxInactiveInterval(int seconds)设置有效秒数

  • 在web.xml中配置

    <!--设置全局session配置-->
    <session-config>
        <!--session有效时长,单位为分钟-->
        <session-timeout>15</session-timeout>
    </session-config>
    

Session和Cookie对比

  • session中保存的是对象Object,cookie中保存的是字符串String,都以键值对的形式保存
  • session保存在浏览器和服务器端,cookie保存在浏览器
  • session保存的数据没有大小限制,cookie保存的数据有大小限制,不超过3KB
  • session在30分钟内没有访问或随着浏览器的关闭而销毁,cookie可以设置销毁时间

监听器Listener

对于项目的某个操作进行监听,这个操作可以是创建或销毁application、session,发送请求、得到响应。

用于在执行某个操作时,通过监听器同时再执行其他操作,如记录日志、统计站点人数等。

常用的三个监听器接口

ServletContextListener		application监听器
HttpSessionListener			session监听器
ServletRequestListener		request监听器    

实现一个监听器

1.创建一个类,实现某个监听器接口

2.重写某个监听器接口中方法

  • 初始化的方法
  • 销毁的方法

3.在web.xml中配置监听器或通过注解配置

  • 如果在web.xml中配置

    <!--配置监听器-->
    <listener>
        <!--设置监听器的全限定名-->
        <listener-class>com.hqyj.bookShop.listener.MyListener</listener-class>
    </listener>
    
  • 如果通过注解配置,在自定义的监听器类上,加入@Web

package com.hqyj.bookShop.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/*
* 监听器
* 1.创建一个类,实现某个或多个监听器接口
* 2.重写方法
* 3.在该类上加入@WebListener注解或在web.xml中配置监听器
* */
@WebListener
public class MyListener implements ServletContextListener,HttpSessionListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("监听到项目初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("监听到项目销毁");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("监听到请求销毁");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("监听到发送请求");
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("监听到session创建");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("监听到session销毁");
    }
}

过滤器Filter

使用

1.创建一个类,继承HttpFilter

2.重写其中受保护的doFilter的方法

3.在web.xml中配置过滤器或使用注解配置

  • 在web.xml中配置的话

    <!--声明过滤器-->
    <filter>
        <filter-name>myFilter</filter-name>
        <filter-class>com.hqyj.filter.MyFilter</filter-class>
    </filter>
    <!--设置什么请求要经过该过滤器,通常过滤所有请求-->
    <filter-mapping>
        <filter-name>myFilter</filter-name>
        <!--/*表示过滤所有请求-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  • 使用@WebFilter(“/*”)注解配置的话

    package com.hqyj.filter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /*
     * 自定义一个过滤器
     * 1.继承HttpFilter
     * 2.重写受保护的doFilter方法
     * 3.web.xml中配置该过滤器
     * */
    //@WebFilter("/*")
    public class MyFilter extends HttpFilter {
        @Override
        protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
            //由于设置了过滤所有请求,所以在这里设置请求的编码格式
            req.setCharacterEncoding("utf-8");
            //获取请求的地址后,决定是否放行
            String uri = req.getRequestURI();
            System.out.println(uri+"试图访问");
            //判断地址中是否包含登录页或登录页相关的servlet映射或资源路径
            if (uri.contains("login")|| uri.contains("customer")||uri.contains("jquery")) {
                //允许放行
                chain.doFilter(req,res);
                return;
            }
            //如果登录成功,会在session中保存customer,所以在这里判断session中是否存在customer,如果存在,放行一切请求
            if(req.getSession().getAttribute("customer")==null){
                res.sendRedirect("http://localhost:8080/Web03/pages/login.html");
            }else{
                //允许放行
                chain.doFilter(req,res);
            }
        }
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值