java web project--learning through building 8

Tue, Jan 12, 2021 6-minute read

a full-stack web application for users to understand java web system

复习

请你谈谈网站是如何进行访问的

  • 输入一个域名;回车
  • 检查本机的 C:\Windows\System32\drivers\etc\hosts 配置文件下有没有这个域名映射
  • 有:直接返回对应的 ip 地址,这个地址中,有我们需要访问的 web 程序,可以直接访问

127.0.0.1 www.rileyshen.github

  • 没有:去 DNS 服务器找,找到的话就返回,找不到就返回找不到

ServletContext

一个 web 容器启动时,会为创建一个对应的 ServletContext 对象,它代表了当前 web 应用;

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    ServletContext servletContext = this.getServletContext();
    String username = "aa";
    servletContext.setAttribute("username",username);//将一个数据保存到了ServletContext中
     String username = (String)context.getAttribute("username");
    System.out.println(username);
    resp.setContentType("text/html");
    resp.setCharacterEncoding("utf-8");
    resp.getWriter().print(username);

Properties

 InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
    Properties prop = new Properties();
    prop.load(is);
    String username = prop.getProperty("username");
    String password = prop.getProperty("password");
    resp.getWriter().print(username+":"+password);

下载文件

  • 要获取下载文件的路径
  • 下载的文件名
  • 设置想办法让浏览器能够支持下载我们需要的东西
  • 获取下载文件的输入流
  • 创建缓冲区
  • 获取 OutputStream 对象
  • 将 FileOutputStream 流写入到 buffer 缓冲区
  • 使用 OutputStream 将缓冲区中的数据输出到客户端
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1. 要获取下载文件的路径
    String realPath = "D:\\javacode\\javaweb-02servlet\\response\\target\\classes\\1.png";
    System.out.println("下载文件的路径:"+realPath);
    // 2. 下载的文件名是啥?
    String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
    // 3. 设置想办法让浏览器能够支持(Content-Disposition)下载我们需要的东西,中文文件名URLEncoder.encode编码,否则有可能乱码
    resp.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
    // 4. 获取下载文件的输入流
    FileInputStream in = new FileInputStream(realPath);
    // 5. 创建缓冲区
    int len = 0;
    byte[] buffer = new byte[1024];
    // 6. 获取OutputStream对象
    ServletOutputStream out = resp.getOutputStream();
    // 7. 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端!
    while ((len=in.read(buffer))>0){
        out.write(buffer,0,len);
    }

    in.close();
    out.close();
}

4 大作用域

  • request:客户端向服务器发送请求,产生的数据,用户看完就没用了,比如:新闻,用户看完没用的!
  • session:客户端向服务器发送请求,产生的数据,用户用完一会还有用,比如:购物车;
  • application:客户端向服务器发送请求,产生的数据,一个用户用完了,其他用户还可能使用,比如:聊天数据;

监听器

编写一个监听器,实现监听器的接口

统计网站在线人数 : 统计 session

public class OnlineCountListener implements HttpSessionListener {
    //创建session监听: 看你的一举一动
    //一旦创建Session就会触发一次这个事件!
    public void sessionCreated(HttpSessionEvent se) {
        ServletContext ctx = se.getSession().getServletContext();
        System.out.println(se.getSession().getId());
        Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
        if (onlineCount == null) {
            onlineCount = new Integer(1);
        } else {
            int count = onlineCount.intValue();
            onlineCount = new Integer(count + 1);
        }
        ctx.setAttribute("OnlineCount", onlineCount);
    }

    //销毁session监听
    //一旦销毁Session就会触发一次这个事件!
    public void sessionDestroyed(HttpSessionEvent se) {
        ServletContext ctx = se.getSession().getServletContext();
        Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
        if (onlineCount == null) {
            onlineCount = new Integer(0);
        } else {
            int count = onlineCount.intValue();
            onlineCount = new Integer(count - 1);
        }
        ctx.setAttribute("OnlineCount", onlineCount);
    }
    /*
    Session销毁:
    1. 手动销毁  getSession().invalidate();
    2. 自动销毁  web.xml中通过配置来添加session过期时间
     */
}

Filter 实现拦截权限

用户登录之后才能进入主页!用户注销后就不能进入主页了

用户登录之后,向 Sesison 中放入用户的数据

进入主页的时候要判断用户是否已经登录;要求:在过滤器中实现!

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
   if (request.getSession().getAttribute(Constant.USER_SESSION)==null){
    response.sendRedirect("/error.jsp");
    }
   
    chain.doFilter(request,response);

JDBC

  1. 导入驱动 jar 包
  2. 注册驱动
