java学习笔记——javaweb之Filter 与 ThreadLocal

1、Filter

1.1、什么是Filter?

  1. 首先Filter是一个接口。
  2. Filter是java Web三大组件之一。   javaWeb三大组件分别是:Servlet小程序、Filter过滤器、Listener监听器
  3. Filter是服务器专门用来过滤请求,拦截响应的。

Filter的常见作用:

  1. 检查用户访问权限。
  2. 设置请求响应编码,解决乱码问题。

1.2、Filter的初体验

需求:

现在在WebContent目录下有一个目录admin。这个目录是管理员操作的目录。这个目录里有jsp文件,有html文件,还有图片资源文件。现在我们要让这些资源都在用户登录才能被访问。那么我们要怎么实现这样的需求。

 

思路:

前面我们讲过Session。有同学可能会想,我们可以在用户登录之后。把用户的信息保存在Session域对象中。然后在jsp页面里通过Session域对象获取用户的信息,如果用户信息存在,说明用户已登录。否则就重定向到登录页面。这个方案可行。可是html页面呢? html页面是没有Session域对象的。

解决方案:

这就需要我们使用Filter过滤器来进行请求的拦截。然后判断Session域对象中是否包含用户的信息。

现在我们以admin目录下user.jsp为例进行讲解。

 

  1. 首先,我们需要创建一个类来实现Filter接口,用来检查Session中是否包含用户信息。
  2. 实现Filter中的doFilter方法
  3. 然后到web.xml文件中去配置Filter的过滤信息。
  4. 然后重启服务器访问测试

 

 

1)Filter1的类代码:

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

public class Filter1 implements Filter {

 

    /**

     * Filter初始化方法

     */

    public void init(FilterConfig filterConfig) throws ServletException {

 

    }

 

    /**

     * Filter的过滤方法

     */

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        // 强转

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 获取用户登录信息

        String username = (String) httpRequest.getSession().getAttribute("username");

        if (username != null) {

            // 过滤器中,只要允许用户访问资源,一定要调用chain.doFilter方法,否则用户永远访问不到资源

            chain.doFilter(request, response);

        } else {

            // 如果用户未登录。返回登录页面

            httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.jsp");

        }

    }

 

    /**

     * Filter销毁的方法

     */

    public void destroy() {

    }

 

}

 

 

2)web.xml文件中的Filter配置

    <!-- 配置Filter1 -->

    <filter>

        <!-- 给Filter1起一个名字 -->

        <filter-name>Filter1</filter-name>

        <!-- 是哪一个Filter类 -->

        <filter-class>com.atguigu.filter.Filter1</filter-class>

    </filter>

    <filter-mapping>

        <!-- Filter的名字 -->

        <filter-name>Filter1</filter-name>

        <!-- Filter1的过滤地址

            表示过滤http://127.0.0.1:8080/day17/admin/user.jsp

         -->

        <url-pattern>/admin/user.jsp</url-pattern>

    </filter-mapping>

 

 

1.3、Filter的生命周期

Servlet的生命周期

  1. 先执行构造方法
  2. 执行init方法做初始化操作
  3. 执行Service方法
  4. 销毁的时候调用destory方法

 

 

Filter生命周期:

  1. 先执行Filter的构造方法
  2. 然后执行Filter的init方法
  3. 执行Filter的doFilter方法,每次访问资源,只要匹配过滤的地址,就会调用。
  4. 执行Filter的destroy方法

 

创建一个Filter2类。代码如下:

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

 

public class Filter2 implements Filter {

 

    public Filter2() {

        System.out.println("Filter2 构造 方法 被调用");

    }

   

    /**

     * Filter初始化方法

     */

    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter2 init 方法被调用。初始化……");

    }

 

    /**

     * Filter的过滤方法

     */

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        System.out.println("Filter2 doFilter 方法被调用  ");

        // 一定要调用此方法,否则用户访问的资源会访问不到。

        chain.doFilter(request, response);

    }

 

    /**

     * Filter销毁的方法

     */

    public void destroy() {

        System.out.println("Filter2 的destroy方法被调用……");

    }

 

}

 

 

