14-MVC(JavaWeb)

MVC模式

        首先MVC模式并不是javaweb项目中独有的,MVC是一种软件工程中的一种设计模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),即为MVC。

        它是一种软件设计的典范,最早为Trygve Reenskaug提出,为施乐帕罗奥多研究中心(Xerox PARC)的Smalltalk语言发明的一种软件设计模式。

  • MVC模式详解: 

        虽然MVC并不是Java当中独有的,但是现在几乎所有的B/S的架构都采用了MVC框架模式。

控制器Controller:控制器即是控制请求的处理逻辑,对请求进行处理,负责请求转发和重定向;
视图View:视图即是用户看到并与之交互的界面,比如HTML(静态资源),JSP(动态资源)等等。
模型Model:模型代表着一种企业规范,就是业务流程/状态的处理以及业务规则的规定。
    业务流程的处理过程对其他层来说是不透明的,模型接受的请求,并返回最终的处理结果。
    业务模型的设计可以说是MVC的核心。
  •  MVC高级框架应用:

        MVC模式被广泛用于Java的各种框架中,比如Struts2、springMVC等等都用到了这种思想。

        Struts2是基于MVC的轻量级的web应用框架。基于MVC,说明基于Struts2开发的Web应用自然就能实现MVC,也说明Struts2着力于在MVC的各个部分为我们的开发提供相应帮助。


JSP开发模型

        JavaWeb经历两个时期:JSP Model1 和 JSP Model2

  • JSP Model1:

        JSP Model1是JavaWeb早期的模型,它适合小型Web项目,开发成本低!Model1第一代时期,服务器端只有JSP页面,所有的操作都在JSP页面中,连访问数据库的API也在JSP页面中完成。也就是说,所有的东西都耦合在一起,对后期的维护和扩展极为不利。

  • JSP Model1的优化(Model1第二代):

        JSP Model1优化后有所改进,把业务逻辑和数据访问的内容放到了JavaBean(狭义JavaBean:实体类,广义JavaBean:实体类,dao,service,工具类)中,而JSP页面负责显示以及请求调度的工作。虽然第二代比第一代好了些,但还让JSP做了过多的工作,JSP中把视图工作和请求调度(控制器)的工作耦合在一起了。

  •  JSP Model2:

        JSP Model2模式已经可以清晰的看到MVC完整的结构了。

JSP:视图层,用来与用户打交道。负责接收数据,以及显示数据给用户;
Servlet:控制层,负责找到合适的模型对象来处理业务逻辑,转发到合适的视图;
JavaBean:模型层,完成具体的业务工作,例如:开启、转账等。

        这就是javaweb经历的两个时期,JSP Model2适合多人合作开发大型的Web项目,各司其职,互不干涉,有利于开发中的分工,有利于组件的重用。但是,Web项目的开发难度加大,同时对开发人员的技术要求也提高了。


 基于MVC的三层架构

        虽然MVC把程序分成三部分,每个部分负责不同的功能,但是这只是逻辑的分离,实际代码并没有真正分离,特别是Model(包括业务、数据访问和实体类、工具类等)部分的代码,为了增强代码的维护性和降低代码耦合性,需要把代码分层管理,于是就有了三层架构:

        分别是:web层(表示|界面层)、service层(业务逻辑层)、dao层(数据访问层、持久层)

        web层对应MVC中的Servlet和JSP,其他层都属于MVC中的Model。


MVC事务案例

  • 转账页面:
  <body>
      <form action="${pageContext.request.contextPath }/servlet/AccountServlet" method="post" >
         
             转出方:<input type="text"  name="outaccount"/><br/>
             转入方:<input type="text"  name="intaccount"/><br/>        
             金额<input type="text"  name="money"/><br/>
     <input type="submit"  value="转账"/><br/>    
      </form>
  </body>
  •  数据库配置文件:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/gp03
username=root
password=root

initialSize=10
maxActive=50
maxWait=50000
minIdle=5
  • 项目分包:
com.qf.domain、com.qf.dao、com.qf.dao.impl
com.qf.service、com.qf.service.impl
com.qf.utils
com.qf.web.servlet
  • 工具类:
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class DataSourceUtils {
    private static DruidDataSource dataSource;
    //线程局部变量
    private static ThreadLocal<Connection> threadLocal;

    static {
        try {
            threadLocal = new ThreadLocal<>();
            Properties properties = new Properties();
            InputStream is = DataSourceUtils.class.getClassLoader().getResourceAsStream("database.properties");
            properties.load(is);

            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("初始化连接池失败");
        }
    }

    public static DataSource getDataSource() {
        return dataSource;
    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection == null) {
            connection = getDataSource().getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }

    //开启事务
    public static void startTransaction() throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);
    }

    //提交
    public static void commit() throws SQLException {
        Connection connection = getConnection();
        connection.commit();
    }

    //回滚
    public static void rollback() throws SQLException {
        Connection connection = getConnection();
        connection.rollback();
        connection.commit();
    }

    //关闭
    public static void close() throws SQLException {
        Connection connection = getConnection();
        connection.close();
        threadLocal.remove();

    }
}
  • DAO:
@Data
public class Account {
    private int id;
    private String name;
    private BigDecimal money;
}

public interface AccountDao {
    //加钱
    public void addMoney(Account account, BigDecimal m);
    //扣钱
    public void subMoney(Account account,BigDecimal m);
}


public class AccountDaoImpl implements AccountDao {
    @Override
    public void addMoney(Account account, BigDecimal m) {
        QueryRunner qr = new QueryRunner();
        try {
            qr.update(DataSourceUtils.getConnection(), "update account set money=money+? where id=?", m, account.getId());
        } catch (SQLException e) {
            throw new RuntimeException("加钱失败", e);
        }
    }

    @Override
    public void subMoney(Account account, BigDecimal m) {
        QueryRunner qr = new QueryRunner();
        try {
            qr.update(DataSourceUtils.getConnection(), "update account set money=money-? where id=?", m, account.getId());
        } catch (SQLException e) {
            throw new RuntimeException("扣钱失败", e);
        }
    }
}
  • Service:
public interface AccountService {
    //转账
    public void transMoney(Account from, Account to, BigDecimal m);
}