// 功能:
// 	1.【注册驱动】 :告诉程序该使用哪一个数据库驱动jar包
// 	写代码使用Class.forName("com.mysql.jdbc.Driver")	
// ---------Driver源码-----------
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
        }
    }
------------------------------
// 【注意】:mysql5之后的驱动jar包可以省略注册驱动的步骤
//     2.【获取数据库连接】 
static Connection getConnection(String url,String user,String passward)
    // url:指定连接路径。语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
    // 例子:jdbc:mysql://localhost:3306/test

  1. 获取数据库连接对象 Connection
功能
	1.获取执行sql的对象
	statement createStatement()
	preparedStatement prepareStatement(String sql)
	2.管理事务
	开启事务 void setAutoCommit(boolean autocommit)
	提交事务 void commit()
	回滚事务 void rollback()
  1. 获取执行 sql 语句的对象 Statement
  2. 执行 sql,接受返回结果
    • SQL 注入问题:在拼接 sql 时,有一些 sql 的特殊关键字参与字符串的拼接,会造成安全性问题,PreparedStatement 可以防止该问题出现
// 预编译的SQL:参数使用?作为占位符
// e.g :
// ?用于参数标记
PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");
// 动态参数绑定
pstmt.setString(1,username);// 1指第一个?占位符
pstmt.setString(2,password);

  1. 处理结果
  2. 释放资源

Statement 接口提供了三种执行

Statement 接口提供了三种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定

druid(Alibaba)

step:
1.导入 jar 包 druid-1.0.9.jar
2.定义 properties 形式的配置文件,可以叫任意名称,放在任意目录
3.加载配置文件 properties
4.获取数据库连接池对象:通过工厂类来获取 DruidDataSourceFactory.createDataSource(properties)
5.获取连接 getconnection
6.归还连接 close

druid 工具类

   private  static DataSource dataSource;
    static {
        Properties properties = new Properties();
        try {
            InputStream is = DruidUtils.class.getResourceAsStream("/druid.properties");
            properties.load(is);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
// 获取连接对象
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return  null;
    }

DbUtils(Apache)

PreparedStatement 和 Statement 的区别

1.Connection 在获取 sql 的对象时
Statement createStatement()
PreparedStatement prepareStatement(String sql)
PreparedStatement 需要传入 sql 语句进行【预编译】,这样效率更高
2、PreparedStatement 能防止 SQL 注入

QueryRunner()(线程安全)

1.创建 QueryRunner 的实现类
2.使用其 update、query 方法
update(sql 语句,可变参数)
query(sql 语句,new BeanHandle<>(类名.class))
query(sql 语句,new BeanHandle<>(类名.class),可变参数)

ResultSetHandler 接口:转换类型接口

  • BeanHandler 类:实现类,把一条记录转换成对象
  • BeanListHandler 类:实现类,把多条记录转换成 List 集合
  • ScalarHandler 类:实现类,适合获取一行一列的数据

Dbutil 工具类

  • 定义一个类 JDBCUtils
  • 提供静态代码块加载配置文件,初始化连接池对象
  • 提供方法
    • 获取连接方法:通过数据库连接池获取连接
    • 释放资源
    • 获取连接池的方法
public class DBUtils {
    private static DruidDataSource dataSource;

    static {
        //1.加载配置文件
        Properties properties = new Properties();
        InputStream is = DBUtils.class.getResourceAsStream("/database.properties");
        try {
            properties.load(is);
            //2.获取DataSource
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 获取资源
     * @return
     * @throws SQLException
     */

    /**
     * 释放资源
     * @param conn
     * @throws SQLException
     */

     /**
     * 获取连接池方法
     */
    public static DataSource getDataSource(){
        return dataSource;
    }
}

Dbutil 方法

public class UserDaoImpl implements UserDao {
    //1.创建QueryRunner对象,并传递一个数据源对象
    private QueryRunner queryRunner = new QueryRunner(DBUtils.getDataSource());
    @Override
    public int insert(User user) {
        Object[] params={user.getId(),user.getUsername(),user.getPassword(),user.getSex(),user.getEmail(),user.getAddress()};
        try {
            return queryRunner.update("insert into user (id,username,password,sex,email,address) values(?,?,?,?,?,?)",params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int update(User user) {
        Object[] params={user.getUsername(),user.getPassword(),user.getSex(),user.getEmail(),user.getAddress(),user.getId()};
        try {
            return queryRunner.update("update user set username=?,password=?,sex=?,email=?,address=? where id = ?",params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int delete(int id) {
        try {
            return queryRunner.update("delete from user where id = ?",id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public User select(int id) {
        try {
            //把查询到的记录封装成 指定对象
            return queryRunner.query("select * from user where id = ?", new BeanHandler<User>(User.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查询所有
     * @return
     */
    @Override
    public List<User> selectAll() {
        try {
            return queryRunner.query("select * from user;",new BeanListHandler<User>(User.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

用 preparedStatement

Connection conn = null;
        PreparedStatement preparedStatement = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "insert into user(username,password) values(?,?)";
            preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setString(1,"admin");
            preparedStatement.setString(2,"admin");

            int i = preparedStatement.executeUpdate();
            System.out.println(i);


        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            JDBCUtils.close(preparedStatement,conn);
        }
    }

jdbc 固定步骤

  • 加载驱动
  • 链接数据库
  • 向数据库发送 SQL 对象 Statement:CRUD
  • 编写 sql
  • 执行 SQL
  • 关闭连接
// 配置信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8";
String name = "root";
String password = "root";
// 1/ 加载驱动
Class.forName("com.mysql.ct.jdbc.Driver");
// 2. 链接数据库
Connection conn = DirverManager.getConnction(url, username, password);
// 3. 向数据库发送 SQL 对象 Statement:CRUD
Statement statement= connection.createStatement();
// 4. 编写 sql
String sql = "select * from users";
// 5. 执行 SQL
ResultSet resultset= statement.executeQuery(sql);
while (resultset.next()) {
    resultSet.getObject("id");
    resultSet.getObject("name");
}
// 6. 关闭连接
resultSet.close();
statement.close();
conn.close();

用 preparedStatement

// 配置信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8";
String name = "root";
String password = "root";
// 1/ 加载驱动
Class.forName("com.mysql.ct.jdbc.Driver");
// 2. 链接数据库
Connection conn = DirverManager.getConnction(url, username, password);
// // 3. 向数据库发送 SQL 对象 Statement:CRUD
// Statement statement= connection.createStatement();
// 3. 编写 sql
String sql = "insert into users(id, name, password, email, birthday) values(?, ?, ?, ?, ?)";
// 4. preparestatement
PreparedStatement preparedStatement= connection.preparedStatement(sql);
preparedStatement.setInt(1, 4);
preparedStatement.setInt(2, "aa");
preparedStatement.setInt(3, "123456");
preparedStatement.setInt(4, "123456@qq.com");
preparedStatement.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
// 5. 执行 SQL
int i = preparedStatement.executeUpdate();
if (i > 0) {
   sout("success");
}
// 6. 关闭连接

preparedStatement.close();
conn.close();

spring JDBC :JDBC template

Spring 框架对 JDBC 的简单封装。提供了一个 JDBCTemplate 对象简化 JDBC 的开发

步骤

导入 jar 包
创建 JdbcTemplate 对象。依赖于数据源 DataSource
JdbcTemplate template = new JdbcTemplate(ds);
调用 JdbcTemplate 的方法来完成 CRUD 的操作

  1. update():执行 DML 语句。增、删、改语句
  1. queryForMap():查询结果将结果集封装为 map 集合,将列名作为 key,将值作为 value 将这条记录封装为一个 map 集合

3.queryForList():查询结果将结果集封装为 list 集合

4.query():查询结果,将结果封装为 JavaBean 对象

  1. queryForObject:查询结果,将结果封装为对象

jdbcTemplate and preparedstatements

statement 在执行一个查询的时候,数据库将首先查询字符串,然后在执行之前进行解析,编译以及计算执行计划, 如果多次执行相同的查询,那么该预处理步骤就会成为一个性能瓶颈,

而如果使用 preparedstatements ,则该预处理不走仅被执行一次,因此,会减少执行时间,相比于每次动态创建查询字符串,

使用 preparedstatements 的另外一个好处就是保护系统免受 sql 注入供给,

preparedstatements 执行的查询使用起来更安全

jdbcTemplate 对 preparedstatements 提供了方法 主要是将普通的参数改为 PreparedStatementSetter 来设置传入的参数 方法如下

query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper)
  List<User> userList = jdbcTemplate.query("select * from user where id = ?", preparedStatement -> preparedStatement.setInt(1,id), new BeanPropertyRowMapper<>(User.class));
query(PreparedStatementCreator psc, RowMapper<T> rowMapper)

public voidetValues(PreparedStatement ps, int i) throws SQLException{
User user = (User) users.get(i);
ps.setString(1, user.getName());
ps.setInt(2, user.getAge().intValue());
}

public int getBatchSize() {
return users.size();

DataSource

DataSource 用来取代 DriverManager 来获取 Connection,获取速度快,同时可以大幅度提高数据库访问速度。

特别注意:

数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。

当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但 conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。