web.xml文件中的配置

 

    <!-- 配置Filter2 -->

    <filter>

        <!-- 给Filter2起一个名字 -->

        <filter-name>Filter2</filter-name>

        <!-- 是哪一个Filter类 -->

        <filter-class>com.atguigu.filter.Filter2</filter-class>

    </filter>

    <filter-mapping>

        <!-- Filter的名字 -->

        <filter-name>Filter2</filter-name>

        <!-- Filter1的过滤地址

            表示过滤http://127.0.0.1:8080/day17/login.jsp

         -->

        <url-pattern>/login.jsp</url-pattern>

    </filter-mapping>

 

 

 

然后打开浏览器访问 http://127.0.0.1:8080/day17/login.jsp。

查看整个控制台的打印如下:

1)Filter在工程启动的时候初始化。

2)在访问过滤的时候调用doFilter方法

3)Tomcat关闭Filter被销毁的时候调用destory方法

 

1.4、FilterConfig类

 

作用:FilterConfig类和ServletConfig类是一样的。可以获取Filter在web.xml文件中的配置信息,做初始化之用。

我们可以在web.xml文件中给Filter添加初始化参数。然后在init初始化方法中使用FilterConfig类获取到初始化的参数。

 

FilterConfig类,一般有三个作用:

  1. 获取Filter在web.xml文件中配置的名称
  2. 获取Filter在web.xml文件中配置的初始化参数
  3. 通过FilterConfig类获取ServletContext对象实例

 

1.4.1、修改Filter2在web.xml中的配置信息

    <!-- 配置Filter2 -->

    <filter>

        <!-- 给Filter2起一个名字 -->

        <filter-name>Filter2</filter-name>

        <!-- 是哪一个Filter类 -->

        <filter-class>com.atguigu.filter.Filter2</filter-class>

        <!-- 配置初始化参数 -->

        <init-param>

            <!-- 初始化参数的名称 -->

            <param-name>username</param-name>

            <!-- 初始化参数的值 -->

            <param-value>root</param-value>

        </init-param>

    </filter>

 

1.4.2、修改Filter2中init方法的代码如下:

    /**

     * Filter初始化方法

     */

    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter2 init 方法被调用。初始化……");

        // 获取Filter的名称

        String filterName = filterConfig.getFilterName();

        System.out.println("Filter name ==>>> " + filterName);

        // 获取初始化参数。username的值

        String username = filterConfig.getInitParameter("username");

        System.out.println("username ==>> " + username);

        // 获取ServletContext的对象实例

        ServletContext ctx = filterConfig.getServletContext();

        System.out.println(ctx);

    }

 

1.4.3、然后重启Tomcat服务器,控制台打印如下:

1.5FilterChain 过滤器链(重点****)

FilterChain是整个Filter过滤器的调用者。Filter与Filter之间的传递,或者Filter与请求资源之间的传递都靠FilterChain.doFilter方法。

一般Filter.doFilter中的代码分为三段。

第一段是FilterChain.doFilter之前的代码。一般用来做请求的拦截,检查用户访问的权限,访问日记的记录。参数编码的设置等等操作。

第二段是FilterChain.doFilter方法。此方法可以将代码的执行传递到下一个Filter中。或者是传递到用户最终访问的资源中。

第三段是FilterChain.doFilter之后的代码。主要用过做一些日记操作。我们很少会在第三段中做太多复杂的操作。

 

在每一个Filter类的doFilter方法中,一定要调用chain.doFilter方法,除非你想要阻止用户继续往下面访问。否则一定要调用FilterChain的doFilter方法。

 

1.5.1、图解:多个Filter过滤器的代码流转

 

1.5.2、现在我们添加两个Filter类。对同一个资源进行过滤。

第一个Filter类ChainFilter1 代码:

 

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

 

public class ChainFilter1 implements Filter {

 

