文章目录
1. JavaWeb 入门
Web:网页,Web可分为两种:
- 静态Web:
- 比如:html,css。
- 提供给人看的数据永远都不会变化。 也就是无法与数据库交互,也无法动态更新。
- 可以直接访问,比如:www.baidu.com/index.html。前提是服务器允许你访问该静态Web
- 动态Web:
- 几乎所有的网站都是动态Web。
- 提供给人看的数据会变化,比如淘宝上面的用户名,电商里面会根据你最近经常浏览的商品再推荐更多类似的商品。也就是会跟数据库交互,动态更新数据。
- 使用到的技术(后台技术):Java,PHP,Servlet/JSP(JSP了解一下就行)
- 缺点:动态Web资源如果出现错误,那么得停机维护,重新编写后台程序。
所以,在Java中,开发动态Web资源的技术统称为 JavaWeb。
1.1 Web应用程序
Web应用程序:可以提供浏览器访问的程序; 比如:
- html,css等,这些Web资源可以被外部访问,对外界提供服务。
- 通过URL,能够访问到的任何一个页面或资源,都存在于这世界某一角落的计算机上。
一个Web应用由静态Web和动态Web组成,动态Web会嵌套在静态Web中。若想要给外界访问还需要提供一个服务器,让它来统一管理这些资源。
1.2 Web服务器
Web服务器跟服务器不一样,服务器就是我们电脑,而Web服务器就是可以处理浏览器等Web客户端的请求并返回相应响应,也可以放置网站文件等。
看下有趣的历史:
-
使用ASP来开发Web页面,基本一个页面都有几千行的业务代码,页面极其换乱 ,不易维护。
-
然后就换成PHP,PHP开发速度很快,功能很强大,跨平台,代码很简单 ,但是随着互联网的扩大,流量越来越大,无法承载大访问量的情况 。
-
出现了JSP/Servlet,提供两种架构:B/S:浏览器和服务器 和 C/S: 客户端和服务器。 sun公司主推 B/S架构,使用的是 Java语言,可以承载三高问题带来的影响。
三高问题:
高可用:尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间。
高并发:增加服务器内存的大小,更多人同时访问,更多线程。
高性能:提高CPU速度,优化程序,处理任务快。
额外:都2020了,
JSP
可以了解,不过现在开发基本都不会用。需要注意的是,并不是电脑只安装了jre
就可以运行所有Java程序,使用JSP
部署 Web应用程序,此一看是在运行Java程序,但是应用程序服务器会将JSP
转换为Java servlet
,这个过程需要使用JDK
来编译。servlet
。
服务器是一种被动的操作,用来处理用户的一些请求和给用户一些响应信息 。
常见的服务器有:
- IIS(了解):由微软提供,Windos自带的,如果没有则得自己开启。
- Tomcat:推荐使用!!性能稳定,而且是免费的,还提供源代码,属于轻量级应用服务器(Application Server)。也是Apache,Sun等共同开发的一个项目。Tomcat在JVM上运行,处理的是JSP页面和Servlet。
- Nginx:可以跟Tomcat配合,属于HTTP Server。功能:参考知乎:作者 David
- 反向代理:代表资源服务器来回应客户端的请求,就跟被告和被告律师一样,你也可以自己辩解(资源服务器),也可以让被告律师辩解(Nginx),不过谁跟专业点呢(参考下面HTTP Server的解释)?
- 动静资源分离:通过反向代理分发请求,使得所有动态资源的请求都交给Tomcat,而静态资源的请求(例如图片、视频、CSS、JavaScript文件等)则直接由Nginx返回到浏览器(Nginx并没有处理,只是分发),这样能大大减轻Tomcat的压力。
- 负载均衡:当业务压力增大时,可能一个Tomcat的实例不足以处理,那么这时可以启动多个Tomcat实例进行水平扩展,而Nginx的负载均衡功能可以把请求通过算法分发到各个不同的实例进行处理。
正向代理:代表客户端去访问服务器。
- Apache:也是HTTP Server,只支持静态页面,不过可以通过插件支持PHP,还可以与Tomcat连通。Nginx是Apache目前最大的竞争对手,Apache是同步多进程模型,一个连接对应一个进程,而Nginx是异步的,多个连接(万级别)可以对应一个进程。具体情况
- …
- …
HTTP Server 关心的是 HTTP 协议层面的传输和访问控制,虽然Application Server也有,但不是很强大。
1.3 Tomcat
安装和配置网上一大推。。。虽然下面的东西可以在IDE中配置更方便,但是如果在Linux中安装Tomcat并部署一个网站的话,还是需要认识目录结构和一些相关东西。虽然SpringBoot也有内置Tomcat,但是内置的Tomcat有漏洞的话,SpringBoot得升级,可能代码要大改动,所以可以移除使用我们自己的Tomcat。
看下Tomcat的目录结构:
启动和关闭Tomcat,在bin目录下:
配置:
可以配置启动的端口号
- tomcat的默认端口号为:8080,可以修改下面代码中的prot
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
- mysql默认端口号:3306;http默认端口号:80;https默认端口号:443
可以配置主机的名称:
- 默认的主机名为:localhost->127.0.0.1,修改后还得去电脑中的hosts中修改127.0.0.1的映射。
- 默认网站应用存放的位置为:webapps
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
具体其他配置:百度一下。
面试题:请你谈谈网站是如何进行访问的?
- 输入一个域名;回车
- 检查本机的 C:\Windows\System32\drivers\etc\hosts配置文件下有没有这个域名映射;
- 有:直接返回对应的ip地址,这个地址中,有我们需要访问的web程序,可以直接访问
- 没有:去DNS服务器找,找到的话就返回,找不到就返回404;
一个网站应有的结构:
--webapps :Tomcat服务器的web目录
-ROOT
-xxx :网站的目录名
- WEB-INF
-classes : java程序
-lib:web应用所依赖的jar包
-web.xml :网站配置文件
- index.html 默认的首页
- static
-css
-style.css
-js
-img
-.....
1.4 HTTP
HTTP(超文本传输协议)是一个简单的请求-响应协议,它通常运行在TCP之上。
- 文本:html,字符串, ….
- 超文本:图片,音乐,视频,定位,地图…….
- 端口号:80
HTTPS是安全的超文本传输协议,端口号为:443
HTTP的两个版本:(面试考)
- HTTP/1.0:客户端可以与web服务器连接后,只能获得一个web资源,断开连接。
- HTTP/1.1:客户端可以与web服务器连接后,可以获得多个web资源。
因为HTTP内容还是很多,待另开章补充,推荐看《图解HTTP》。
2. 什么是Servlet
虽然MVC框架封装了Servlet,也会听到有人说可以不学,但是我觉得Servlet还是挺重要的,就是数据交互,学会了到时候前后端交互,传什么数据过来,需要传什么数据回去就很清晰。
Servlet是一个接口,是运行在服务器上的一个 Java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端。服务器都有很多Servlet,每一个Servlet的功能不一样,等待请求。
Servlet的生命周期:在Servlet API就可以看到
- void init(ServeletConfig):出生(一次),只有当客户端访问时才创建对应的Serlvet对象。
- void service(ServletRequest request, ServletResponse response):每次处理请求都会被调用。
- void destroy():服务结束时会调用一次,在服务器停止或者服务器把该web应用移除且卸装 Servlet 时执行该方法,类似遗言。
Servlet的特性:
- 单例,一个Servlet类只有一个对象。也可能存在多个Servlet类。
- 线程不安全,所以它的效率是最高的(异步)。
Servlet的实例化是由服务器创建,而且得首次访问时才会创建。
看下Servlet的源码:
public interface Servlet {
void init(ServletConfig var1) throws ServletException; // 在首次访问时才会创建对象
ServletConfig getServletConfig(); // 获取Servlet配置信息
// ServletRequest var1:这个就是来接收客户端的请求
// ServletResponse var2:响应客户端
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo(); // 可以提供有关 servlet 的信息,如作者、版本、版权。
void destroy(); // 在服务器停止且卸装 Servlet 时执行该方法
}
ServletConfig:(这个的理解得看代码)
public interface ServletConfig { // 可以看到它是一个接口,具体实现交给GenericServlet
String getServletName(); // 获取<servlet-name>中的内容
ServletContext getServletContext(); // 获取Servlet上下文对象,非常重要
String getInitParameter(String var1); // 通过名称获取指定Servlet初始化参数的值,基本不会用到
Enumeration<String> getInitParameterNames(); // 获得所以Servlet初始化参数的名称,基本不会用到
}
对于Serlvet接口,Sun公司有两个默认的实现类:HttpServlet,GenericServlet。因为我们是面向HTTP协议的,所以使用好HttpServlet就行。
HttpServlet 源码的一些方法:
// 强制将两个参数转为http协议相关的类型,然后调用本类的service(HttpServletRequest req, HttpServletResponse resp)
public void service(ServletRequest req, ServletResponse res)
// 会通过request得到当前的请求的请求方法,例如:GET,POST等,然后根据请求方法去调用doGet()或doPost()等
protected service(HttpServletRequest req, HttpServletResponse resp)
所以如果使用HttpServlet,只需要重写doGet(),doPost()方法即可。其他的请求方法类似,比如还有PUT,DELETE等也是一样,不过这是在Restful风格才会用到的。一个继承HttpServlet的类,可以同时重写doGet(),doPost()等方法,或者只写一个,不会限制一个Servlet只能处理一种请求方式。
2.1 使用Servlet
可能需要导入servlet的包,这里可以使用Maven的依赖配置:需要创建Maven项目:
然后引入jar包:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
我这里也出现这个:(第一个也可以在线下载,第二个就是上面Maven那个)
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author flunggg
* @date 2020/7/13 14:51
* @Email: chaste86@163.com
*/
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter(); // 响应流
writer.println("hehe"); // 正常情况在访问时会在浏览器打印
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
我们写的是JAVA程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以我们需要再web服务中注册我们写的Servlet,还需给他一个浏览器能够访问的路径,在web.xml中配置:(这个是xml版,还有配置版)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<!--注册Servlet-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>zhou.HelloServlet</servlet-class>
</servlet>
<!--Servlet的请求路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
然后还需要配置下Tomcat:
点击Fix,就会弹出来一个框,再点击就可以添加:(这里我不知道怎么了,本来会给出两个可以直接点击用的,然后我是弹出下面这个。其实重启一下IDEA就可以)
其实可以看到Tomcat中自带JSP,和Servlet的包,运行时可能会冲突,如果冲突去掉自己依赖的servlet包。
访问:http://localhost:8080/servlet/hello
我喜欢用注解版:
/**
* @author flunggg
* @date 2020/7/13 14:51
* @Email: chaste86@163.com
*/
// name名称随意,反正不能跟其他Servlet相同,urlPatterns就是我们可以访问该Servlet的路径
@WebServlet(name = "HelloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter(); // 响应流
writer.println("hehe"); // 正常情况在访问时会在浏览器打印
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
但是我配置完,发现访问不了,原因是因为web.xml的问题:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
</web-app>
可能出现的问题:
- version=“4.0”,一定要大于3.0。
- metadata-complete:如果不配置该属性,或者将其设置为false,则表示启动注解支持。但为false时,表示web.xml和注解对于Servlet的影响同时起作用,两种方法定义的url-partten都可以访问到该Servlet,但是当通过web.xml定义的url-partten访问时,注解定义的属性将失效。
然后就可以访问成功了。
2.2 Servlet原理
Servlet容器/Web服务器:Servlet没有main()方法。Servlet受控于另一个Java应用,这个Java应用称为容器。Tomcat就是这样一个容器。如果web服务器应用得到一个指向servlet的请求(而不是其他请求,如请求一个普通的静态HTML),此时服务器不是把这个请求交给servlet本身,而是交给部署该servlet的容器,要由容器调用servlet的方法,如doPost()或doGet()。
Servlet容器的功能:
-
通信支持:利用容器提供的方法,你能轻松地让servle与Web服务器对话;
-
生命周期管理:容器控制着servlet的生与死。它会负责加载类、实例和初始化servlet、调用servlet方法,并使servlet实例能够被垃圾回收。
-
多线程支持:容器会自动地为它接收的每个servlet请求创建一个新的java线程。针对客户机的请求,如果servlet已经运行完相应的HTTP服务方法,这个线程就会结束(也就是会死掉)。
-
声明方式实现安全:利用容器,可以使用XML部署描述文件配置(和修改)安全性,而不必将其硬编码写到servlet(或其他)类代码中。
-
JSP支持:负责把JSP代码翻译成真正的java。
Servlet的执行过程:(非常重要)
- 第一步:客户端向Web服务器发出指向servlet的http请求。
- 第二步:Web服务器先检查是否已经创建该Servlet的实例,如果为未创建则创建,如果创建,则直接调到第三步。
- 第三步:Web服务器创建ServletRequest对象并把客户端的http请求信息封装进去,并且创建ServletResponse对象。
- 第四步:调用该Servlet的service方法,先把Request对象和Response对象转为HttpServletRequest对象和HttpServletResponse对象,然后根据HttpRequest对象中的请求方法(GET,POST等),调用相应的方法(doGet()或doPost()等)。
- 第五步:执行doGet()或doPost()等完毕后,把想要返回给客户端的结果封装进HttpReHsponse对象,然后返回给客户端。
(图的话可能有点乱)
关于Servlet的路径配置,如果要想任何访问都调用一个servlet可以使用路径:/* , * 就是一个通配符,还可以这样:flung/* ,这样:*.aaa 等。
但是关于通配符是有优先级关系的,指定了固有的映射路径优先级最高,如果找不到就会走默认的处理请求 。
2.3 ServletContext
web容器在启动的时候,它会为每个Web项目都创建一个对应的ServletContext对象,ServletContext可以理解为Servlet数据共享区,也可以称为中间商或上下文对象,一个Web项目只有一个ServletContext对象,可以在N个Servlet中来获取这个唯一的对象,使用它可以给多个Servlet传送数据。
ServletContext的生命周期:随着Web容器的启动而创建,随着Web容器的关闭而消亡。
2.3.1 共享数据
把数据保存在一个Servlet,在另一个Servlet可以拿到。
/**
* @author flunggg
* @date 2020/7/14 9:28
* @Email: chaste86@163.com
*/
@WebServlet(name = "SetServlet", urlPatterns = "/set")
public class SetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// getServletContext()已经在GenericServlet父类写好了,使用this来获取
ServletContext servletContext = this.getServletContext();
// 传入数据,设置键值对,这跟Session相同,其实ServletContext跟Session有关系的,待说
servletContext.setAttribute("username", "小明");
}
}
/**
* @author flunggg
* @date 2020/7/14 9:31
* @Email: chaste86@163.com
*/
@WebServlet(name = "GetServlet", urlPatterns = "/get")
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 想要获取数据,还是得先获取ServletContext对象
ServletContext servletContext = this.getServletContext();
String username = (String) servletContext.getAttribute("username");
// 如果数据中有中文,在响应时必须设置编码,固定下面这两句
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
// 也可以这样:
// response.setHeader("Content-type", "text/html;charset=UTF-8");
// resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().print("名字:" + username);
}
}
对于中文,注意需要设置编码问题,上面那两句设置编码的可以直接死记。
输出结果:
- 情况一:先直接访问 /get,会发现:名字:null,说明拿不到值;
- 情况二:先访问 /set,在访问 /get,输出:名字:小明。
- 情况三:经过情况二,然后几次刷新 /get,都会发现该值已经存在 ServletContext 中了,除非去主动销毁(servletContext.removeAttribute(“username”);)或者停下Web服务器,否则一直存在该键值对。
注意:使用setAttribute(),如果键相同值不同,会覆盖掉原先值。
2.3.2 获得初始化参数
可以预先在web.xml配置一些键值对,只要Web服务器一启动,就可以获取到。跟上面不同的是上面是访问一个Servlet才设置一些键值对。
<!--配置一些web应用初始化参数-->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
</context-param>
/**
* @author flunggg
* @date 2020/7/14 9:50
* @Email: chaste86@163.com
*/
@WebServlet(name = "GetInitServlet", urlPatterns = "/getInit")
public class GetInitServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String url = servletContext.getInitParameter("url");
resp.getWriter().println(url);
}
}
2.3.3 请求转发
表面上看起来是在访问这个Servlet,实际上访问另一个Servlet,或者这样理解访问一个Servlet,会跳转到另一个Servlet。
/**
* @author flunggg
* @date 2020/7/14 10:00
* @Email: chaste86@163.com
*/
@WebServlet(name = "DispatcherServlet", urlPatterns = "/forward")
public class DispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/hello"); // 转发的请求路径
requestDispatcher.forward(req, resp); // 调用 forward实现转发,转发需要带入HttpServletRequest,HttpServletResponse到另一个Servlet
}
}
熟练的话可以这样写:
/**
* @author flunggg
* @date 2020/7/14 10:00
* @Email: chaste86@163.com
*/
@WebServlet(name = "DispatcherServlet", urlPatterns = "/forward")
public class DispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.getServletContext().getRequestDispatcher("/hello").forward(req, resp);
}
}
转发跟重定向:
- 转发,可能会携带当前访问的页面的某些信息/数据,然后跳转到另一个页面,但是URL不变。
- 重定向,也是跳转到另一个页面,只是简单的页面跳转不会携带数据,但是URL会变。
所以重定向只能重定向到webapp中的页面,对于目前的项目结构来说:
resp.sendRedirect("/index.jsp");
访问时还需要注意Tomcat的路径,我是:http://localhost:8081/,就可以访问到index.jsp,如果是这样:http://localhost:8081/xxx/,那么应该这样写:(如何修改Tomcat的访问路径?到IDEA右上角那个Tomcat中,先点进点Tomca配置,再点进Deployment中的Application contxt那修改)
resp.sendRedirect("/xxx/index.jsp");
2.3.4 读取资源文件
像在Spring,SpringBoot中都有这个后缀文件:.properties,它的作用可以存放一些对数据库连接的配置等等。
在resources目录下新建 .properties文件,然后填写:
username=root
password=123456
现在先用路径来找到这个文件,先启动Tomcat,就会在项目中出现一个target文件:
/**
* @author flunggg
* @date 2020/7/14 10:35
* @Email: chaste86@163.com
*/
@WebServlet(name = "PropertiesServlet", urlPatterns = "/properties")
public class PropertiesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 根据上面的路径,ser是我的项目名,在项目中的 /WEB-INF/classes/db.properties
InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
String root = properties.getProperty("username");
String password = properties.getProperty("password");
resp.getWriter().println(root + " " + password);
}
}
2.4 HttpServletResponse
web服务器接收到客户端的http请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,代表响应的一个HttpServletResponse;
- 如果要获取客户端请求过来的参数:HttpServletRequest
- 如果要给客户端响应一些信息:HttpServletResponse
负责向浏览器发送数据的方法
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
其他可以设置响应头的方法…
在HttpServletResponse中还可以看到很多状态码,也提供一个方法设置状态码。
HttpServletResponse除了向浏览器发送信息,还有其他功能:
2.4.1 下载文件(了解)
步骤:(了解)
- 要获取下载文件的路径
- 截取下载的文件名
- 设置响应让浏览器能够支持下载我们需要的东西
- 获取下载文件的输入流
- 创建缓冲区
- 获取OutputStream对象
- 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
/**
* @author flunggg
* @date 2020/7/14 14:47
* @Email: chaste86@163.com
*/
@WebServlet(name = "DownServlet", urlPatterns = "/down")
public class DownServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 要获取下载文件的路径,如何看路径,还是在项目的target文件看
String realPath = this.getServletContext().getRealPath("/WEB-INF/classes/2.png");
System.out.println(realPath);
// 2. 下载的文件名是啥
String fileName = realPath.substring(realPath.lastIndexOf(File.separator) + 1);
// 3. 设置响应让浏览器能够支持下载我们需要的东西,不用记这个头
// 如果文件名带有中文,还得设置中文编码: URLEncoder.encode()
// 如果设置响应编码是没用的,我试了:resp.setContentType("text/html;charset=UTF-8");
resp.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 4. 获取下载文件的输入流
FileInputStream input = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6. 获取OutputStream对象
ServletOutputStream output = resp.getOutputStream();
// 7. 将FileOutputStream流写入到buffer缓冲区, 使用OutputStream将缓冲区中的数据输出到客户端
while((len = input.read(buffer)) != -1) {
output.write(buffer);
}
output.close();
input.close();
}
}
2.4.2 重定向
这个在上面转发那里讲了,需要用到的是:
resp.sendRedirect();
需要传入参数,一个路径,表示的是要重定向到哪个网页。回想登录后会跳转,上面的URL会变。
面试题:请你聊聊重定向和转发的区别?
- 相同点
- 页面都会实现跳转
- 不同点
- 请求转发的时候,url不会产生变化,状态码:307
- 重定向时候,url地址栏会发生变化,状态码:302
重定向的使用场景:避免在用户重新加载页面时两次调用相同的动作。比如如果使用转发,在注册时,提交表单,服务器会进行保存,但是如果此时重新刷新页面,可能又会执行一次保存,因为刷新完又发送相同的访问请求(相同url),但如果使用重定向,可以在提交表单后重定向到另一个页面,就是发送不同的url,所以即使刷新了也不怕重复提交表单。
2.5 HttpServletRequest
HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,HTTP请求中的所有信息会被封装到HttpServletRequest,通过这个HttpServletRequest的方法,获得客户端的所有信息;
可以获得前端传递过来的参数:(比如表单)(中间两个方法)
需要注意的是,Servlet接收的HttpServletRequest中包含中文,如果没设置HttpServletRequest的编码,则乱码。
req.setCharacterEncoding("utf-8");
还可以获取项目路径:写路径时就不会写死。
req.getContextPath();
3. Cookie,Session
3.1 会话
会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话。
有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学,曾经来过,称之为有状态会话。
无状态会话:跟上面的意思相反,你来就来,我不需要记住你是否来过。
一个网站,怎么证明你来过?
- 服务端给客户端一个 信件,客户端下次访问服务端带上信件就可以了; 这个信件就是 Cookie
- 服务器登记你来过了,下次你来的时候我来匹配你; 这使用到 Seesion
3.2 保存会话的两种技术
Session:
- 服务器技术,利用这个技术,可以保存用户的会话信息(比如用户登录信息)。 一个用户对服务器的多次连贯性请求!所谓连贯性请求,就是该用户多次请求中间没有关闭浏览器!会话就是对话,可共享数据。
- Session生命周期:会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束!
Cookie:
- 客户端技术 (响应,请求),Cookie是HTTP协议制定的!由服务器保存Cookie发送到浏览器,浏览器会把Cookie保存到本地,再下次浏览器请求服务器时把上一次请求得到Cookie再归还给服务器。
- 其实Cookie就是由服务器创建保存到客户端浏览器的一个键值对,允许有多个键值对。
- Cookie生命周期:首次访问服务器则创建,默认情况下,关闭浏览器则销毁。
3.3 Cookie
Http协议是无状态协议,不会保留什么数据,只管传。
Http协议规定(保证不给浏览器太大压力):
-
1个Cookie只能保存一个信息
-
1个Cookie最大4KB
-
1个服务器最多向1个浏览器保存20个Cookie
-
1个浏览器最多可以保存300个Cookie
Cookie 的应用:
- 服务器使用Cookie来跟踪客户端状态!识别用户。
- 显示上次登录名(也是一个用户多个请求),这个很常见。
Cookie是不能跨浏览器的!
Cookie的使用:
原始方式(了解):
- 使用response发送Set-Cookie响应头:response.addHeader(“Set-Cookie”, “aaa=AAA”)
- 使用request获取Cookie请求头:req.getHeader(“Cookie”)
便捷方式(掌握):
- 使用response.addCookie()方法向浏览器保存Cookie,需要new一个Cookie,传入键值对,必须都是字符串。
- 使用request.getCookies()方法获取浏览器归还的Cookie,返回一个Cookie[]
来看看Cookie的部分源码:
// 为什么只能传字符串,因为总是来回传,如果数据太大,传输需要时间,影响性能
private String name; // 键
private String value; // 值
// Cookie的最大生命,即Cookie可保存的最大时长。以秒为单位
// maxAge > 0 : 浏览器会把Cookie保存到客户机硬盘上,有效时长为maxAge的值决定。
// maxAge < 0 : Cookie只在浏览器内存中存在,当用户关闭浏览器时,浏览器进程结束,同时Cookie也就死亡了。
// maxAge = 0 : 浏览器会马上删除这个Cookie!
private int maxAge = -1;
private String path;
private String domain;
Cookie的path属性:
- 并不是设置这个Cookie在客户端的保存路径!!!
- Cookie的path由服务器创建Cookie时设置,当浏览器访问服务器某个路径时,需要归还哪些Cookie给服务器呢?这由Cookie的path决定。
- 浏览器访问服务器的路径,如果包含某个Cookie的路径,那么就会归还这个Cookie。
- 代码:cookie.setPath(“url”)
- 例如:aCookie.path=/day11_1/ 和 bCookie.path=/day11_1/jsps/
- 访问:/day11_1/index.jsp时,归还:aCookie
- 访问:/day11_1/jsps/a.jsp时,归还:aCookie、bCookie
Cookie的domain属性(了解):
- domain用来指定Cookie的域名!当多个二级域中共享Cookie时才有用。
- 例如: www.baidu.com、zhidao.baidu.com、news.baidu.com、tieba.baidu.com 之间共享Cookie时可以使用domain
- 设置domain为:cookie.setDomain(".baidu.com");
- 设置path为:cookie.setPath("/");
Cookie不能存储中文会乱码,如果想要存储,必须这样:
URLEncoder.encode("小明","utf-8"); // 编码
获取时:
URLDecoder.decode(cookie.getValue(),"UTF-8"); // 解码
Cookie不能存储特殊字符,比如:空格、分号(;)、逗号(,)、等号(=),暂时测试这4个,其他:(), $, !, -可以存储。
Cookie Version 1规定中放宽了限制,可以使用某些特殊字符,但是考虑到新版本的Cookie规范目前仍然没有为所有的浏览器所支持,因而为保险起见,我们应该在Cookie的内容中尽量避免使用这些字符。
Cookie虽然不能存储特殊字符,但是可以可以对特殊字符进行加密来存储,使用URLEncoder;解密来获取,使用URLDecode,看下面代码:
/**
* 记录上次访问的时间
* @author flunggg
* @date 2020/7/14 20:26
* @Email: chaste86@163.com
*/
@WebServlet(name = "WalkServlet", urlPatterns = "/walk")
public class WalkServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 浏览器默认会给几个
Cookie[] cookies = req.getCookies();
PrintWriter writer = resp.getWriter();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 加密/编码
String encoding = URLEncoder.encode(simpleDateFormat.format(new Date()), "utf-8");
boolean flag = true;
if(cookies != null) {
for(Cookie cookie : cookies) {
if("lastTime".equals(cookie.getName())) {
// 因为cookie.getValue()包含特殊字符,直接获取会出错
// String value = cookie.getValue();
// 解密
String decode = URLDecoder.decode(cookie.getValue(), "utf-8");
writer.println("你上次访问的时间为:" + decode);
}
}
}
// 存储Cookie时,值传入加密的
resp.addCookie(new Cookie("lastTime", encoding));
}
}
3.4 Session(重要)
服务器会为每个客户端创建一个Session对象,Session就好比客户在服务器端的账户,它们被服务器保存到一个Map中,这个Map被称之为Session缓存!
获取Session对象:
HttpSession session = request.getSession();
request.getSession(false)、request.getSession(true)、request.getSession():
- 后两个方法效果相同。
- 第一个方法:如果Session缓存中,找不到对应的Session,那么返回null,并且不会创建Session对象。
服务器会给每一个用户(浏览器)创建一个Session对象后,会执行下面两条代码:
Cookie cookie = new Cookie("JSESSIONID",sessionId); // sessionId是创建Session对象分配的,JSESSIONID其实就是身份证
resp.addCookie(cookie);
所以Session跟Cookie是息息相关的。
Session特点:
- 服务器会给每一个用户(浏览器)创建一个Session对象;Session是服务器端对象,保存在服务器端!!!
- 一个Seesion独占一个浏览器,只要浏览器没有关闭,这个Session就存在。
- Session的数据没有大小的限制,而且数据是安全的。(Cookie数据是不安全的,因为我们在浏览器可以看到)
- 因为Http协议是无状态协议,当传输完就关闭,如果客户端在网站跳转它的子页面,用户信息会传不过去,导致得重新登录,所以需要Session。有了Session,用户登录之后,整个网站它都可以访问!–> 保存用户的信息;保存购物车的信息……
Session的使用:常用三个方法:
void setAttribute(String var1, Object var2);
Object getAttribute(String var1);
void removeAttribute(String var1);
// 其他方法,了解
void invalidate() // 让session失效!调用这个方法会被session失效,当session失效后,客户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId;--- 有些网站登陆后可以退出,这就要废掉session
void setMaxInactiveInterval() // 设置session可以的最大不活动时间(秒)
int getMaxInactiveInterval() // 获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;
String getId() // 获取sessionId;
boolean isNew() // 查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新。
跟ServletContext一样,那么功能主要是前后端共享数据。下面会说到它们的区别
HttpSession原理:其实就是:request.getSession()的步骤:
- 获取客户端的Cookie中的 JSESSIONID (跟身份证一样),然后到Session缓存查询
- 如果SessionId不存在,创建Session,把Session保存起来,把新创建的SessionId保存到Cookie中
- 如果SessionId存在,通过SessionId查找Session对象,如果没有查找到,创建Session,把Session保存起来,把新创建的SessionId保存到Cookie中;如果通过sessionId查找到了session对象,那么就不会再创建session对象了。
- 如果创建了新的Session,浏览器会得到一个包含了SessionId的Cookie,这个Cookie的生命为-1,即只在浏览器内存中存在,如果不关闭浏览器,那么Cookie就一直存在。
- 下次请求时,再次执行request.getSession()方法时,因为可以通过Cookie中的SessionId找到Session对象,所以与上一次请求使用的是同一Session对象。
一些细节:
-
当客户端关闭后,服务器不关闭,两次获取的session是否为同一个?
- 默认情况下,不是。
- 如果想要相同,可以创建Cookie,写一个 JSESSIONID和值,并设置MaxAge,让Cookie持久化保存。
-
客户端不关闭,服务器在此期间被重启一次,两次获取的session是同一个吗?
- 不是同一个。但是要确保数据不丢失,得这样做:
- Session钝化:在服务器正常关闭之前,将Session对象序列化到磁盘上;
- Session活化:在服务器启动后,将Session文件转化为内存中的Session对象。
- 其实Tomcat会自动帮我们做完这两件事。如果是本地Tomcat,在Session钝化时,会在Tomcat目录下的
work\Catalina\localhost
会生成你的项目名文件,然后在里面会生成一个文件,是存储Session对象的文件;当Tomcat启动时会把该Session加载进内存,然后用户又可以读取到。 - 在IDEA中会在这里生成:
C:\Users\chast\.IntelliJIdea2019.2\system\tomcat\Unnamed_ser
, 在里面会看到work
- 不是同一个。但是要确保数据不丢失,得这样做:
- 场景:像淘宝双十一抢购,我们会先登进网站等待(创建Session),但是可能长时间不操作,会有很多Session占用内存,浪费内存;而为了更好的处理,使用了Session的钝化和活化,将长时间无操作的Session放进磁盘,等到要用时再从磁盘拿到内存来。也有可能会设置Session的生命周期,如果超过一定时间,该Session就会失效,用户下次要用时可能要重新登录。
-
Session什么时候被销毁?
- 服务器关闭;
- Session对象调用invalidate()方法,主动销毁
- Session默认失效时间,30分钟。
默认失效时间也可以修改,在项目中的web.xml:(也可以使用Session的setMaxInactiveInterval())
<!--设置Session默认的失效时间--> <session-config> <!--15分钟后Session自动失效,以分钟为单位--> <session-timeout>15</session-timeout> </session-config>
也可以在Tomcat中的conf的web.xml设置,它是所有项目的web.xml的父配置文件。
Session除了Cookie来实现,还可以使用URL重写来实现,因为在浏览器我们可以禁用Cookie,那么就需要用URL重写来实现Session:
// 有两个功能:转码,和创建Session,需要传入一个Servlet的URL,URL必须是指向本站的URL。
// 它会查看Cookie是否存在,如果不存在,在指定的URL后添加JSESSIONID参数,如果Cookie存在,则不会再URL后添加日任何东西。
resp.encodeURL();
网上看到一条对Session不错的总结:
- 在客户端访问服务器时,服务器会给每一个用户创建一个Session对象,由于Session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它Web资源时,其它Web资源再从用户各自的session中取出数据为用户服务。
- 在第一次调用request.getSession()方法时,服务器会检查是否已经有对应的session,如果没有就在内存中创建一个并返回。
当一段时间内,session没有被使用(默认是30分钟),服务器会销毁该session。如果服务器非正常关闭(强行关闭),还未到期的session也会被销毁。 - 可以主动调用session的invalidate()方法销毁session。
参考:作者:Zhuang_ET,链接:https://www.jianshu.com/p/6c02951267d8
面试题:为什么在分布式部署的时候使用Session有问题?
分布式部署指的是有多台服务器处理请求,假设有3台编号为1,2,3。一般会使用nginx的负载均衡能力,让请求能均分到每个服务器,如果有空闲的就先分配到空闲的。此时有一个请求被分到编号1处理,会创建session,分配sessionid,当下一次请求还是这个sessionid,但是此时编号1处理的业务繁忙,被分配到编号2处理,此时找不到对应的sessionid,就又会新建session。也就是说同一个客户端发送的请求如果每次都在不同的服务器处理就会产生不同的session,而且原先的session在有效的生命周期内都无法用也会变成垃圾内存,如果有上万个这样的客户端,使得内存被大量占用,会影响性能,甚至系统崩溃。
解决:
- 粘性Session,比如以固定IP来分配固定服务器,但是这样就影响了服务器之间的负载均衡。
- 同步Session,比如一个客户端发送请求给服务器处理,服务器会创建session,并且会把该session同步到其他服务器共享,所以每一个服务器都存了这个session。但是做了同步就会产生性能影响,对每个服务器之间还会增加耦合度。
- 共享session,单独搞一台专门存储session的服务器,每一个服务器就处理请求时会从该服务器创建session或者拿session。但是如果该服务器挂了,GG。如果要搞集群服务器,那跟同步session没什么区别。
- 可以存到数据库中,并且做数据库集群存储备份,但是这种方式是存储在硬盘的,比起从内存中读取数据来说,很慢。但是目前有非关系型数据,比如Redis,所以最终把Session存储到Redis。这是目前的解决方案。
4. JSP(了解)
我知道JSP不用学,但是发现JSP里面的一些概念挺重要的,需要了解JSP的一些概念。
4.1 什么是JSP?
Java Server Pages : Java服务器端页面,也和Servlet一样,用于动态Web技术!
最大的特点:
- 写JSP就像在写HTML
区别:
- HTML只给用户提供静态的数据
- JSP页面中可以嵌入JAVA代码,为用户提供动态数据;
4.2、JSP原理
JSP到底怎么执行的?
-
在代码层面还是JSP
-
这时从Tomcat内部工作入手
- 如果是本地Tomcat:
- 如果是IDEA的Tomcat:
- 关于IDEA的Tomcat如果没有则在:下面的路径下找
- 发现JSP页面转变成了Java程序!
看看这个的源码,会发现继承一个HttpJspBase(了解)
// 里面还可以发现三个方法,跟Servlet类似的三个方法
//初始化
public void _jspInit() {
}
//销毁
public void _jspDestroy() {
}
//JSPService
public void _jspService(.HttpServletRequest request,HttpServletResponse response)
如果加入了JSP,来看看客户端访问浏览器的JSP页面的整个过程(了解)
所以可以得到(记一记):浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet! JSP本质上就是一个Servlet
基础语法就不用看了。。
5. 域对象
域表示的是一个作用范围,JavaWeb中有4大域:ServletContext,Session,Request,PageContext。PageContext域只有JSP才有,Servlet只有前面3个。
5.1 ServletContext
生命周期:当Web应用加入Web容器时,就会创建代表整一个Web应用的ServletContext对象,当服务器关闭,ServletContext对象会被销毁。
作用范围:这个Web应用。
作用:这个Web应用中的所有Sevlet共享一个ServletContext对象,所以所有的Servlet对象之间可以通过ServletContext来通讯。
应用:上面有说。
5.2 ServletRequest
生命周期:客户端发送到服务器的请求(对一个Sevlet的URL发起访问),在调用service()前,服务器会创建ServletRequest对象,整个请求结束,ServletRequest生命周期就结束。
作用范围:一次请求。
应用:获取客户端的信息。
5.3 Session
生命周期:服务器会为每个客户端创建一个Session对象,当浏览器长时间不操作,或者关闭浏览器,Session就失效。
作用范围:一次会话。
5.4 PageContext(了解)
只能在JSP用,是四大域中作用范围最小的。
参考: 作者:Zhuang_ET,链接:https://www.jianshu.com/p/6c02951267d8
6. JavaBean
实体类
JavaBean有特定的写法:
- 必须要有一个无参构造
- 属性必须私有化
- 必须有对应的get/set方法;
一般用来和数据库的字段做映射 ORM(对象关系映射);
比如:
- 表—>类
- 字段–>属性
- 行记录---->对象
7. MVC三层架构
MVC: 把各个大写字母拆开得:Model view Controller,中文翻译为模型、视图、控制器 。
以前:
servlet--CRUD-->数据库
弊端:程序十分臃肿,不利于维护
servlet的代码中:处理请求、响应、视图跳转、处理JDBC、处理业务代码、处理逻辑代码
架构:没有什么是加一层解决不了的!
Model
- 业务处理 :业务逻辑(Service,以service命名包),如果是大型的,对于业务逻辑还可以分:service(业务逻辑接口), serviceImpl(业务逻辑实现)
- 数据持久层:CRUD (Dao)
- 实体:JavaBean(以pojo,entity命名包)
View
- 展示数据
- 提供链接发起Servlet请求 (a,form,img…)
Controller (Servlet)
- 接收用户的请求 :(req:请求参数、Session信息….)
- 交给业务层处理对应的代码
- 控制视图的跳转
例如:
登录--->接收用户的登录请求--->处理用户的请求(获取用户登录的参数,username,password)---->交给业务层处理登录业务(判断用户名密码是否正确:事务)--->Dao层查询用户名和密码是否正确-->数据库
可能有时候项目简单,业务逻辑都写在了Controller,而在service层就调用Dao的CRUD。体现不出service的特点。
8. Filter
Filter:过滤器 ,用来过滤网站的数据;
- 处理中文乱码
- 禁用缓存
- 防止脚本攻击
- 支持跨域资源访问(什么是跨域?看下面)
Filter是在javax.servlet包中,在IDEA中可以看到很多Filter,注意包名。Filter是一个接口:
public interface Filter {
void init(FilterConfig var1) throws ServletException;
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
void destroy();
}
其实跟Servlet一样,只不过Servlet已有实现类,而Filter是得我们自己去写。
示例:处理乱码
/**
* @author flunggg
* @date 2020/7/15 12:27
* @Email: chaste86@163.com
*/
// 必须配置,也可以在web.xml配置
@WebFilter(filterName = "CharacterEncodingFilter", urlPatterns = {
"/*"
})
public class CharacterEncodingFilter implements Filter {
//初始化:web服务器启动,就以及初始化了,随时等待过滤对象出现!
public void init(FilterConfig filterConfig) throws
ServletException {
System.out.println("CharacterEncodingFilter初始化");
}
/*Chain : 链
/*
* 1. 过滤中的所有代码,在过滤特定请求的时候都会执行
* 2. 必须要让过滤器继续同行
* chain.doFilter(request,response);
*/
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");
System.out.println("CharacterEncodingFilter执行前....");
// 固定调用
chain.doFilter(request,response); //让我们的请求继续走,如果不写,程序到这里就被拦截停止!
System.out.println("CharacterEncodingFilter执行后....");
}
//销毁:web服务器关闭的时候,过滤会销毁
public void destroy() {
System.out.println("CharacterEncodingFilter销毁");
}
}
什么是跨域?
- 比如:从 http://www.baidu.com 访问到 http://www.baidu.com/inedx.html,这种情况不会跨域,因为协议,域名,端口号相同;
- 从 http://www.baidu.com 访问到 https://www.baidu.com/inedx.html,这种情况会跨域,因为协议不同;
- 从 http://www.baidu.com 访问到 http://www.zhihu.com,这种情况会跨域,因为域名不同;
- 从 http://www.baidu.com 访问到 http://www.zhihu.com,这种情况会跨域,因为域名不同;
- 从 http://aaa.baidu.com 访问到 http://bbb.baidu.com,这种情况会跨域,因为子域名不同(aaa. 和 bbb.);
- 从 http://www.baidu.com:8080 访问到 http://www.baidu.com:4000,这种情况会跨域,因为端口号不同;
浏览器是不允许我们跨域的。
跨域会发送什么问题:
- 无法读取非同源网页的Cookie
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求
除了过滤器,还有拦截器Interceptor,区别:
执行顺序:Filter过滤前处理 --> Interceptor拦截前处理 --> 处理 --> Interceptor拦截后处理 --> Filter过滤后处理
- Interceptor定义在org.springframework.web.servlet,Filter定义在javax.servlet
9 监听器
顾名思义,监听事件,监听浏览器点击,了解就好,基本很少用,因为过滤器可以实现监听器的功能。
使用HttpSessionListener 接口来实现
//统计网站在线人数 : 统计session
@WebListener(value = "OnlineCountListener") // 注册进去才生效
public class OnlineCountListener implements HttpSessionListener {
//创建session监听: 看你的一举一动
//一旦创建Session就会触发一次这个事件!
public void sessionCreated(HttpSessionEvent se) {
ServletContext ctx = se.getSession().getServletContext();
System.out.println(se.getSession().getId());
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount==null){
onlineCount = new Integer(1);
}else {
int count = onlineCount.intValue();
onlineCount = new Integer(count+1);
}
ctx.setAttribute("OnlineCount",onlineCount);
}
//销毁session监听
//一旦销毁Session就会触发一次这个事件!
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext ctx = se.getSession().getServletContext();
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount==null){
onlineCount = new Integer(0);
}else {
int count = onlineCount.intValue();
onlineCount = new Integer(count-1);
}
ctx.setAttribute("OnlineCount",onlineCount);
}
}
10. JDBC
JDBC:Java连接数据库!
JDBC 固定步骤:
- 加载驱动
- 连接数据库,代表数据库
- 向数据库发送SQL的对象Statement : CRUD
- 编写SQL (根据业务,不同的SQL)
- 执行SQL
- 关闭连接
需要的jar包:
- java.sql
- javax.sql
- mysql-conneter-java… 连接驱动(必须要导入)
<!--mysql的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
示例:查询
/**
* @author flunggg
* @date 2020/7/15 17:35
* @Email: chaste86@163.com
*/
class JDBCTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 配置信息
// useUnicode=true&characterEncoding=utf-8 解决中文乱码
// 对于mysql8版本还得加个时区
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&&characterEncoding=utf-8";
String user = "root";
String password = "123456";
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.连接数据库, Connection代表数据库
Connection connection = DriverManager.getConnection(url, user, password);
// 3.向数据库发送SQL的对象Statement
Statement statement = connection.createStatement();
// 4.编写SQL
String sql = "select * from user";
// 5.执行SQL
// boolean execute = statement.execute(sql); // 效率低,可以执行任何SQL语句
// 返回一个结果集
ResultSet resultSet = statement.executeQuery(sql); // 专门执行查询的
while(resultSet.next()) {
System.out.println("id:" + resultSet.getObject("id"));
System.out.println("name:" + resultSet.getObject("name"));
System.out.println("password:" + resultSet.getObject("password"));
System.out.println("email:" + resultSet.getObject("email"));
}
// 6.关闭连接,释放资源(原则:先开后关)
resultSet.close();
statement.close();
connection.close();
}
}
修改:改变第4,5步
// 4.编写SQL
String sql = "insert into user(id, name, password, email) values(5, '周', '123456', 'chaste@163.com')";
// 5.执行SQL
// 返回一个结果集
statement.executeUpdate(sql);// 专门执行修改,删除,插入
预编译:
/**
* @author flunggg
* @date 2020/7/15 17:35
* @Email: chaste86@163.com
*/
class JDBCTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 配置信息
// useUnicode=true&characterEncoding=utf-8 解决中文乱码
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&&characterEncoding=utf-8";
String user = "root";
String password = "123456";
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.连接数据库, Connection代表数据库
Connection connection = DriverManager.getConnection(url, user, password);
// 3.编写SQL
String sql = "insert into user(id, name, password, email) values(null, ?, ?, ?)";
// 4.向数据库拿到预编译
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "小明");
preparedStatement.setString(2, "098978676767");
preparedStatement.setString(3, "123456@qq.com");
// 5.执行SQL
preparedStatement.execute();
// 6.关闭连接,释放资源(原则:先开后关)
preparedStatement.close();
connection.close();
}
}
还有预编译查询,只需要该第三和第4步。
预编译(PreparedStatement)和普通(Statement)的区别:
- PreparedStatement的执行效率快,在数据库处理SQL语句时,都会有一个预编译的过程,也就是把一些格式固定的SQL编译后,放入数据库缓冲池中,当下次执行相同的SQL语句就不需要预编译,直接解析执行(如果带有参数,传参就行)。而使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。
- 提高可读性和易维护性。虽然可能比Statement多写几行代码,但是看起来更加清晰。
// Statement的sql得这样写
String sql = "select * from user where name = '" + userName + "' and password = '" + password + "'";
// PreparedStatement的sql也可以像上面一样,但也提供另外的方法
String sql = "select * from user where name = ? and password = ?";
// 然后只需要
preparedStatement.setString(1, userName);
preparedStatement.setString(2, password);
- 安全问题,防止恶意SQL注入:恶意的SQL语句,比如:
String sql = "select * from user where name = '" + userName + "' and password = '" + password + "'";
假设密码被传入:userName = “小明”, password = “’ or ‘1’ = ‘1’”,sql语句变这样:
String sql = "select * from user where name = '小明' and password = '' or '1' = '1'";
这样数据库中的全部数据就会被获取。如果表的数据比较多,一下子全部拿出来可能会挤爆内存。
而使用了:
// 3.编写SQL
String userName = "小明";
// 因为有or 1=1,所以无论条件满不满足都是恒成立
String userPassword = "' or '1' = '1'";
String sql = "select * from user where name = ? and password = ?";
// 4.向数据库拿到预编译
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, userName);
preparedStatement.setString(2, userPssword);
不会输出结果。
使用Statement会把该表的全部数据全输出。
参考: