SpringMVC-nfjh
SpringMVC
springmvc项目创建
1、使用maven创建web项目结构
2、补充更改结构
3、springmvc-config.xml
1)添加包扫描(context命名空间)
2)添加视图解析器
前缀后缀
<!--包扫描-->
<context:component-scan base-package="com.nfjh.springmvc.controller"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/admin/"/>
<property name="suffix" value=".jsp"/>
</bean><!--
/admin/main.jsp
-->
4、更换新的web.xml文件
设置前置控制器DispatcherServlet
并在控制器中引入spring.xml的配置
<!--前置控制器DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--引入springmvc-config.xml配置-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
5、index.jsp
<a href="${pageContext.request.contextPath}/hello.action">访问服务器</a>
6、在controller包下创建控制器类
@Controller
public class UserController {
@RequestMapping("/hello.action")
public String sayHello(){
return "main";
}
}
@RequestMapping
此注解就是来映射服务器访问的路径.
1)此注解可加在方法上,是为此方法注册一个可以访问的名称(路径)
@RequestMapping("/demo.action")
public String demo(){
System.out.println("服务器被访问到了.......");
return "main"; //可以直接跳到/admin/main.jsp页面上
}
<a href="${pageContext.request.contextPath}/demo.action">访问服务器</a>
2)此注解可以加在类上,相当于是包名(虚拟路径),区分不同类中相同的action的名称
@RequestMapping("/user.action")
public class DemoAction1 {..}
<a href="${pageContext.request.contextPath}/user/demo.action">访问服务器</a>
3)此注解可区分get请求和post请求
@Controller
public class ReqAction {
@RequestMapping(value = "/req.action",method = RequestMethod.GET)
public String doGetTest(){
System.out.println("我是处理get请求的........");
return "main";
}
@RequestMapping(value = "/req.action" ,method = RequestMethod.POST)
public String doPostTest(){
System.out.println("我是处理post请求的........");
return "main";
}
}
/**
通过此案例可以发现,对于相同的请求路径,使用RequestMethod.POST/RequestMethod.GET
可以进入不同的方法
*/
5中数据提交方式的优化
总结
1、表单name对应action方法形参
2、表单name对应实体类属性,action 形参类实体类
3、 仅限于超链接或地址拦提交数据,链接中的数据值以'/'分割
action方法上的@RequestMapping(value="/{占位字符串1}/{占位字符串2}/submit.action")
占位字符串对应action形参名,且需要使用@PathVariable解析
4、表单name与action方法形参名不对应时,在形参名前添加@RequestParam("表单name")
5、action方法形参类型使用 (HttpServletRequest request),方法内部和以前一样
1)方法形参名
<%--1、表单中name对应方法形参名--%>
<form action="${pageContext.request.contextPath}/submit1.action" method="post">
<input type="text" name="student_name"/>
<input type="text" name="studentNo"/> <!--测试是否区分大小写-->
<input type="submit" value="submit"/>
</form>
//接收数据
@RequestMapping(value={"/submit1.action"})
public String getData1(String student_name, int studentNo){//㊟:此处的形参名区分大小写,所以要和表单中的完全一致
System.out.println(student_name + studentNo);
return "main";
}
2)封装实体类
<%--
中文乱码问题:
当前使用的是Tomcat10 ,post 和 get提交的中文乱码问题都已经被解决了
Tomcat8之前的版本没有解决post乱码,但get请求的乱码问题可能已经解决
所以对于低版本的Tomcat的post乱码需要解决
实体类:
private String name;
private int age;
--%>
<form action="${pageContext.request.contextPath}/submit2.action" method="post">
<input type="text" name="name"/>
<input type="text" name="age"/>
<input type="submit" value="submit2"/>
</form>
@RequestMapping(value="/submit2.action")
public String getDataByPojo(Student student){//此处的Student类并不在包扫描中,可正常运行
System.out.println(student);
return "main";
}
3)动态占位符提交
<a href="${pageContext.request.contextPath}/郭洪宇/20111022/submit3.action">submit3</a>
@RequestMapping(value = "/{name}/{no}/submit3.action")
public String getDataByPlaceholder(
//测试后发现 如果占位的字符串与形参不对应,会报500错误
@PathVariable
String name,
@PathVariable
int no){
System.out.println(name + no);
return "main";
}
4) 映射名称不一致
<form action="${pageContext.request.contextPath}/submit4.action" method="post">
<input type="text" name="uname"/>
<input type="text" name="uage"/>
<input type="submit" value="submit4"/>
</form>
@RequestMapping(value = "/submit4.action")
public String getDataAndInconsistentMapping(
@RequestParam("uname")
String name,
@RequestParam("uage")
int age){
System.out.println(name + age);
return "main";
}
5) HttpServletRequest
<form action="${pageContext.request.contextPath}/submit5.action" method="post">
<input type="text" name="name"/>
<input type="text" name="age"/>
<input type="submit" value="submit5"/>
</form>
@RequestMapping(value = "/submit5.action")
public String getDataByHttpServletRequest(HttpServletRequest request){
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println(name + age);
return "main";
}
乱码问题
在web.xml文件中添加类CharacterEncoding类
<!--中文乱码响应-->
<!--
private String encoding;//编码格式
private boolean forceRequestEncoding;//请求编码,默认false,不开启
private boolean forceResponseEncoding;//响应编码,默认false,不开启
-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ajax请求
pom.xml
spring-webmvc
junit
jakarta.servlet-api
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--导入jQuery--%>
<script src="js/jquery-3.3.1.js"></script>
</head>
<body>
<a href="javascript:show()">点击显示学生信息</a>
<div id="mydiv">
等待响应.....
</div>
<script type="text/javascript">
function show() {
$.ajax({
url :"${pageContext.request.contextPath}/list.action",
type :"get",
dataType : "json",
success:function(responseList){
let s = "";
$.each(responseList,function (i,stuInfo){
s+= stuInfo.name +"------"+ stuInfo.age +"</br>";
});
$("#mydiv").html(s);
}
});
}
</script>
</body>
</html>
pojo
private String name;
private int age;
...
action
/**
* @Auther: 34838
* @Date: 2023/2/2 18:41
* @Description:
* 生成Student对象封装入List返回
* jackson-databind自动转化实体对象成JSON并响应给前端
*/
@Controller
public class StudentListAction {
@ResponseBody
@RequestMapping("/list.action")
public List<Student> classToJson(){
List<Student> list = new ArrayList<>();
list.add(new Student("小郭",22));
list.add(new Student("小张",21));
list.add(new Student("小刘",22));
return list;//jackson-databind自动转化实体对象成JSON并响应给前端
}
}
web.xml → CharacterEncodingFilter 、DispatcherServlet
springmvc-config.xml
<!--
添加context & mvc 命名空间
-->
<context:component-scan base-package="com.springmvc.controller"/>
<mvc:annotation-driven/>
跳转方式
前提回顾:
转发:携带的数据依旧存在,转发的过程发生在"服务器",由服务器转发到新的界面
重定向:全新的请求,重定向的过程发生在"客户端"
按照我的归类跳转方式为
- springmvc 默认返回值拼接转发
- forward: 转发action/转发页面
- redirect: 重定向action/重定向页面
- HttpServletRequest HttpServletResponse 转发
- HttpServletRequest HttpServletResponse 重定向
- 举例:
@RequestMapping("/forward/test.action")
public String requestForwarding(){
System.out.println("forward:转发开始");
// return "forward:/admin/main.jsp"
return "forward:/ghy/other.action";\
//注意此处的/ghy是路径的一部分,不是项目名
// return "forward:/temp/test.jsp";
}
@RequestMapping("/redirect/HttpServlet.action")
public void redirectHttpServlet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("HttpServlet重定向开始");
response.sendRedirect(request.getContextPath() + "/admin/main.jsp");
//在使用HttpServletResponse 进行页面的重定向时需要加上项目名
}
/*
总结:
springmvc-config.xml文件中 前缀 /admin/ 后缀 .jsp
1、不使用forward: & redirect: 时,返回值自动拼接前后缀
2、forward: & redirect: 都是以webapp为起始点,最前面加'/' 开始
3、使用 forward: & redirect: 跳转文件夹前后缀无用,
都需要加上文件夹名,admin文件夹也要加
4、使用HttpServletResponse 进行重定向需要加上项目名
常用默认参数[可传递数据]
HttpServletRequest
HttpServletResponse
Model
Map
ModelMap
index.jsp
<a href="${pageContext.request.contextPath}/data.action?haha=22">springmvc param</a>
action:
@Controller
public class DataParamAction {
@RequestMapping("/data.action")
public String forwardDataTest(
HttpServletRequest request,
HttpSession session,
Model model,
Map map,
ModelMap modelMap){
//待转发的数据
Student stu = new Student("张三", 45);
request.setAttribute("requestStu",stu);
session.setAttribute("sessionStu",stu);
model.addAttribute("modelStu",stu);
map.put("map_stu",stu);
modelMap.addAttribute("modelMapStu",stu);
return "forward:/data/data.jsp";
}
}
webapp/data/data.sp
HttpServletRequest ${requestStu}<br>
HttpSession session${sessionStu}<br>
Model model${modelStu}<br>
Map map${map_stu}<br>
ModelMap modelMap${modelMapStu}<br>
错误写法:\${haha} ${haha}<br>
正确写法:\${param.haha}${param.haha}<br>
日期
单个日期
总结:@DateTimeFormat 加 mvc:annotation-driven/
springmvc-config.xml
添加mvc命名空间
开启注释解析器
<mvc:annotation-driven/>
index.jsp
<form action="${pageContext.request.contextPath}/date.action" method="post">
<input type="date" name="datePlaceholder1"/>
<input type="date" name="datePlaceholder2"/>
<input type="submit"/>
</form>
//简单日期格式刷:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@RequestMapping("/date.action")
public String testDateParam(
@DateTimeFormat(pattern = "yyyy-MM-dd")Date datePlaceholder1,
@DateTimeFormat(pattern = "yyyy-MM-dd")Date datePlaceholder2){
//注意此处形参与表单name保持一致
System.out.println("原始值:" + datePlaceholder1);
System.out.println("SimpleDateFormat: " + sdf.format(datePlaceholder1)); //注意此处形参与表单name保持一致
System.out.println("原始值:" + datePlaceholder2);
System.out.println("SimpleDateFormat: " + sdf.format(datePlaceholder2));
return "main";
}
/*
原始值:Wed Feb 08 00:00:00 CST 2023
SimpleDateFormat: 2023-02-08
原始值:Mon Feb 20 00:00:00 CST 2023
SimpleDateFormat: 2023-02-20
*/
多个日期,全局注解
使用@InitBinder注解,不需要添加mvc:annotation-driven/
<form action="${pageContext.request.contextPath}/date2.action" method="post">
<input type="date" name="datePlaceholder1"/>
<input type="date" name="datePlaceholder2"/>
<input type="submit"/>
</form>
@InitBinder
public void initBinderForDate(WebDataBinder webDataBinder){
webDataBinder.registerCustomEditor(
Date.class,new CustomDateEditor(sdf,true)
);
}
@RequestMapping("/date2.action")
public String testDateParams(Date datePlaceholder1,Date datePlaceholder2){
//注意此处形参与表单name保持一致
System.out.println("原始值:" + datePlaceholder1);
System.out.println("SimpleDateFormat: " + sdf.format(datePlaceholder1)); //注意此处形参与表单name保持一致
System.out.println("原始值:" + datePlaceholder2);
System.out.println("SimpleDateFormat: " + sdf.format(datePlaceholder2));
return "main";
}
在jsp界面处理日期
1、添加依赖:[Tomcat 10版]
<!-- https://mvnrepository.com/artifact/org.glassfish.web/jakarta.servlet.jsp.jstl -->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>2.0.0</version>
</dependency>
<!--
org.glassfish.web的jakarta.servlet.jsp.jstl
在使用时就用2.0.0的版本,在此基础上时可以使用 core核心库的
如果使用3.0.1的版本,使用core就会抛出异常
-->
[Tomcat 低版本]
<!--理论上可行,未经过测试
出现问题看看
https://www.jianshu.com/p/e8b35b8ad9ab
文件在 V:\02-工具环境\JAR\JavaWeb\JSTL
https://blog.csdn.net/Ruiskey/article/details/115363117
-->
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--如果不行的话试试下面的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2、index.jsp
<a href="${pageContext.request.contextPath}/date3.action">date数据在前端处理</a>
3、模拟数据传给前端
//Date数据在前端转化并展示
@RequestMapping("/date3.action")
public String showDate(HttpServletRequest request) throws ParseException {
//模拟一个数据传输给前端
/*Person
private String name;
private Date birthday;
*/
Person jack = new Person("Jack", sdf.parse("2001-11-02"));
request.setAttribute("person",jack);
return "forward:/date/date.jsp";
}
4、数据处理 date.jsp
1- 导入格式化标签库
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
2- 使用
<fmt:formatDate value="${person.birthday}" pattern="yyyy-MM-dd"/>
get、set方法上使用
可以在
Person 中的birthday属性上添加 @DateTimeFormat(patter="yyyy-MM-dd")注解
或者在set方法上添加
如果返回的是JSON格式数据在get方法上添加
[具体内容看文档.doc]
WEB-INF资源访问受限问题
举例:
webapp文件结构:
webapp
WEB-INF
jsp
hello.jsp
web.xml
index.jsp
spring-config.xml:
<!--包扫描-->
<context:component-scan base-package="com.nfjh.springmvc.controller"/>
web.xml
<!--前置控制器DispatcherServlet 不在赘述-->
@Controller
public class UserController {
//此处的.action
@RequestMapping("/hello.action")
public String sayHello(){
return "/WEB-INF/jsp/hello.jsp";
//如果spring-config.xml中添加了前缀 /WEB-INF/jsp/ 和后缀.jsp
//则此处可以直接写 return "hello";
}
}
此目录下的动态资源,不可直接访问,只能通过请求转发的方式进行访问
访问路径:
http://localhost:8080/项目名/hello.action
简单登录功能
结构:
@Controller
public class LoginController {
//从loginAction进入登录界面login.jsp
@RequestMapping("/loginAction")
public String toLogin(){
return "login";
}
@RequestMapping("/loginCheck")
public String loginCheck(String name, String passwd, HttpServletRequest request){
//假设 name: hello
// passwd : world
if (name.equals("hello")&&passwd.equals("world")){
//成功跳转hello.jsp
return "hello";
}else{
//验证失败回到/loginAction
request.setAttribute("mgs","用户名或密码不正确!");
return "login";
}
}
}
springmvc-config.xml
<!--包扫描-->
<context:component-scan base-package="com.nfjh.springmvc.controller"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
web.xml
<!--前置控制器DispatcherServlet-->
但是过滤器的过滤路径变成了'/'
<url-pattern>/</url-pattern>
http://localhost:8080/springmvc_05_webInf_war/loginAction
拦截器
SpringMVC的拦截器
针对请求和响应进行的额外的处理.在请求和响应的过程中添加预处理,后处理和最终处理.
拦截器执行的时机
1)preHandle():在请求被处理之前进行操作,预处理
2)postHandle():在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果,后处理
3)afterCompletion:所有的请求响应结束后执行善后工作,清理对象,关闭资源 ,最终处理.
拦截器实现的两种方式
1)继承HandlerInterceptorAdapter的父类
2)实现HandlerInterceptor接口,实现的接口,推荐使用实现接口的方式
HandlerInterceptor实现三步
1、存储数据到session中
2、实现HandlerInterceptor接口
3、在springmvc-config.xml文件中注册拦截器
[springmvc_05_webInf]
@Controller
public class LoginController {
//从loginAction进入登录界面login.jsp
@RequestMapping("/loginAction")
public String toLogin(){
return "login";
}
@RequestMapping("/loginCheck")
public String loginCheck(String name, String passwd, HttpServletRequest request){
//假设 name: hello
// passwd : world
if (name!=null&&passwd!=null&&
name.equals("hello")&&passwd.equals("world")){
//成功跳转hello.jsp
//1、拦截器实现-存储数据到session
request.getSession().setAttribute("name",name);
return "hello";
}else{
//验证失败回到/loginAction
request.setAttribute("mgs","用户名或密码不正确!");
return "login";
}
}
@RequestMapping("/other")
public String otherPage(){
return "other";
}
}
public class LoginInterceptor implements HandlerInterceptor {
//拦截器预处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//取出Session中的用户数据进行判断
HttpSession session = request.getSession();
Object name = session.getAttribute("name");
if (name==null){
request.setAttribute("mgs","用户未登录,请登录!!");
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
}
return true;
}
}
<!--拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截路径(全部)-->
<mvc:mapping path="/**"/>
<!--放行路径-->
<mvc:exclude-mapping path="/loginAction"/>
<mvc:exclude-mapping path="/loginCheck"/>
<!--实现拦截器接口的类-->
<bean class="com.nfjh.springmvc.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
不是,关于登录验证的不能拦截,首页展示不能拦截
SpringMVC-nfjh的更多相关文章
- 【分享】标准springMVC+mybatis项目maven搭建最精简教程
文章由来:公司有个实习同学需要做毕业设计,不会搭建环境,我就代劳了,顺便分享给刚入门的小伙伴,我是自学的JAVA,所以我懂的.... (大图直接观看显示很模糊,请在图片上点击右键然后在新窗口打开看) ...
- Springmvc数据校验
步骤一:导入四个jar包 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=" ...
- 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了,随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑 ...
- 【SSM框架】Spring + Springmvc + Mybatis 基本框架搭建集成教程
本文将讲解SSM框架的基本搭建集成,并有一个简单demo案例 说明:1.本文暂未使用maven集成,jar包需要手动导入. 2.本文为基础教程,大神切勿见笑. 3.如果对您学习有帮助,欢迎各种转载,注 ...
- 快速搭建springmvc+spring data jpa工程
一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...
- redis集成到Springmvc中及使用实例
redis是现在主流的缓存工具了,因为使用简单.高效且对服务器要求较小,用于大数据量下的缓存 spring也提供了对redis的支持: org.springframework.data.redis.c ...
- 流程开发Activiti 与SpringMVC整合实例
流程(Activiti) 流程是完成一系列有序动作的概述.每一个节点动作的结果将对后面的具体操作步骤产生影响.信息化系统中流程的功能完全等同于纸上办公的层级审批,尤其在oa系统中各类电子流提现较为明显 ...
- springMVC学习笔记--知识点总结1
以下是学习springmvc框架时的笔记整理: 结果跳转方式 1.设置ModelAndView,根据view的名称,和视图渲染器跳转到指定的页面. 比如jsp的视图渲染器是如下配置的: <!-- ...
- springMVC初探--环境搭建和第一个HelloWorld简单项目
注:此篇为学习springMVC时,做的笔记整理. MVC框架要做哪些事情? a,将url映射到java类,或者java类的方法上 b,封装用户提交的数据 c,处理请求->调用相关的业务处理—& ...
- springmvc的拦截器
什么是拦截器 java里的拦截器是动态拦截action调用的对象.它提供了一种机制可以使 ...
随机推荐
- 高斯判别分析GDA推导与代码实现
高斯判别分析GDA推导与代码实现 生成学习 处理分类问题,我们可以使用逻辑回归.Softmax.这两种方法都属于"判别学习",也就是给定 \((x^{(i)}, y^{(i)})\ ...
- 统一单点登录&跳转
在客户端输入地址(xxx.xxx.xxx/controll/方法/参数) 服务端到controll层 进行数据匹配 跳转 前端响应情况 图片跳转与列表跳转 图片:编写跳转函数 列表公用图片跳转函数,@ ...
- mariadb 集群 重启的几种情形
最近项目中遇到过一次mariadb集群中三台节点有两台挂掉的情况.总结了一下mariadb集群可能遇到的几种情况需要重启. 环境说明: mariadb集群ip: 10.0.101.51 10.0.10 ...
- Jenkins+maven+gitlab+harbor+Rancher
- 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的互转 JAVA
package com.asiabasehk.cgg.util; /**火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的互转 * Created by macremote on 1 ...
- css样式 div垂直水平居中对齐
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- DevExpress.XtraEditors.ComboBoxEdit 只能选择不能在里面 输入数据
DevExpress.XtraEditors.ComboBoxEdit 只能选择不能在里面 输入数据 combboxEdit.propoties.textEditStyle=DisableTextEd ...
- GPIO原理及配制方法
GPIO原理及配制方法 引用地址: ARM SOC芯片的GPIO结构示意图 @@@ GPIO的八种模式 1,输入浮空模式 2,输入上拉模式 3,输入下拉模式 4,模拟输入模式 5,开漏输出模式 6,开 ...
- 为什么要有jvm,jvm的作用?
jvm的两个作用:第一.运行并管理java源码文件所生成的Class文件.第二.在不同的操作系统上安装不同的jvm,从而去实现跨平台的一个保障. 一般情况下,即使不熟悉jvm的运行机制,也不影响业务代 ...
- 面试官:MySQL一千万数据,怎么快速查询?
前言 面试官:来说说,一千万的数据,你是怎么查询的? me:直接分页查询,使用limit分页. 面试官:有实操过吗? me:肯定有呀 此刻献上一首<凉凉> 也许有些人没遇过上千万数据量的表 ...