    public void init(FilterConfig filterConfig) throws ServletException {

    }

 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        System.out.println("资源访问前---ChainFilter1 -- 开始执行");

        // 转发下一个Filter或者请求的资源

        chain.doFilter(request, response);

        System.out.println("资源访问后---ChainFilter1 -- 执行结束");

    }

 

    public void destroy() {

    }

 

}

 

 

第二个Filter类ChainFilter2 代码:

 

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

 

public class ChainFilter2 implements Filter {

 

    public void init(FilterConfig filterConfig) throws ServletException {

    }

 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        System.out.println("资源访问前---ChainFilter2 -- 开始执行");

        // 转发下一个Filter或者请求的资源

        chain.doFilter(request, response);

        System.out.println("资源访问后---ChainFilter2 -- 执行结束");

    }

 

    public void destroy() {

    }

 

}

 

 

1.5.3、他们在web.xml文件中的配置如下:

    <filter>

        <filter-name>ChainFilter1</filter-name>

        <filter-class>com.atguigu.filter.ChainFilter1</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>ChainFilter1</filter-name>

        <url-pattern>/chainFilter.jsp</url-pattern>

    </filter-mapping>

    <filter>

        <filter-name>ChainFilter2</filter-name>

        <filter-class>com.atguigu.filter.ChainFilter2</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>ChainFilter2</filter-name>

        <url-pattern>/chainFilter.jsp</url-pattern>

    </filter-mapping>

 

1.5.4、WebContent/chainFilter.jsp文件的内容如下:

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

    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

    <head>

        <meta http-equiv="pragma" content="no-cache" />

        <meta http-equiv="cache-control" content="no-cache" />

        <meta http-equiv="Expires" content="0" />

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <title>Insert title here</title>

    </head>

    <body>

        <%

            System.out.println("这是请求资源的代码");

        %>

        这是ChainFilter.jsp

    </body>

</html>

 

1.5.5、打开浏览器输入http://127.0.0.1:8080/day17/chainFilter.jsp回车访问:

 

 

千万要注意:Filter类的doFilter方法中,除非你要拦截请求的资源,否则一定要调用FilterChain参数的doFilter方法让代码的执行传递到下一个Filter或访问的资源中

1.6、Filter的拦截路径

精确匹配 比如: /xxx/xxx/xxx.jsp 或 /xxx/xxx/xxx.html 等

目录匹配 比如:

/abc/* 表示可以拦截abc目录下的所有资源,甚至是abc目录下的其他目录,

/* 表示访问 当前工程下所有资源

后缀名匹配 比如:*.jsp 表示拦截所有后缀为jsp文件资源

 

 

精确匹配前面 ,我们已经演示过了。

下面我们以目录匹配为示例展示代码。大家可以在此基础上。修改web.xml文件中的<url-pattern>标签来测试自己想要的路径

 

1)Filter的代码如下:

 

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

 

public class FilterPath implements Filter {

 

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

    }

 

    @Override

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        System.out.println("filter path 执行了");

        // 将代码执行传递到下一个Filter或者是请求资源

        chain.doFilter(request, response);

    }

 

    @Override

    public void destroy() {

 

    }

 

}

2)web.xml文件中的配置内容:

 

    <filter>

        <filter-name>FilterPath</filter-name>

        <filter-class>com.atguigu.filter.FilterPath</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>FilterPath</filter-name>

        <url-pattern>/admin/*</url-pattern>

    </filter-mapping>

2、练习

1、使用Filter来拦截/pages/manager/目录下的所有页面。因为这些内容都属于管理员模块。

2、使用Filter和ThreadLocal组合来控制事务

2.1、使用Filter对/pages/manager/下的内容进行权限管理。

 

根据咱们今天讲的Filter的知识点,我们只需要,在登录的时候,把用户的信息保存到Session对象中,

然后创建一个Filter类去检查Session对象中用户的登录信息。然后再去web.xml文件中去配置拦截的地址为/pages/manager/*即可。

 

 

1)修改UserServlet中登录的代码
       

/**

     * 登录的操作

     *

     * @param request

     * @param response

     * @throws IOException

     * @throws ServletException

     */

    public void login(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

 

        User t = new User();

        Utils.copyMap2Bean(request.getParameterMap(), t);

 

        // 根据用户名密码根据系统

        User user = userService.loginByUsernameAndPassword(t);

        // 如果user不为null,说明登录成功!

        if (user != null) {

            // 把用户添加到Session对象中

            request.getSession().setAttribute("user", user);

 

            System.out.println("[" + user.getUsername() + "]用户登录成功!跳转去登录成功页面login_success.jsp");

            // 登录成功之后。转发到登录成功 页面

            request.getRequestDispatcher("/pages/user/login_success.jsp")

                    .forward(request, response);

        } else {

            // 如果user == null ,说明登录失败,用户名错误,或者密码错误。

            request.setAttribute("msg", "登录失败,用户名或密码错误!");

            request.setAttribute("username", t.getUsername());

            System.out.println("[" + t.getUsername() + "]用户登录失败!跳转去登录页面login.jsp");

            request.getRequestDispatcher("/pages/user/login.jsp").forward(request, response);

        }

    }