public class AccountServiceImpl implements AccountService {
    @Override
    public void transMoney(Account from, Account to, BigDecimal m) {
        try {
            //开启事务
            DataSourceUtils.startTransaction();

            AccountDao accountDao = new AccountDaoImpl();
            //1 先把from钱扣掉
            accountDao.subMoney(from, m);
            //int i=10/0;
            //2 再给to加钱
            accountDao.addMoney(to, m);

            //提交
            DataSourceUtils.commit();
        } catch (Exception e) {
            //回滚
            try {
                DataSourceUtils.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            throw new RuntimeException("转账失败", e);
        } finally {
            try {
                DataSourceUtils.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  • Servlet:
@WebServlet(name = "TransServlet", urlPatterns = "/trans")
public class TransServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接受数据
        String from = request.getParameter("from");
        String to = request.getParameter("to");
        String money = request.getParameter("money");
        //验证金额
        BigDecimal m = new BigDecimal(money);
        if (m.doubleValue() <= 0) {
            response.getWriter().write("转账金额不能小于0");
            return;
        }
        //转账
        AccountService accountService = new AccountServiceImpl();
        Account accfrom = new Account(Integer.parseInt(from), "宁宁", new BigDecimal(0));
        Account accto = new Account(Integer.parseInt(to), "春春", new BigDecimal(0));
        try {
            accountService.transMoney(accfrom, accto, m);
            response.getWriter().write("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().write("转账失败");
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  • 防止表单重复提交:

        常见用两种方式

1、通过JavaScript屏蔽提交按钮(不推荐)
通过js代码:当用户点击提交按钮后,屏蔽提交按钮使用户无法点击提交按钮或点击无效,
    从而实现防止表单重复提交。
缺点:js代码很容易被绕过。
    比如用户通过刷新页面方式,
    或使用postman等工具绕过前段页面仍能重复提交表单。因此不推荐此方法。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>转账</title>
    <script type="text/javascript">
        //默认提交状态为false
        var commitStatus = false;
        function dosubmit(){
            if(commitStatus==false){
                //提交表单后,将提交状态改为true
                commitStatus = true;
                document.getElementById("btnsubmit").disabled=true;
                return true;
            }else{
                return false;
            }
        }
     </script>
</head>
<body>
    <h2>转账</h2>
    <form action="${pageContext.request.contextPath}/trans" onsubmit="return dosubmit()" method="post" target="_blank">
        <input type="text" name="from" placeholder="请输入转出账号"><br/>
        <input type="text" name="to" placeholder="请输入转入账号"><br/>
        <input type="text" name="money" placeholder="请输入金额"><br/>
        <input id="btnsubmit" type="submit" value="转账">
    </form>
</body>
</html>
2、利用Session防止表单重复提交(推荐)
服务器返回表单页面时,会先生成一个subToken保存于session,并把该subToken传给表单页面。
当表单提交时会带上subToken,服务器判断session中的subToken和表单提交subToken是否一致。
若不一致或session的subToken为空或表单未携带subToken则不通过。

首次提交表单时session的subToken与表单携带的subToken一致走正常流程,
然后服务器会删除session保存的subToken。
当再次提交表单时由于session的subToken为空则不通过。从而实现了防止表单重复提交。
public class TokenProccessor {
    private TokenProccessor() {}
    private static final TokenProccessor instance = new TokenProccessor();
    public static TokenProccessor getInstance() {return instance;}

    public String makeToken() {
        String str = System.currentTimeMillis() + new Random().nextInt(999999999) + "";
        try {
            //1使用md5加密
            MessageDigest digest = MessageDigest.getInstance("md5");
            digest.update(str.getBytes());
            byte[] digest1 = digest.digest();
            //2使用base64
            BASE64Encoder encoder = new BASE64Encoder();
            return encoder.encode(digest1);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
}


public class TokenTools {
    //把token放入session
    public static void createToken(HttpServletRequest request, String tokenkey) {
        String token = TokenProccessor.getInstance().makeToken();
        request.getSession().setAttribute(tokenkey, token);
    }

    //把token从session删除
    public static void removeToken(HttpServletRequest request, String tokenkey) {
        request.getSession().removeAttribute(tokenkey);
    }
}
@WebServlet(name = "TransViewServlet", urlPatterns = "/transview")
public class TransViewServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //生成一个token
        TokenTools.createToken(request, "mytoken");
        request.getRequestDispatcher("/WEB-INF/jsp/transview.jsp").forward(request, response);
    }
}
<body>
    <h2>转账</h2>
    <form action="${pageContext.request.contextPath}/trans" onsubmit="return dosubmit()" method="post" target="_blank">
        <input type="text" name="from" placeholder="请输入转出账号"><br/>
        <input type="text" name="to" placeholder="请输入转入账号"><br/>
        <input type="text" name="money" placeholder="请输入金额"><br/>
        <input type="hidden" name="mytoken" value="${mytoken}">
        <input id="btnsubmit" type="submit" value="转账">
    </form>
</body>
@WebServlet(name = "TransServlet", urlPatterns = "/trans")
public class TransServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //判断是否是重复提交
        boolean b = isRepeatSubmit(request, "mytoken");
        if (b) {
            response.getWriter().write("不好意思,重复提交了...");
            return;
        }
        //通过验证删除
        TokenTools.removeToken(request, "mytoken");

        //接受数据
        String from = request.getParameter("from");
        String to = request.getParameter("to");
        String money = request.getParameter("money");
        //验证金额
        BigDecimal m = new BigDecimal(money);
        if (m.doubleValue() <= 0) {
            response.getWriter().write("转账金额不能小于0");
            return;
        }
        //转账
        AccountService accountService = new AccountServiceImpl();
        Account accfrom = new Account(Integer.parseInt(from), "宁宁", new BigDecimal(0));
        Account accto = new Account(Integer.parseInt(to), "春春", new BigDecimal(0));
        try {
            accountService.transMoney(accfrom, accto, m);
            response.getWriter().write("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().write("转账失败");
        }
    }

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

    public boolean isRepeatSubmit(HttpServletRequest request, String clientTokeyKey) {
        //1 判断session有没有
        String serverToken = (String) request.getSession().getAttribute(clientTokeyKey);
        if (serverToken == null) {
            return true;
        }
        //2 判断客户端有没有token
        String clientToken = request.getParameter(clientTokeyKey);
        if (clientToken == null) {
            return true;
        }
        //3 两个是否相同
        if (!serverToken.equals(clientToken)) {
            return true;
        }
        return false;
    }
}

MVC分页案例

        分页是web应用程序非常重要的一个技术。数据库中的数据可能是成千上万的,不可能把这么多的数据一次显示在浏览器上面。一般根据每行数据在页面上所占的空间每页显示若干行,比如一般20行是一个比较理想的显示状态。

分页的思路:对于海量的数据查询,需要多少就取多少,显然是最佳的解决方法,
假如某个表中有200万条记录,第一页取前20条,第二页取21~40条记录。
select  * from 表名 order by id limit  0,20;
select  * from 表名 order by id limit  20,20;
select  * from 表名 order by id limit  40,20;
  • 数据库配置文件:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/myschool
username=root
password=root
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=5000<?xml version="1.0" encoding="UTF-8"?>
  • 实体类和工具类:
@Data
public class Student {
		private int studentNo;
		private String loginPwd;
		private String studentName;
		private String sex;
		private Date bornDate;
}
@Setter
@Getter
public class PageBean<T> {
    //五个属性
    private int pageIndex;
    private int pageSize;
    private long totalSize;
    private int pageCount;

    private List<T> data;

    //开始和结束页码
    private int startPage;
    private int endPage;

    public PageBean(int pageIndex, int pageSize, long totalSize, List<T> data) {
        this.pageIndex = pageIndex;
        this.pageSize = pageSize;
        this.totalSize = totalSize;
        this.data = data;

        //计算pageCount
        pageCount = (int) (totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1);

        //设置开始页码和结束页码
        //1正常情况
        startPage = pageIndex - 4;
        endPage = pageIndex + 5;
        //2当前页面小于5
        if (pageIndex < 5) {
            startPage = 1;
            endPage = 10;
        }
        //3当前页面大于总页码-5
        if (pageIndex > pageCount - 5) {
            startPage = pageCount - 9;
            endPage = pageCount;
        }
        //4总页码小于10
        if (pageCount <= 10) {
            startPage = 1;
            endPage = pageCount;
        }
    }
}
public class DataSourceUtils {
    private static DruidDataSource dataSource;
    //线程局部变量
    private static ThreadLocal<Connection> threadLocal;

    static {
        try {
            threadLocal=new ThreadLocal<>();
            Properties properties=new Properties();
            InputStream is= DataSourceUtils.class.getClassLoader().getResourceAsStream("database.properties");
            properties.load(is);

            dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("初始化连接池失败");
        }
    }

    public static DataSource getDataSource(){
        return dataSource;
    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if(connection==null){
            connection=getDataSource().getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }
}
  • DAO:
public interface StudentDao {
    //分页查询方法
    public List<Student> findByPage(int pageIndex, int pageSize);
    //添加
    public void add(Student stu);
    //获取数据个数的方法
    public long getCount();
}


public class StudentDaoImpl implements StudentDao {
    @Override
    public List<Student> findByPage(int pageIndex, int pageSize) {  //1  20   2  20
        QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());
        try {
            return qr.query("select * from student order by studentNo limit ?,?", new BeanListHandler<>(Student.class), (pageIndex - 1) * pageSize, pageSize);
        } catch (Exception e) {
            throw new RuntimeException("分页查询失败", e);
        }
    }

    @Override
    public void add(Student stu) {
        QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());
        try {
            qr.update("insert into student(studentNo,loginPwd,studentName,sex,bornDate) values(?,?,?,?,?);", stu.getStudentNo(), stu.getLoginPwd(), stu.getStudentName(), stu.getSex(), stu.getBornDate());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("添加失败", e);
        }
    }

    @Override
    public long getCount() {
        QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());
        try {
            return qr.query("select count(*) from student", new ScalarHandler<>());
        } catch (Exception e) {
            throw new RuntimeException("获取数据个数失败", e);
        }
    }
}
  • Service:
public interface StudentService {
    PageBean<Student> getDataByPage(int pageIndex, int pageSize);
}

public class StudentServiceImpl implements StudentService {
    @Override
    public PageBean<Student> getDataByPage(int pageIndex, int pageSize) {
        //1获取数据
        StudentDao studentDao=new StudentDaoImpl();
        List<Student> data=studentDao.findByPage(pageIndex, pageSize);
        //2获取数据个数
        long totalSize = studentDao.getCount();
        PageBean<Student> pageBean=new PageBean<>(pageIndex, pageSize, totalSize, data);
        return pageBean;
    }
}
  • Servlet:
@WebServlet(name = "StudentListServlet",urlPatterns = "/stulist")
public class StudentListServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //1获取pageIndex,pageSize;
        String spageIndex = request.getParameter("pageIndex");
        String spageSize = request.getParameter("pageSize");
        int pageIndex=1;
        int pageSize=10;
        if(!StringUtils.isEmpty(spageIndex)){
            pageIndex=Integer.parseInt(spageIndex);
        }
        if(!StringUtils.isEmpty(spageSize)){
            pageSize=Integer.parseInt(spageSize);
        }
        //分页查询
        StudentService studentService=new StudentServiceImpl();
        PageBean<Student> pageBean=studentService.getDataByPage(pageIndex,pageSize);
        if(pageBean!=null){
            request.setAttribute("pageBean", pageBean);
        }else{
            request.setAttribute("msg", "数据为空");
        }
        request.getRequestDispatcher("/studentlist.jsp").forward(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  • jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>学生列表</title>
</head>
<body>
    <h2>学生信息</h2>
    ${msg}
    <table>
        <tr>
            <th>学号</th>
            <th>密码</th>
            <th>姓名</th>
            <th>性别</th>
            <th>出生日期</th>
        </tr>
        <c:forEach items="${pageBean.data}" var="stu">
            <tr>
                <td>${stu.studentNo}</td>
                <td>${stu.loginPwd}</td>
                <td>${stu.studentName}</td>
                <td>${stu.sex}</td>
                <td>${stu.bornDate}</td>
            </tr>
        </c:forEach>
    </table>

    <div id="nav">
        <a href="${pageContext.request.contextPath}/stulist?pageIndex=1&pageSize=${pageBean.pageSize}">首页</a>
        <c:if test="${pageBean.pageIndex>1}">
            <a href="${pageContext.request.contextPath}/stulist?pageIndex=${pageBean.pageIndex-1}&pageSize=${pageBean.pageSize}">上一页</a>
        </c:if>

        <c:forEach begin="${pageBean.startPage}" end="${pageBean.endPage}" var="n">
            <a href="${pageContext.request.contextPath}/stulist?pageIndex=${n}&pageSize=${pageBean.pageSize}">${n}</a>
        </c:forEach>

        <c:if test="${pageBean.pageIndex<pageBean.pageCount}">
            <a href="${pageContext.request.contextPath}/stulist?pageIndex=${pageBean.pageIndex+1}&pageSize=${pageBean.pageSize}">下一页</a>
        </c:if>

        <a href="${pageContext.request.contextPath}/stulist?pageIndex=${pageBean.pageCount}&pageSize=${pageBean.pageSize}">尾页</a>

        共【${pageBean.pageIndex}/${pageBean.pageCount}】页
        <input id="pagenum" type="number" name="pagenum" style="width: 50px" value="${pageBean.pageIndex}"><input type="button" value="跳转" onclick="changenum()">
    </div>

    <script type="text/javascript">
        function  changenum() {
           var pnum= document.getElementById("pagenum").value;
           if(isNaN(pnum)){
               alert("您输入数字有误");
               return;
           }
           window.location='${pageContext.request.contextPath}/stulist?pageIndex='+pnum+'&pageSize=${pageBean.pageSize}';
        }
    </script>
</body>
</html>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值