MVC的实用教程,带你理解它的世界

之前看过我博客的朋友应该都已经懂了servlet和JSP的使用和他们的特性,我们在使用servlet进行设计开发时,会遇到短板,我们需要在后台代码中写入大量的html内容,内容繁琐而且也不好看更不方便修改和维护。我们在使用JSP进行开发的时候,后台业务逻辑代码又带到了前台页面中去写,很不方便。那MVC的这种设计模式就解决了这个问题,它能够让我们的前后端不在一起写,页面就是页面,后台就是后台,我们只需要完成前后端数据的交互就OK了。那很多人会纳闷,数据怎么传递的呢?别着急,在下面的demo中我会很详细的解释。

技术就是这样,单一的特性很重要,但是只有把他们的单一特性结合起来使用,更方便我们的开发,这才是更重要的,其实这也就是所谓框架的理念。

MVC(Model View Control)是一种分层设计模式,所谓分层,就是谁该干嘛就干嘛,互不打扰。

Model指的是模型,也就是我们通常理解的数据准备层,它主要负责连接数据库,准备sql并执行,完成我们的增删改查操作。

View指的是视图,也就是我们常说的前端页面,用来展示后台我们需要的数据和内容。

Control指的是控制层,他是MVC的核心部分,连通数据层和展示层。它怎么控制呢,首先它会从数据准备层调取相应的方法,获取到数据到他这里,然后把这些数据进行业务处理,再通过传递参数或者request对象等将数据传递到前端页面。或者它把前端页面传递过来的数据通过调用制定的方法交给数据准备层,让他存进数据库中。这就是他的工作内容。

其实MVC的这种设计方式很容易理解,理解懂了MVC,相信大家在以后理解更多框架的时候会有很大的帮助。

下面我通过自己搭建的demo给大家逐步分析MVC是怎样从无到有的。

一:准备工作

1. 首先准备我们的数据库,我这里的demo是通过mvc完成对一张表的增删改查操作。所以只新建一张表,表结构如下图:

主键id(自增长)、英雄名name、血量Hp、伤害damage

 手动添加几条数据进去。新建数据库和添加数据就不一一赘述了。

2.eclipse中新建一个dynamic Web project项目,配置好tomcat,新建web.xml文件。在lib路径下导入jar包,这里用到的jar包有:mysql-conncetor-java-bin.jar,servlet-api.jar,jstl.jar,standard.jar。项目路径截图如下:

3. 在web下新建heroList.jsp用来展示我们查询到的结果。src路径下新建bean包(javabean也就是我们的实体类)、dao包(连接数据库并执行sql,也就是model层),这里的default package包下就是我们的control层,在src下右击新建class,把默认的报名删掉就会新建在这个 default package包中。

二:开始设计

我们先实现一个功能,从数据库中查询出所有的英雄,并显示在前端页面的表格中。

1. 首先新建我们的bean包中javaBean实体类,Hero.java,定义变量id,name,hp,damage。并给出对应的getter和setter方法。实体类准备好了。(如果不明白为什么要建实体类的话,你就把它理解成一个对象,一个对象里面装的就是我们数据库中的一条记录就好了,配合百度你就懂了)

2. 在dao包中新建HeroDao.java,完成JDBC连接数据库,完成查询数据库中所有英雄的方法。代码如下:

package com.mvc.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.mvc.bean.Hero;

public class HeroDao {
	