2)

  1. 创建ManagerFilter过滤器类进行拦截的代码:

 

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import com.atguigu.bean.User;

 

public class ManagerFilter implements Filter {

 

    public void init(FilterConfig filterConfig) throws ServletException { }

 

    @Override

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        //强转request为HttpServletRequest和response为HttpServletResponse

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        HttpServletResponse httpResponse = (HttpServletResponse) response;

       

        // 获取Session中的用户信息

        User user = (User) httpRequest.getSession().getAttribute("user");

        // 用户不为空,说明已登录

        if (user != null) {

            // 用户已登录,就继续访问资源

            chain.doFilter(request, response);

        } else {

            // 没有登录就重定向到登录页面

            httpResponse.sendRedirect(httpRequest.getContextPath() + "/pages/user/login.jsp");

        }

       

    }

 

    public void destroy() {  }

 

}

 

 

3)web.xml文件中的配置:

 

    <filter>

        <filter-name>managerFilter</filter-name>

        <filter-class>com.atguigu.filter.ManagerFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>managerFilter</filter-name>

        <!-- 拦截pages/manager/下所有的内容 -->

        <url-pattern>/pages/manager/*</url-pattern>      

    </filter-mapping>

 

2.2、使用Filter和ThreadLocal组合来控制事务

两个问题?

  1. 为什么要使用Filter来控制事务。
  2. ThreadLocal有什么用?

 

我们先来看第一个问题。我们以去结账生成订单为例。在结账生成订单的时候,我们要插入订单的信息,还要插入订单项的信息。还有修改图书的库存信息。这一系列的操作要么应该都成功 。要么应该都失败。比如说,当我们插入订单信息,成功之后,插入订单项之前抛了一个异常,而使得插入订单项,修改图书库存信息这一系列的操作都无法执行。那么数据库里就只有订单的信息,而找不到这个订单购买了什么商品。这种情况下,我们就需要使用数据库的事务来管理这一系列的数据库操作。当所有数据库的操作都成功之后 。我们再手动的提交事务。来确保这些操作的原子性。

 

现在我们来看看第二个问题。ThreadLocal类可以让我们在每个线程中存取当前线程使用的Connection对象,这样在下次获取的时候,就是同一个Connection连接

 

ThreadLocal的使用

ThreadLocal使用,我们只需要记住三点:

一:ThreadLocal类可以让我们以当前线程为key保存一个线程内部变量。而不必担心线程安全问题。

二:ThreadLocal类一般是和static静态关键字组合使用。

三:ThreadLocal类存储的变量在线程销毁后。会被虚拟机自动gc,释放。

 

我们先来看一下。在线程里保存变量,然后在线程中取自己保存的变量的情况

 

1)map来实现线程保存变量:

package com.atguigu.threadlocal;

 

import java.util.HashMap;

import java.util.Map;

import java.util.Random;

 

public class TestThreadLocal1 {

 

    // 定义一个整型

    private static Map<String, Integer> map = new HashMap<String, Integer>();

    // 随机数对象

    private static Random random = new Random(System.currentTimeMillis());

 

    static class MyTask implements Runnable {

 

        Map<String, Integer> map;

 

        public MyTask(Map<String, Integer> map) {

            super();

            this.map = map;

        }

 

        public void run() {

            System.out.println(Thread.currentThread().getName() + " -- begin");

 

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            // 生成随机数,用于相加

            int num = random.nextInt(1000);

            //

            System.out.println(Thread.currentThread().getName() + "生成一个随机数:" + num);

            // 把随机数保存到map中

            map.put(Thread.currentThread().getName(), num);

            // 用线程做key获取自己的变量

            System.out.println("获取" + Thread.currentThread().getName() + " -- "

                    + map.get(Thread.currentThread().getName()));

            System.out.println(Thread.currentThread().getName() + " -- end");

        }

    }

 

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

        Thread t1 = new Thread(new MyTask(map));

        Thread t2 = new Thread(new MyTask(map));

        t1.start();

        t2.start();

    }

 

}

 

 

2)ThreadLocal实现线程保存变量

 

package com.atguigu.threadlocal;

 

import java.util.Random;

 

public class TestThreadLocal2 {

 

    // 定义一个整型

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    // 随机数对象

    private static Random random = new Random(System.currentTimeMillis());

 

    static class MyTask implements Runnable {

 

        ThreadLocal<Integer> i;

 

        public MyTask(ThreadLocal<Integer> i) {

            this.i = i;

        }

 

        public void run() {

            System.out.println(Thread.currentThread().getName() + " -- begin");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            // 生成随机数,用于相加

            int num = random.nextInt(1000);

            //

            System.out.println(Thread.currentThread().getName() + "生成一个随机数:" + num);

            // 把随机数保存到map中

            i.set(num);

            // 用线程做key获取自己的变量

            System.out.println("获取" + Thread.currentThread().getName() + " -- " + i.get());

            System.out.println(Thread.currentThread().getName() + " -- end");

        }

    }

 

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

        Thread t1 = new Thread(new MyTask(threadLocal));

        Thread t2 = new Thread(new MyTask(threadLocal));

        t1.start();

        t2.start();

    }

 

}

 

 

经过上面两个小示例的代码,我们可以知道,在多个线程里我们可以使用ThreadLocal保存线程自己需要的变量,而不需要担心线程安全的问题。所以我们只可以使用ThreadLocal来保存数据库的连接,这样,在一次请求中。是一个线程处理所有的操作。这样可以保正一个请求,使用相同的Connection对象。可以确保多个操作在一个连接的一个事务中完成。来达到。要么都成功 。要么都失败的效果。

 

 

使用Filter和ThreadLocal组合管理事务

以生成订单为例。来展示如何使用Filter和ThreadLocal管理事务

1)先修改JDBCUtils中代码。使用ThreadLocal管理Connection对象

package com.atguigu.util;

 

import java.sql.Connection;

import java.sql.SQLException;

 

import com.mchange.v2.c3p0.ComboPooledDataSource;

 

/**

 * 获取数据库连接的工具类

 *

 * @author wzg

 *

 */

