MVC三层架构学习总结实例

一个简单的转账Servlet Demo

使用MVC三层架构实现

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #mainApp{
            font-size: 20px;
            font-family: "Microsoft YaHei UI",serif;
            text-align: center;
            margin-top: 200px;
            font-weight: bold;
        }
    </style>
</head>
<body>
<div id="mainApp">
    <form action="servlet/transfer" method="post">
        <p><label for="transOut">请填写转出用户名:</label><input type="text" id="transOut" name="transOut" required /></p>
        <p><label for="transIn">请填写转入用户名:</label><input type="text" id="transIn" name="transIn" required /></p>
        <p><label for="money">请填写转账数目:</label><input type="text" id="money" name="money" required /></p>
        <p><input type="submit"><input type="reset"></p>
    </form>
</div>
</body>
</html>

后端

数据库工具类保证数据库调用的统一

public class C3P0Utils {
    /**
     * 获取连接池
     * @return 返回c3p0默认连接池
     */
    public static DataSource getDataSource(){
        return new ComboPooledDataSource();
    }

    /**
     * 获取连接
     * @return 返回一个基于c3p0连接池的连接
     */
    public static Connection getConnection(){
        try {
            return getDataSource().getConnection();
        } catch (SQLException e){
            throw new RuntimeException("无法获取连接,请检查数据库配置文件");
        }
    }

    /**
     * 实现资源的释放
     * 细节在于首先是对于顺序的先开后关
     * 对于每个对象都要有try...catch保证哪怕报错了其他的对象也可以关闭
     * @param connection 数据库连接
     * @param ps 预编译sql对象
     * @param resultSet 数据库结果集
     */
    public static void release(Connection connection, PreparedStatement ps, ResultSet resultSet){
        try {
            if (resultSet != null){
                resultSet.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if (ps != null){
                ps.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if (connection != null){
                connection.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

model用于封装数据库对象

public class User {

    private String name;
    private BigDecimal money;

    public User() {
    }

    public User(String name, BigDecimal money) {
        this.name = name;
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getMoney() {
        return money;
    }

    public void setMoney(BigDecimal money) {
        this.money = money;
    }
}

Dao层用于操作数据库

/**
 * 此接口规定对User数据库的操作
 * @author Rainful
 * @create 2021/07/29
 */
public interface UserDao {
    /**
     * 通过name查询用户
     * @param connection 数据库连接
     * @param ps 预编译sql对象
     * @param sql sql语句
     * @param name 用户名
     * @param money 钱数
     * @throws SQLException 抛出一个查询错误让业务代码回滚
     * @return 返回一个可以查找到的用户
     */
    int moneyTransfer(Connection connection, PreparedStatement ps,
                      String sql, String name, double money) throws SQLException;
}
public class UserDaoImpl implements UserDao {
    @Override
    public int moneyTransfer(Connection connection, PreparedStatement ps,
                             String sql, String name, double money) throws SQLException {
        ps = connection.prepareStatement(sql);
        ps.setDouble(1, money);
        ps.setString(2, name);
        return ps.executeUpdate();
    }
}

业务层用于调用Dao层验证从控制层传来的参数等

/**
 * 此接口用于规范数据库查询
 * @author Rainful
 * @create 2021/07/29
 */
public interface UserServlet {
    /**
     * 业务层调用dao层完成数据库更新及控制事务
     * @param name1 转出账户用户名
     * @param name2 转入账户用户名
     * @param money 修改
     * @return 修改结果
     */
    boolean moneyTransfer(String name1, String name2, double money);
}
public class UserServletImpl implements UserServlet {
    private final UserDao userDao;

    public UserServletImpl() {
        this.userDao = new UserDaoImpl();
    }

    @Override
    public boolean moneyTransfer(String name1, String name2, double money) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = C3P0Utils.getConnection();
            // 开启事务
            connection.setAutoCommit(false);
            // 转出账户
            int transOutRow = transfer(connection, ps, name1, money, -1);
            // 转入账户
            int transInRow = transfer(connection, ps, name2, money, 1);
            // 提交事务
            connection.commit();
            return transOutRow > 0 && transInRow > 0;
        } catch (Exception e) {
            // 发生异常进行回滚处理
            try {
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
            return false;
        } finally {
            // 关闭数据库连接
            try {
                connection.setAutoCommit(true);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            C3P0Utils.release(connection, ps, null);
        }
    }

    /**
     * 返回转账sql的影响行数
     *
     * @param connection 数据库连接
     * @param ps         预编译sql对象
     * @param name       账户更改姓名
     * @param money      更改的钱数
     * @param value      为了保证方法共用性而设置的修改参数
     *                   转入为 1, 转出为 -1
     * @return 返回影响的行数
     * @throws SQLException 抛出sql异常回滚
     */
    private int transfer(Connection connection, PreparedStatement ps, String name, double money, int value) throws SQLException {
        // 转出账户的话因为钱是减少的
        String sql = "update account set money = money + ? where name = ?";
        return userDao.moneyTransfer(connection, ps, sql, name, money * value);
    }
}

控制层用于接收前端数据传输给业务层做逻辑判断

@WebServlet("/servlet/transfer")
public class TransferMoney extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String transOut = req.getParameter("transOut");
        String transIn = req.getParameter("transIn");
        String money = req.getParameter("money");

        // 三者都在前端进行了非空判断
        // 后续如果再加上判断就好了,因为要防止前端被人恶意修改传输数据过来
        long moneyNum;
        System.out.println(money);
        //System.out.println(Long.parseLong(money));
        try {
            moneyNum = Long.parseLong(money);
        } catch (Exception e){
            resp.getWriter().print("金额不符合规范");
            return;
        }

        // 调用业务层进行处理
        UserServlet userServlet = new UserServletImpl();
        boolean flag = userServlet.moneyTransfer(transOut, transIn, moneyNum);
        if (flag){
            resp.getWriter().print("转账成功");
        } else {
            resp.getWriter().print("转账失败");
        }
    }

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

其他

  • 编码转换过滤

    @WebFilter("/servlet/*")
    public class CodeChange implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
    
            chain.doFilter(request, response);
        }
    }
    
  • 权限管理过滤防止恶意直接访问servlet

    @WebFilter("/servlet/*")
    public class FilterServlet implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            String money = request.getParameter("money");
            if (money == null){
                ((HttpServletResponse)response).sendRedirect("../index.html");
            }
            chain.doFilter(request, response);
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

总结

  • bug反思

    • 整体来说一次性写完,但是在数据库调用的时候发现数据库没有修改,经过单元测试排除方法最后发现调用sql的时候参数传递问题
  • 整个servlet优点

    • 完成了全部功能,实现了sql调用的时候connection复用,对一次业务进行connection的统一关闭
    • sql调用的时候进行参数传递到dao层可以一个方法完成增加和减少
    • 实现了MVC三层架构,并且使用接口实现多态,并且规范了实现类的行为
    • 实现了编码转换及权限过滤
  • 优化方向

    • 后续增加业务的时候,可以抽取sql代码完成sqlUtils类的封装规范Dao层的sql调用
    • 增加常用变量的时候可以进行一个静态变量工具类的封装等
posted @ 2021-07-29 19:37  Rainful  阅读(260)  评论(0编辑  收藏  举报