	public HeroDao() {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	public Connection getConnection() {
		String user = "root";
		String password = "root";
		String data = "jdbc:mysql://localhost:3306/test";
		Connection con = null;
		try {
			con = DriverManager.getConnection(data, user, password);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		System.out.println("*****************数据库连接成功!*****************");
		return con;
	}
	
	public List<Hero> getList() {
		List<Hero> listHero = new ArrayList<Hero>();
		try {
			Connection con = getConnection();
			Statement stmt = con.createStatement();
			String sql = "select * from hero";
			ResultSet rs = stmt.executeQuery(sql);
			while(rs.next()) {
				Hero hero = new Hero();
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int damage = rs.getInt("damage");
				float hp = rs.getFloat("hp");
				hero.setId(id);
				hero.setName(name);
				hero.setHp(hp);
				hero.setDamage(damage);
				listHero.add(hero);
			}
			stmt.close();
			con.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return listHero;
	}
}

大概解释一下:重写heroDao的无参构造方法,并在其中加载JDBCMySQL的加载类。

新建getConnection方法,用来连接数据库,我们在其他方法中直接调用即可 。

getList()方法,获取数据库中所有的英雄,此为JDBC基础知识,如果看不懂就狂补一下JDBC的基础吧。

也就是说,如果我们在其他类中调用了getList()方法之后,返回值中的listHero集合中就是我们数据库中的全部英雄数据。

3. 编写我们的control层,连通前后端。在src下新建类HeroList.java,默认包删掉(主要是方便在web.xml中配置servlet简便,不用加包名)。这个类就是一个servlet,让他继承httpServlet类。并重写service方法,用来处理请求。在service方法中,我们新建hero实体,并新建一个集合heros来存放数据库中查询出来的集合。然后调用heroDao中的getList()方法。将返回结果给heros。将heros存在在session中,重点来了,MVC控制层在前后端交换数据的方式就是通过request或者session对象的setAttribute()方法来存放和拿取数据。这里再解释一下,session会话对象在用户开发浏览器,访问服务器的时候就自动创建了,关闭浏览器或者超过有效期就失效了。request对象传递参数只会在一次完整的请求中有效,请求结束或者发起新的请求时数据就丢失了。一般使用session存放数据的多。具体请参考我的博客JSP和servlet中对session的介绍。最后将服务器重定向到heroList.jsp页面中。代码如下:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mvc.bean.Hero;
import com.mvc.dao.HeroDao;


public class HeroList extends HttpServlet {
	
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		//验证是否登录成功 暂时先忽略
		if(null == request.getSession().getAttribute("user") || "" == request.getSession().getAttribute("user")) {
			response.sendRedirect("login.html");
			return;
		}
		
		//以下为分页代码,暂时先忽略
		int total = new HeroDao().getTotal();
		int start = 0;
		int count = 5;
		int lastPage = 0;
		if(total/count == 0) {
			lastPage = total -count;
		}else {
			lastPage = (total/count) * count;
		}
		try {
			int next = Integer.parseInt(request.getParameter("next"));
			if(next <= total) {
				start = next;
			}else {
				start = (total/count) * count;
			}
		} catch (Exception e) {
			start = 0;
		}

        //从这里开始看,新建集合对象
		List<Hero> heros = new ArrayList<Hero>();
		
        //忽略,此为分页内容
		int xiaye = start + count;
		int shangye = xiaye - (count * 2);
		if(shangye < 0) {
			shangye = 0;
		}

        //将数据库查询结果赋值给heros,并存在session中,key为hero1,重定向到前端页面。
		heros = new HeroDao().getHeroList(start,count);
		request.setAttribute("xiaye", xiaye);
		request.setAttribute("shangye", shangye);
		request.setAttribute("lastPage", lastPage);
		request.getSession().setAttribute("hero1", heros);
		request.setAttribute("hero", heros);
		request.getRequestDispatcher("heroList.jsp").forward(request, response);
	}

}

4. 此时服务器地址将转向:http:localhost:8080/mvc/heroList.jsp,并在session中存了一个key为hero1的数据。接下来是我们的View层heroList.jsp如何将session中的值取出,然后显示出来的时候了。

在改JSP中,我们需要用到jstl的知识和EL表达式(两者我的JSP博客中有介绍,很简单),jar包已经导入好。

加入指令<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>告诉浏览器我们使用c标签。

先上代码:

<%@page import="com.mvc.bean.Hero"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<style>
	a{
		text-decoration: none
	}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>英雄列表</title>
</head>
<body>

<h2>***************使用JSP获取的数据库中的集合值***************</h2>
<%
	List<Hero> hlist = (List<Hero>)request.getSession().getAttribute("hero1");
%>
<table style="width: 400px;" align="center" border="1">
	<tr>
		<td>id</td>
		<td>name</td>
		<td>hp</td>
		<td>damage</td>
	</tr>
	<%for(int i = 0; i < hlist.size(); i++) { %>
	<tr>
		<td><%=hlist.get(i).getId() %></td>
		<td><%=hlist.get(i).getName() %></td>
		<td><%=hlist.get(i).getHp() %></td>
		<td><%=hlist.get(i).getDamage() %></td>
	</tr>
	<%} %>
</table>
<h2>***************使用JSTL中的forEach获取的数据库中的集合值***************</h2>
<table style="400px;"border="1" bordercolor="lightSkyBlue" align="center" cellspacing="0">
	<tr>
		<td>id</td>
		<td>name</td>
		<td>hp</td>
		<td>damage</td>
		<td colspan="2" align="center"><font color="green">操作</font></td>
	</tr>
<c:forEach items="${hero1}" var="hero" varStatus="st">
	<tr>
		<td>${hero1.id}</td>
		<td>${hero1.name}</td>
		<td>${hero1.hp}</td>
		<td>${hero1.damage}</td>
		<td><a href="update?id=${hero1.id}"><font color="green">更新</font></a></td>
		<td><a href="del?id=${hero1.id}"><font color="red">删除</font></a></td>
	</tr>
</c:forEach>
</table>
<div align="center">
<a href="?next=0">首页</a>
<a href="?next=${shangye}">【上一页】</a>
<a href="?next=${xiaye}">【下一页】</a>
<a href="?next=${lastPage}">末页</a>
</div>
<hr>
<p>当前在线用户数为:${online_number}</p>
<a href="goOut">退出登录</a>
</body>
</html>

我这里使用了两种方式展示数据,一种是纯JSP展示,一种是比较方便的EL表达式展示数据。分页和删除更新和当前用户在线数那里先不用看。只看表格中的数据展示即可。

对代码进行解释:服务器带着session数据进入到该页面,我们获取session中数据的方式有很多种,这里使用了JSP的隐式对象request.getSession.getAttribute(heros1)方法来获取或者使用EL表达式中${heros1}。因为我们heros1是个集合,集合中存的是hero对象,我们需要的数据在对象中,也就是heros1[*].hero.name这样的形式获取,所以我们需要遍历数据,完成取数。这里使用的是JSTL中的forEach。通过这种方式,我们前端页面成功的显示出了数据库中的内容。因为使用了分页,只显示五条数据

5. 配置web.xml,配置我们的servlet,地址栏输入:http:localhost:8080/mvc/heroList,请求服务器,就会到我们显示英雄的页面啦。

  
	<servlet>
		<servlet-name>HeroList</servlet-name>
		<servlet-class>HeroList</servlet-class>	
	</servlet>
	<servlet-mapping>
		<servlet-name>HeroList</servlet-name>
		<url-pattern>/heroList</url-pattern>
	</servlet-mapping>

 

6. 其实你如果会操作的这些,对于MVC你已经掌握了,他就是servlet和JSP的结合版,没有什么特殊的呀

三:删改操作。增加操作这里不再写了,有兴趣的朋友可结合我的servlet博客自行联系 。

1. 在heroDao中增加删除和修改的方法,其中删除很简单,只需要获取该条数据的ID对应去数据库执行删除操作即可,更新则多了一部,需要先从数据库中获取到该条数据,然后在编辑后保存。具体代码如下:

//删除
public void delHero(int id) {
		try {
			String sql = "delete from hero where id = ?";
			Connection con = getConnection();
			PreparedStatement pm = con.prepareStatement(sql);
			pm.setInt(1, id);
			pm.execute();
			pm.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
//获取
	public Hero getById(int id) {
		Hero hero = new Hero();
		try {
			String sql = "select * from hero where id=?";
			Connection con = getConnection();
			PreparedStatement pm = con.prepareStatement(sql);
			pm.setInt(1, id);
			ResultSet rs = pm.executeQuery();
			while(rs.next()) {
				hero.setId(rs.getInt("id"));
				hero.setName(rs.getString("name"));
				hero.setHp(rs.getFloat("hp"));
				hero.setDamage(rs.getInt("damage"));
			}
			pm.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return hero;
	}

//更新	
	public void updateHero(Hero hero) {
		try {
			Connection con = getConnection();
			String sql = "update hero set name=?,hp=?,damage=? where id = ?";
			PreparedStatement pm = con.prepareStatement(sql);
			pm.setString(1, hero.getName());
			pm.setFloat(2, hero.getHp());
			pm.setInt(3, hero.getDamage());
			pm.setInt(4, hero.getId());
			pm.execute();
			pm.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

2. 配置web,xml文件,配置删除和更新的servlet:

3.. 编写delHero.java和updateHero.java类,首先一定前提:观察上面的heroList.jsp中,对应的删除和更新链接会传递一个参数,就是该条数据的id值。我们在页面中点删除时,连接访问servlet,servlet访问delHero类,执行service方法。我们在delHero.java中通过request.getParameter("id")获取到该条数据的ID值,然后执行删除方法,最后重定向到heroList这个servlet中去,再去查询一遍数据库,显示heroList.jsp,代码如下:

public class DelHero extends HttpServlet {
	protected void service(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
		int id = Integer.parseInt(request.getParameter("id"));
		new HeroDao().delHero(id);
		response.sendRedirect("heroList");
	}
}

4. 点更新后,根据servlet配置会跳转到获取数据的类中,获取传过来的id值,执行getById方法,获取数据,将数据保存在request中,服务器跳转到editHero.jsp,代码如下:

public class UpdateHero extends HttpServlet {

	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
		Hero hero = new Hero();
		hero = new HeroDao().getById(Integer.parseInt(request.getParameter("id")));
		request.setAttribute("hero", hero);
		request.getRequestDispatcher("editHero.jsp").forward(request, response);
	}
}

在编辑页面editHero.jsp,该页面中会获取到这条需要更新的数据的所有值,显示出来,把需要修改的数据进行修改后,点击更新按钮,重新提交请求到实现更新的servlet进行执行update操作。

<title>修改英雄属性</title>
</head>
<body>
<form action="update1Hero" method="get">
	英雄:<input type="text" name="name" value="${hero.name}">
	血量:<input type="text" name="hp" value="${hero.hp}">
	伤害:<input type="text" name="damage" value="${hero.damage}">
	<input type="hidden" name="id" value="${hero.id}">
	<input type="submit" value="更新">
</form>
</body>
</html>

此时点更新后会请求地址update1Hero,web.xml中配置了该servlet的对应的类,然后在该类中获取到所有通过form表单提交的参数值,将参数值存放在hero对象中,然后执行updateHero()方法。完成更新。最后重定向到listHero中,重新查询数据库中所有的数据,显示到heroList.jsp里面。更新类的代码如下:

public class Update1Hero extends HttpServlet {

	protected void service(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException{
		Hero hero = new Hero();
		hero.setId(Integer.parseInt(request.getParameter("id")));
		hero.setName(request.getParameter("name"));
		hero.setDamage(Integer.parseInt(request.getParameter("damage")));
		hero.setHp((float)Double.parseDouble(request.getParameter("hp")));
		new HeroDao().updateHero(hero);
		response.sendRedirect("heroList");
		//request.getRequestDispatcher("heroList").forward(request, response);
	}
}

四:分页:

分页的核心知识点其实就在于sql的limit a,b的用法,他的意思就是从第a条数据开始查询,查询b跳,a从基数0开始数。

核心sql:select * from hero order by id asc limit ?,?   其中order by id asc是按照id降序排序的意思。也就是说,我们只需要修改后面的两个参数,即可完成分页功能。我么分别在页面中给上一页,下一页,首页,未页赋予一个参数,并将该参数通过连接改变地址栏的地址,将地址带上参数访问服务器。然后在servlet中对这些参数完成逻辑处理,即可轻松实现分页功能。其中分页查询的方法如下:有两个参数

public List<Hero> getHeroList(int start, int count) {
		List<Hero> listHero = new ArrayList<Hero>();
		try {
			Connection con = getConnection();
			String sql = "select * from hero order by id asc limit ?,?";
			PreparedStatement pm = con.prepareStatement(sql);
			pm.setInt(1, start);
			pm.setInt(2, count);
			ResultSet rs = pm.executeQuery();
			while(rs.next()) {
				Hero hero = new Hero();
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int damage = rs.getInt("damage");
				float hp = rs.getFloat("hp");
				hero.setId(id);
				hero.setName(name);
				hero.setHp(hp);
				hero.setDamage(damage);
				listHero.add(hero);
			}
			pm.close();
			con.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return listHero;
	}

servlet中处理参数的逻辑如下:

int total = new HeroDao().getTotal();
		int start = 0;
		int count = 5;
		int lastPage = 0;
		if(total/count == 0) {
			lastPage = total -count;
		}else {
			lastPage = (total/count) * count;
		}
		try {
			int next = Integer.parseInt(request.getParameter("next"));
			if(next <= total) {
				start = next;
			}else {
				start = (total/count) * count;
			}
		} catch (Exception e) {
			start = 0;
		}
		List<Hero> heros = new ArrayList<Hero>();
		
		int xiaye = start + count;
		int shangye = xiaye - (count * 2);
		if(shangye < 0) {
			shangye = 0;
		}
		heros = new HeroDao().getHeroList(start,count);
		request.setAttribute("xiaye", xiaye);
		request.setAttribute("shangye", shangye);
		request.setAttribute("lastPage", lastPage);
		request.getSession().setAttribute("hero1", heros);
		request.setAttribute("hero", heros);
		request.getRequestDispatcher("heroList.jsp").forward(request, response);

 大体对处理分页的逻辑进行解析:

total就是获取数据库中的总记录条数。用以判断未页不在跳转。

第一次访问servlet时,根据逻辑我们判断好了lastPage的值和xiaye的值和shangye的值,将该值传递给前端页面,点击时通过next参数传递过来。

假如一共有13页,在前台中点击下一页,地址栏传递一个next的参数,该值初始值为5(因为第一次访问servlet时,这个值就被赋予了5),next=5在servlet中获取之后,判断下一页的参数是否超过了总记录条数,如果超过则start的值就是总记录条数除以分页条数取整然后乘以每页条数,也就是13/5 * 5=10,也就是最后一页的开始数。如果没有超过,则让start的值等于这个next,此时start=5,然后再设置准备传递给前台页面中下一个下一个参数的值,也就是xiaye:5+5=10,然后传递给前台下次点击上页时应该传递的值,shangye:10-(5*2)=0,调用方法getHeroList(start,count),其中count值是不变的,变的是start的值,此时该值为5,则数据库去查询从第六条数据开始,查询五条,也就是我们的第二页。

此时跳转到第二页,我们再点击下一页时,next是几呢?跳转之前就以为为我们算好了,此时next是10,没有超过总记录条数,start=10,下一个下页的值更新为:15,下一个上页的值更新为5,然后执行getHeroList(10,5)显示第三页。

第三页中再点击下一页,next=15了,超过了记录条数,start=10,在设置下一个下页的值为15,然后去查询getHeroList(10,5)仍然显示第三页,在去点击下一页的时候,start还是等于10,仍然会查询getHeroList(10,5)第三页。

如果在第三页点击上一页的时候,此时next的值是什么呢?我们跳转之前就设置好了,此时next传递的是5,没有超过记录条数,start=5,设置下一个下页的值为10,下一个上页的值为0,然后执行getHeroList(5,5)返回到第二页中。

如果在第一页点击上一页呢?next=0,start=0,shangye的值:5-5*2=-5,增加了判断if(shangye < 0),shangye就=0,则此时执行getHeroList(0,5)还是查询第一页的值。

稍微思考一下就能理解这个逻辑了,算法好的人一看应该就懂。我竟然赘述了这么多...

五:至此,MVC设计模式中小demo的增删该查已经全部完成了,就这么简单。

在这个demo中我增加了登录功能和监听器监听session和request的变动,如果需要了解,我会在下面的博客中进行解析和分享。也就是大家之前看到的验证登录(判断用户是否登录,如果登录可以访问全部页面,如果未登录,则只能访问登录页面,无法访问英雄列表等页面)和监听在线人数

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值