public class JDBCUtils {

 

    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("book_devoloper");

    /**

     * 使用ThreadLocal保存Connection对象

     */

    private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();

 

    private JDBCUtils() {

    }

 

    /**

     * 获取数据库连接

     *

     * @return 如果获取连接成功,返回数据的连接对象。<br/>

     *         如果获取数据库连接失败,则返回null

     */

    public static Connection getConnection() {

        // 先从ThreadLocal中获取

        Connection connection = connectionThreadLocal.get();

        try {

            if (connection == null) {

                // 从c3p0中获取数据库连接

                connection = dataSource.getConnection();

                // 设置事务为手动提交

                connection.setAutoCommit(false);

                connectionThreadLocal.set(connection);

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

        return connection;

    }

 

    /**

     * 释放数据库连接

     */

    public static void closeConnection(Connection conn) {

        if (conn != null) {

            try {

                conn.close();

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

 

    /**

     * 释放数据库连接

     */

    public static void closeConnection() {

        // 从线程ThreadLocal中获取

        Connection conn = connectionThreadLocal.get();

        if (conn != null) {

            try {

                // 事务提交

                conn.commit();

                // 事务关闭

                conn.close();

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

        // 移除

        connectionThreadLocal.remove();

    }

 

    public static void rollback() {

        // 从线程ThreadLocal中获取

        Connection conn = connectionThreadLocal.get();

        if (conn != null) {

            try {

                // 事务回滚

                conn.rollback();

                // 关闭连接

                conn.close();

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

        // 移除

        connectionThreadLocal.remove();

    }

 

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {

            System.out.println(getConnection());

        }

    }

}

 

 

2)然后修改BaseDaoImpl的代码

记住,在BaseDaoImpl里,都不要再关闭Connection,因为要让所有的操作都在同一个Connection中使用,而且所有的异常都要往外抛。直到让Filter可以拦截到,做rollback回滚操作。

 

 

package com.atguigu.dao.impl;

 

import java.lang.reflect.ParameterizedType;

import java.sql.Connection;

import java.sql.SQLException;

import java.util.List;

 

import org.apache.commons.dbutils.QueryRunner;

import org.apache.commons.dbutils.handlers.BeanHandler;

import org.apache.commons.dbutils.handlers.BeanListHandler;

import org.apache.commons.dbutils.handlers.ScalarHandler;

 

import com.atguigu.util.JDBCUtils;

 

/**

 * 我们使用的是DBUtil操作数据库

 *

 * @author wzg

 *

 * @param <T>

 */

public abstract class BaseDaoImpl<T> {

 

    /**

     * DBUtils操作数据库需要使用的类

     */

    protected QueryRunner queryRunner;

    /**

     * 保存BaseDaoImpl类的泛型对象class类型

     */

    protected Class<T> type;

 

    @SuppressWarnings("unchecked")

    public BaseDaoImpl() {

        // 创建一个QueryRunner对象实例

        queryRunner = new QueryRunner();

        // 获取父类中带有泛型的父类的class类型

        ParameterizedType supperClass = (ParameterizedType) getClass().getGenericSuperclass();

        // 获取泛型中的具体的类型的class

        type = (Class<T>) supperClass.getActualTypeArguments()[0];

    }

 

    /**

     * 执行update,delete,insert语句

     *

     * @param sql

     *            要执行的sql语句

     * @param params

     *            执行的sql语句的参数

     * @return 如果语句执行成功返回true<br/>

     *         如果执行失败返回false

     * @throws SQLException

     */

    public int update(String sql, Object... params)  {

        Connection connection = null;

        int updateCount = 0;

        try {

            connection = JDBCUtils.getConnection();

            updateCount = queryRunner.update(connection, sql, params);

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

        return updateCount;

    }

 

    /**

     * 执行查询语句,只返回第一个记录

     *

     * @param sql

     *            要执行的sql语句

     * @param params

     *            执行的sql语句的参数

     * @return 返回查询对象的具体实例 <br/>

     *         如果查询的数据不存在,则返回null<br/>

     *         查询失败也返回null

     * @throws SQLException

     */

    public T queryOne(String sql, Object... params) {

        Connection connection = null;

        try {

            // 获取数据库连接

            connection = JDBCUtils.getConnection();

            // 执行查询语句

            return queryRunner.query(connection, sql, new BeanHandler<T>(type), params);

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

    }

 

    /**

     * 执行查询语句,返回查询后的对象实例集合

     *

     * @param sql

     *            要执行的sql语句

     * @param params

     *            sql语句的参数

     * @return 返回的是查找到的对象集合<br/>

     *         查询失败返回null

     * @throws Exception

     */

    public List<T> queryList(String sql, Object... params) {

        Connection connection = null;

 

        try {

            // 获取数据库连接

            connection = JDBCUtils.getConnection();

            // 执行查询语句

            return queryRunner.query(connection, sql, new BeanListHandler<T>(type), params);

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

    }

 

    /**

     * 执行批量操作

     *

     * @param sql

     *            要执行的sql语句

     * @param params

     *            参数

     * @return 返回每个语句修改的数量

     * @throws Exception

     */

    public int[] batch(String sql, Object[][] params) {

        Connection connection = null;

 

        try {

            // 获取数据库连接

            connection = JDBCUtils.getConnection();

            // 执行查询语句

            return queryRunner.batch(connection, sql, params);

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

    }

 

    /**

     * 查询只有一个返回值的语句

     *

     * @param sql

     * @param params

     * @return

     * @throws Exception

     */

    public Object querySingleValue(String sql, Object... params) {

        Connection connection = null;

 

        try {

            // 获取数据库连接

            connection = JDBCUtils.getConnection();

            // 执行查询语句

            return queryRunner.query(connection, sql, new ScalarHandler(), params);

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

    }

 

}

 

 

 

3)修改BaseServlet的方法调用,代码如下 :

在下面的代码中,一定要把所有的异常,都使用RuntimeException包装起来往外抛。这样Filter里才能收到异常,否则异常只会到BaseServlet就会终止,而不会继续往外抛

 

    @Override

    protected void doPost(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

       

        // 解决post乱码问题,设置请求的参数字符集为UTf-8

        request.setCharacterEncoding("UTF-8");

        // action参数就可以用来判断是当前用户模块的哪一个操作。

        String action = request.getParameter("action");

        // 定义一个Method方法对象

        Method actionMethod;

        try {

            // 通过反射获取到与action字符串对应的操作方法

            actionMethod = getClass().getDeclaredMethod(action, HttpServletRequest.class,

                    HttpServletResponse.class);

            // System.out.println(actionMethod);

            // 调用Method对象的invoke函数执行方法

            actionMethod.invoke(this, request, response);

        } catch (NoSuchMethodException | SecurityException | IllegalAccessException

                | IllegalArgumentException | InvocationTargetException e) {

            e.printStackTrace();

            throw new RuntimeException(e);

        }

       

    }

 

 

4)添加TransactionFilter类管理事务

package com.atguigu.filter;

 

import java.io.IOException;

 

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

 

import com.atguigu.util.JDBCUtils;

 

/**

 * TrasactionFilter类用于管理事务

 */

public class TransactionFilter implements Filter {

 

    public TransactionFilter() {

    }

 

    public void destroy() {

    }

 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

        try {

            // 放行操作

            chain.doFilter(request, response);

            // 关闭连接,并提交事务

            JDBCUtils.closeConnection();

        } catch (Exception e) {

            // 回滚 事务

            JDBCUtils.rollback();

            throw e;

        }

    }

 

    public void init(FilterConfig fConfig) throws ServletException {

    }

 

}

 

 

5)在web.xml文件中的配置

  <filter>

    <filter-name>TransactionFilter</filter-name>

    <filter-class>com.atguigu.filter.TransactionFilter</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>TransactionFilter</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>

  <!-- 配置500错误页面 -->

  <error-page>

    <error-code>500</error-code>

    <location>/error500.jsp</location>

  </error-page>

 

 

6)error500.jsp页面中的错误提示

一般每个项目都有自己错误提示页面。等在实际项目中,只需要把这个内容改成你公司提示的页面即可。

 

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

    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

    <head>

        <meta http-equiv="pragma" content="no-cache" />

        <meta http-equiv="cache-control" content="no-cache" />

        <meta http-equiv="Expires" content="0" />

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <title>Insert title here</title>

    </head>

    <body>

        系统出现故障,我们马上为您解决。请耐心等待!<a href="${pageContext.request.contextPath }">返回首页</a>

    </body>

</html>

猜你喜欢

转载自blog.csdn.net/qq_25106373/article/details/81289964
今日推荐