编码
Filter可以用于提前设置请求的编码
若写定了UTF-8则略显僵硬,可以配置initParams,然后在init()中先读取编码方式,交给成员变量,再让方法直接拿来用
@WebFilter(urlPatterns = {"*.do"},initParams = {
@WebInitParam(name="encoding",value="UTF-8")
})
public class EncodingFilter implements Filter {
private String encoding = "UTF-8"; //默认为utf-8
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String encodingStr = filterConfig.getInitParameter("encoding");
if(!StringUtils.isEmpty(encodingStr)) {
encoding = encodingStr;
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
((HttpServletRequest)servletRequest).setCharacterEncoding(encoding);
filterChain.doFilter(servletRequest,servletResponse); //放行
}
@Override
public void destroy() {
}
}
如此一来,就实现了在DS拦截请求之前,先由Filter负责对请求进行编码的过程,保证了信息存入数据库的正确性
保证事务原子性
当前体系下,一个Service(服务)中包含了多个DAO操作,而这个服务本身也应该是要求原子性的,所以,对于事务的管理应该放到Service层去
所以,代码结构应该是:
try{
conn.setAutoCommit(false); //取消自动提交
Service();
conn.commit();
}catch(Exception e){
conn.rollback();
}
将这个操作也放到过滤器去执行,代码结构为:
try{
conn.setAutoCommit(false);
filterChain.doFilter(req, res); //放行,捕捉内部所有Exception
conn.commit();
}catch(Exception e){
conn.rollback();
}
那么,这个过滤器就是OpenSessionInViewFilter
首先,确认所有DAO中都没有对conn进行close(),避免事务执行完毕前被close
接下来,为了方便对conn进行commit、rollback等控制,在第一篇文章就建立的TransactionManager中封装要对conn进行的操作:
public class TransactionManager {
public static void beginTrans() throws SQLException { //开始事务
ConnUtil.getConn().setAutoCommit(false);
}
public static void commitTrans() throws SQLException { //提交事务
ConnUtil.getConn().commit(); //提交事务
ConnUtil.close(); //关闭连接
}
public static void rollbackTrans() throws SQLException { //回滚事务
ConnUtil.getConn().rollback();
ConnUtil.close();
}
}
然后,在OpenSessionInViewFilter类的doFilter()方法中按照上文提到的代码结构来编写事务操作:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {
try{ //捕获事务进行中发生的Exception
//开始事务
TransactionManager.beginTrans();
//放行
filterChain.doFilter(servletRequest,servletResponse);
//一切正常,提交
TransactionManager.commitTrans();
} catch (Exception e) { //注意这里是捕获所有类型的异常
//捕获到错误,打印信息后回滚
e.printStackTrace();
try {
TransactionManager.rollbackTrans();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
要保证事务的原子性,实际上就是让处于Filter中的try{}结构捕获到异常,这样一来就能执行回滚了
但这里存在一个问题:当DAO、Service、Controller、DS中已经存在try{}catch{}结构但未向外抛出异常,则异常不会被最顶层的Filter捕获,而是在下层中就被处理掉
因此,还需要对每一层的Exception
进行处理并抛出的操作,层层上抛,最后得以被Filter捕获。为了便于提示、区分出错层级,可以新建一个异常类,以DAO层为例:
public class DAOException extends RuntimeException {
public DAOException(String mes) {
throw super(mes); //构造一个RuntimeException并将之抛出
}
}
在BaseDAO中,若catch到了异常,则在打印错误信息后向上抛出该错误
try{
//...
}catch(Exception e){
e.printStackTrace();
throw new DAOException("DAO层出错");
}
在处理完BaseDAO之后,原本需要catch的DAO层不再需要catch了,删去它们的try{}catch{}结构即可
从下向上再看一遍,Service层没有catch过,Controller层也没有;DS层存在catch,因此再次进行抛出,代码略,最终,抛出的错误能够回到FIlter中被捕获,最后执行回滚
测试
删去DAOImpl中方法执行的SQL语句中的一部分,使得SQL语句错误,观察异常是否能被FIlter捕获并执行回滚:
![](https://i-blog.csdnimg.cn/blog_migrate/ade4aabe26ac80ec2b1d1e2023517180.png)
此为“登录事务”的第二个调用方法
启动Tomcat,点击登录后,输出如下图:
证明了事务并未进行提交,而是进行了回滚