java EE面试
HTTP
网络分层模型
TCP/IP五层模型:物理,数据链路,网络,传输,应用
OSI 7层模型:物理,数据链路,网络,传输,会话,表示,应用
干嘛用的?
网络分层就是将网络节点所要完成的数据处理工作,分别交给不同的硬件和软件模块去完成
浏览器中输入url地址到显示主页都经历了什么?
- 在浏览器查找域名的IP地址--DNS解析
- 浏览器向web服务器发送一个Http请求--TCP连接
- 服务器处理请求
- 服务器发回一个HTML响应
- 浏览器开始显示HTML
这些过程都使用了哪些协议
- TCP 与服务器建立连接
- IP 网络发送数据
- OPSF 路由选择
- ARP IP地址转换为MAC地址
- HTTP 访问网页
Http是如何保持长连接的?
长连接:指的是通过三次握手建立起一次连接,可以在此连接内发送多次request请求,http1.0是短链接,http1.1协议默认是长连接,响应头加入Connection:keep-alive
Http和Https的区别
- HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
- HTTP 是不安全的,而 HTTPS 是安全的
- HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
- 在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
- HTTP 无法加密,而HTTPS 对传输的数据进行加密
- HTTP无需证书,而HTTPS 需要CA机构颁发的SSL证书
什么是http协议无状态协议,怎么解决
- 无状态协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,也就是说,当客户端第一次请求完成后,再次发生请求,http并不知道当前客户端是一个老用户
- 可以用cookie来解决无状态的问题,Cookie相当于一个通行证,客户端第一次访问的时候给客户端发送一个Cookie,当客户端再一次请求的时候,带着这个Cookie,那么服务器就知道就是老用户了。
什么是Cookie
网页之间的交互是通过Http协议传输数据的,而Http协议是无状态的协议,状态的协议是什么意思呢?一旦数据提交完后,浏览器和服务器的连接就会关闭,再次交互的时候需要重新建立新的连接。
服务器无法确认用户的信息,于是乎,w3c就提出了:给每个用户都发一个通行证,无论谁访问都需要携带通行证,这样服务器就可以从通行证上确认用户的信息了,这个通行证就是Cookie
Session和Cookie的区别
- 从存储上:Session是存储在服务端的,Cookie存储在客户端的,并且Cookie只能存储字符串,如果要存储非ascii字符串还要对其编码,Session可以存储任何类型的数据,可以把Session看成一个容器
- 从安全上:Session的安全性要比Cookie高,因为cookie存储在客户端,对客户是可见的,而Session存储在服务端。
- 从有效期上:Cookie存储在本地硬盘中,只要设置maxAge属性为比较大的数,即使关闭了浏览器,Cookie还是存在的,Session存储在服务器中,设置maxInactiveInterval(inactive 不活跃的,interval间隔)属性来确定Session的有效值,并且Session依赖于名为Jsessionid的Cookie,如果该cookie的maxage属性小于0,关闭浏览量,那么服务器中的session就会失效。
- Cookie禁用的情况下,Session可以通过URL地址重写来进行会话跟踪。使用所有的访问路径url后面,带一个jsessionid的参数
URI和URL的区别
- URI统一资源标识符,Web上可用的每种资源如HTML文档,图像,视频片段,程序都是URI来定位的
一般由三个部分组成
- 访问资源的命名机制
- 存放资源的主机名
- 资源自身的名称,由路径表示,着重强调与资源
- URL是统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL一般由三部组成:
- ①协议(或称为服务方式)
- ②存有该资源的主机IP地址(有时也包括端口号)
- ③主机资源的具体地址。如目录和文件名等
常用的HTTP相应状态码
1XX:指示信息--表示请求已接收,继续处理
2XX:成功-表示请求已成功接收,理解,接受
3XX:重定向-要完成请求必须进一步的操作
4XX:客户端错误-请求有语法错误或者请求无法实现
5XX:服务端错误--服务器未能实现合法的请求
- 200:请求被正常处理
- 204:请求被受理但没有资源可以返回
- 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
- 301:永久性重定向
- 302:临时重定向
- 303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
- 304:发送附带条件的请求时,条件不满足时返回,与重定向无关
- 307:临时重定向,与302类似,只是强制要求使用POST方法
- 400:请求报文语法有误,服务器无法识别
- 401:请求需要认证
- 403:请求的对应资源禁止被访问
- 404:服务器无法找到对应资源
- 500:服务器内部错误
- 503:服务器正忙
TCP的三次握手
- 首先客户端向服务端发送一段TCP报文,其中:
- 标志位为SYN,表示请求建立新连接,
- 序号为Seq=X(X一般为1),
- 随后客户端进入SYN-SENT阶段
- 服务端接受到来自客户端TCP报文之后,结束LISTEN阶段,并返回一段报文,其中:
- 标志位为SYN和ACK,表示确认客户端的Seq序号有效,服务端能正常接受到客户端发送的数据,并同意创建新连接(即告诉客户端,服务器收到了你的数据),
- 序号为Seq=y
- 确认号位Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值,随后服务端进入SYN-PCVD阶段
- 客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:
- 标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
- 序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
- 确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
- 随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
分析:
第一次:客户端发送请求到服务端,这样服务器知道了客户端正常,自己接受正常,SYN=1,seq=x
第二次:服务器发送给客户端,这样客户端知道自己发送,接收正常,服务器接收正常,发送正常,ACK=1,ack=x+1,SYN=1,seq=y
第三次:就剩服务器还不知道客户端接收是否正常和自己发送是否正常了,这样客户端继续发送给服务端,服务端知道客户端发送,接收正常,自己也发送接收正常了。seq=x+1,ACK=1,ack=y+1
上面分析过程可以看出,握手两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论的。
四次挥手的过程
- 第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
- 第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
- 第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
- 第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。

分析:
第一次:客户端请求断开FIN,seq=u
第二次:服务器确认客户端的断开请求ACK,ack=u+1,seq=v
第三次:服务器请求断开FIN,seq=w,ACK,ack=u+1
第四次:客户端确认服务器的断开ACK,ack=w+1,seq=u+1
为什么连接是三次握手,关闭时四次握手
在关闭连接时,当服务端收到FIN报文时,可能服务端还有些数据还没处理,并不会立即关闭socket,只能先回复一个ACK报文,告诉客户端,你的报文我收到了,只有等到我服务端所有的报文都发送完了才能发送FIN报文,因此不能一起发送,所有需要四次握手
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
谈一下Request的结构
- 请求行
- 请求方法
- 请求资源
- 协议,版本
- 请求报头
- 每个头由一个域名,冒号和域值三部分组成
- 告诉web服务器浏览器接收的语言版本
- 请求web服务器的ip地址和端口号
- Cookies等信息
- 空白行
- 分割报头和请求体的专用行
- 请求体
- get请求方法不传送任何数据,post请求可以传数据
TCP和UDP区别
UDP是不可靠连接,在传输数据之前不需要建立连接,主机在接收UDP报文之后,不需要给任何的确认
TCP是可靠连接,在传输数据之前需要建立连接,数据传送结束之后要释放连接
TCP是如何做到可靠传输
- 分割数据报
- 包编号
- 校验和
- 丢弃重复数据
- 流量控制
- 阻塞控制
- ARQ 协议
- 超时重传
JDBC
jdbc操作数据库的步骤
- 建立数据库驱动
- 建立数据库连接
- 创建一个Statement
- 执行sql语句
- 处理结果集
- 关闭数据库连接
为什么要使用PreparedStatement
- PreparedStatement接口继承Statement,PreparedStatement实例包含已编译的SQL语句,所以执行速度高于Statement
- 作为Statement的子类,PreparedStatement继承了Statement的所有功能,三种方法execut、executeQuery、executeUpdate
- 在jdbc应用中,任何时候都不要使用Statement,原因如下:
- 代码的可读性和可维护性,Statement需要不断的拼接,而preparedStatement不会
- PreparedStatement尽最大可能提高性能.DB有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
- 最重要的一点是极大的提高了安全性。Statement容易被SQL注入,而PreparedStatement传入的内容不会和sql语句发生任何匹配关系。
关系数据库中连接池的机制是什么?
前提:为数据库连接建立一个缓冲池。
- 能够从连接池获取或创建可用的连接
- 使用完毕后,将连接返回给连接池
- 在系统关闭前,关闭所有的连接并释放占有的系统资源。
- 能够处理无效的连接,限制连接池中的连接总数不低于或不超过某个限定值。
Java Web
什么是jsp
- jsp本质上就是一个servlet,它是servlet的一种特殊形式,每个jsp页面都是一个servlet实例
jsp的九大内置对象
request,response,out,pagerContext,session,application,config,page,exception
jsp的四大作用范围
application,session,request,page
forward 和redirect有什么区别
- 转发是服务器行为,重定向是客户端行为
- 转发是一次请求,重定向是两次请求
- 转发共享request,重定向不共享
- 转发效率高,重定向效率低
什么是servlet
servlet是java提供用于开发web服务器应用程序的一个组件,运行在服务端,由servlet容器管理,用来生成动态内容。一个servlet实例是实现了特殊接口servlet的java类,所有自定义的servlet均必须实现servlet接口。
Servlet 和JSP有什么联系?
Servlet是一个特殊的Java程序,能够依靠web服务器的支持向浏览器提供显示内容
JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生产。
JSP更侧重于视图,Servlet更侧重于控制逻辑
谈一下Servlet 的生命周期
Servlet运行在Servlet容器中,其生命周期由容器来管理,Servlet的生命周期通过javax.servlet.Servlet接口中的init(),service和destroy()方法来表示。
Servlet的生命周期包含了下面4个阶段
- 加载和实例化
- 初始化
- 请求处理
- 服务终止
Servlet是线程安全的吗
- Servlet是单例的,对于所有请求都使用一个实例
- 如果有全局变量被多线程使用的时候,就会出现线程安全问题
- 解决这个问题有三种方案:singThreadModel synchronized,不使用全局变量
web.xml可以配置哪些内容
监听器,过滤器,相关参数,会话超时时间,安全验证方式,错误页面
Tomcat的省缺端口是多少,怎么修改
tomcat的主目录,conf/serve.xml文件下修改,把8080改为其他的
get和post的区别
- 数据携带上,get方式在url地址后附带的参数是有限的,其数据容量通常不超过1k;post方式可以在请求的请求体内向服务器发送数据,传送的数据量无限制
- 请求参数的位置上,get方式,请求参数放在url地址后面,以?进行拼接;post方式,请求参数放在HTTP请求包内
- 用途上,get方式通常获取数据,post方式通常提交数据
- get方式比post方式要快
tomcat容器是如何创建servlet类实例?用到了什么原理
当容器启动时,会读取在webapps目录下所有的web应用的web.xml文件,然后对xml文件进行解析,并读取servlet注册信息。然后,将每个应用中注册的servlet都进行加载,并通过反射的方式实例化(有时候也是在第一次请求实例化)
在servlet注册时加上
谈一谈Ajax
Ajax是一种创建交互式网页应用的网页开发技术
Ajax的优势:
- 通过异步模式,提升了用户的体验
- 优化了服务器和浏览器之间的传输,减少不必要的数据往返,减少了带宽占用
- Ajax引擎在客户端运行,承担了一部分本来由服务器承担的压力,从而减少了大用户量下的服务器负载。
- 可以实现局部刷新,在不更新整个页面的前提下维护数据,提升用户体验度。
Linux
Linux怎么关闭进程
通常用ps查看进程pid,用kill命令终止进程
ps命令用于查看当前正在运行的进程
ps -ef|grep java //表示查看所有进程里cmd是java的进程信息
ps -aux|grep java //-aux 显示所有状态
kill -9 [pid] //kill命令用于终止进程 -9表示强迫进程立即停止
Redis
redis的好处
- 速度快,数据存储在内存中的
- 丰富的数据结构,有String,list,hash,set,zset
- 支持事务,操作都是原子的,所谓的原子性就是,对数据的操作要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后会自动删除
Redis使用场景
- String 想知道什么时候封锁一个IP地址,Incrby命令
- Hash 存储用户的信息[id,name,age]
- List 实现最新消息排行
- Set 自动排重,比如微博中将每个人的好友存在集合Set中,求两个人的共通好友,我们只需要求交集即可
- Zset 以某个条件为权重,进行排序;京东:商品详情都会有一个综合排名,还可以按照价格进行排名
Redis持久化机制
- RDB
RDB方式是在指定的时间间隔内将内存中的数据集快照写入到磁盘中,会生成一个dump.rdb文件,方便持久化。恢复直接将快照文件直接读到内存中。 - AOF
AOF方法是以日志的形式来记录每个写操作,也就是将redis的每个写的指令记录下来,不记录读的指令,只允许追加文件,不允许改写文件,会生成一个appendonly.aof文件,开启aof方式是在redis的配置文件中将appendonly设置为yes,要保证数据完全不丢失的话,可以将appendfsync设置为always,也就是只要发生变化就会持久化。
AOF的优点: - 备份机制更稳健,丢失数据的概率更低
- 可读的日志文本,通过操作aof稳健,可以处理误操作
AOF的缺点: - 比RDB占用多的磁盘空间
- 恢复备份速度要慢
- 每次读写都同步的话,有一定的性能压力
- 存在个别bug,造成恢复不能
RDB的优点:节省磁盘空间,恢复数据快,虽然redis在fork时使用了写时拷贝技术,如果数据量大的话也是非常消耗性能的。
RDB的缺点:因为备份周期是在一定间隔时间做一次备份,如果redis意外down掉的话,会丢失最后一次快照后的所有修改
redis是单进程单线程?
redis是单进程单线程的,redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
String类型的值最大容量
512M
redis过期键的删除操作
- 惰性删除,放任键过期不管,但是每次从键空间获取键时,都检查取得的键是否过期,如果过期,就删除该键,如果没有过期,就返回该键。
- 定期删除,每过一段时间,程序就对数据库进行一次检查,删除里面的过期键。
- 定时删除,在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
为什么redis需要把所有数据放到内存中
为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘,所以redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O 速度为严重影响redis 的性能。在内存越来越便宜的今天, redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
redis的同步机制
Redis可以使用主从同步,从从同步,第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存,加载完成后,在通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
什么是缓存穿透,如何解决?
一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量的请求而崩掉。
解决方法
- 使用布隆过滤器,将数据库中所有可能存在的数据哈希到一个足够的大bitmap中,这样一个一定不存在的数据就会被这个bitmap给拦截掉,从而避免了对底层存储系统的查询压力。
- 如果一个查询返回结果为空,我们仍然将这个空结果进行缓存,但它的过期时间会很短,一般不会超过5分钟。
Redis雪崩
缓存同一时间大面积失效,所以,后面的请求都会落到数据库上,造成数据库短时间承受大量请求而崩掉。
解决
- 尽量保证整个redis集群的高可用,发现机器宕机尽快补上,
- 本地ehcache缓存+hystrix限流&降级,避免MySQL崩掉
- 利用redis持久化机制,保存的数据尽快恢复缓存。
双写一致性问题
数据库和缓存双缓存,双写,如何解决一致性问题
- 如果你的系统不是严格要求缓存+数据必须强一致性的话,可以采用Cache-Aside Pattern
- 如果不得不做强一致性时,那么可以使用读请求和写请求串行化,串到一个内存队列里去
- 使用队列串行化的方式会导致系统的吞吐量会大幅度的降低
主从复制和哨兵机制
主从复制的好处:
避免redis单点故障
构建读写分离架构,满足读多写少的应用场景
复制的过程原理
- 当从库和主库建立MS关系后,会向主数据库发送sync命令
- 主库接收到sync命令后,会开始在后台fork保存快照(RDB持久化过程bgsave)并将期间收到的写命令缓存起来。
- 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送到从Redis。
- 当从Redis接收到后,会载入快照文件并且执行收到的缓存命令
- 主Redis每当接收到的写命令都会发送给从Redis,从而保证数据的一致性
哨兵
哨兵的作用是对Redis系统运行情况进行监控,是一个独立的进程,他的功能有两个:
- 监控主数据库和从数据库是否运行正常
- 主数据库出现故障后自动将从服务器转为主服务器
既然有了主从了,为什么还需要集群呢?
即使有了主从复制,每个数据库都需要保存整个集群中的所有数据,容易形成木桶效应,所有还需要集群。
集群中Hash槽和Key的关系
key的有效使用CRC16算法计算出哈希值,再对哈希值对163284取余,得到插槽值。
如何解决集群中一个节点下线,导致插槽区有空挡
在Redis集群中可以使用主从模式实现一个节点的高可用
在该结点宕机后,集群会将该结点的slave转变为master继续完成集群服务。
MySQL
BLOB和TEXT有什么区别
BLOB是一个二进制的对象,可以容纳可变数量的数据,TEXT是一个不区分大小写的BLOB。BLOB和TEXT类型的唯一区别在于对BLOB值进行排序和比较时区分大小写,对TEXT值不区分大小写
now()和current_date()有什么区别
now()命令用于显示当前年份,月份,日期,小时,分钟,和秒
current_date()仅显示当前年份,月份和日期
char()和varchar的区别
char的长度是可变的,而varchar的长度是不可变的,当char值被存储时,它们被用空格填充到特定的长度,检索char值需删除尾随空格。
如果一个表有一列定义为TIMESTAMP,将会发生什么
每当行被更改时,时间戳字段将获取当前时间戳
列设置为AUTO INCREMENT 时, 如果在表中达到最大值,会发生什么情况?
它会停止递增,任何进一步的插入都将产生错误, 因为密钥已被使用。
怎样才能找出最后一次插入时分配了哪个自动增量?
LAST_INSERT_ID 将返回由Auto_increment 分配的最后一个值,并且不需要指
定表名称。
mysql中InnoDB支持的四种隔离级别名称
- read uncommited:读到未提交的数据
- read committed: 脏读,不可重复读
- repeatable read:可重复读
- serializable :串行事物
简述mysql中myISAM和InnoDB
myISAM
- 不支持事务,但每次查询都是原子的
- 支持表级锁,即每次操作是对整个表加锁
- 存储表的总行数
一个myISAM表有三个文件:索引文件,表结构文件,数据文件
InnoDB
- 支持ACID的事务,支持事务的四种隔离级别
- 支持行级锁及外键约束,因此可以支持写并发
SQL中on条件和where条件的区别
数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户。
在使用left join时,on和where条件的区别如下:
- on条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录
- where条件是在临时表生成好后,再对临时表进行过滤的条件,这时已经没有left join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。
四个特征及含义
四个基本要素:原子性,一致性,隔离性,持久性
- 原子性:事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全部不执行。
- 一致性:在事务开始和完成时,数据都必须保持一致状态,这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
- 隔离性:数据库系统提供着一定的隔离机制,保证事务在不受外部并发操作影响的独立环境执行,这意味着事务处理过程的中间状态对外部是不可见的,反之亦然。
- 持久性:事务完成之后,它对数据的修改时永久性的,即使出现系统故障也能够保持。
drop,delete,truncate的区别
drop 直接删除表
truncate 删除表中的数据,在插入数据自增长id又从1开始 trun cate
delete 删除表中的数据,可以加where语句
索引
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,以帮助MySQL高效获取数据,这种数据结构就是索引。
优点
- 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
- 创建唯一索引,可以保证每一行数据的唯一性
- 大大加快检索的速度
- 加速表之间的连接
- 使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间
劣势
- 建立索引需要占用物理空间
- 对表的数据进行增删改查时,索引也要动态维护,降低了数据的维护速度
- 创建和维护索引耗费时间,时间随着数据量的增加而增加
不需要创建索引
- 查询中很少使用或者是参考的列
- 数据量很少的列
- 对于那些定义text、image和bit数据类型
行级锁,表级锁,乐观锁,悲观锁
表级锁:每次操作都锁定在整张表,开销少,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低
行级锁:每次操作锁定一行数据,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率最低,并发度最高
页面锁:开销和加锁时间介于两者之间,会出现死锁,并发度一般
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性
乐观锁更适合解决冲突概率极小的情况,而悲观锁则适合解决并发竞争激烈的情况,尽量使用行锁,缩小加锁粒度,以提高并发处理能力,即使加行锁的时间比表锁要长
并发事务带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户
更新丢失
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一java文件,每个程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档,最后保存其更改副本的编辑人员覆盖前一个程序员的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读 事务A读取到了事务B已修改但尚未提交的数据
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些脏数据,并据此做进一步的处理,就会产生未提交的数据依赖关系,这种现象被形象的叫做脏读。
一句话:事务A读取到了事务B已修改但尚未提交的数据,还在这个数据基础上做了操作,此时,事务B进行回滚,A读取的数据无效,不符合一致性要求。
不可重复读 事务A读取到了事务B已经提交的数据
一个数据在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读取出的数据已经发生了变化,或某些记录已经被删除了,这种现象就叫做不可重复读。
一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性。
幻读 事务A读取了事务B提交的新增数据
一个数据按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其条件查询的新数据,这种现象就称为幻读。
一句话:事务A读取到了事务B提交的新增数据,不符合隔离性
多说一句;幻读和脏读有点类似,脏读是事务B里面修改了数据,幻读是事务B里面新增了数据。
更新丢失可以通过加锁来解决,“脏读”、“不可重复读”、“幻读”属于数据库一致性问题,由数据库提供一定的事务隔离机制来解决
事务隔离方式实现
- 在读取数据前,对其进行加锁,阻止事务对数据进行修改
- 不加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照,并用这个快照提供一定级别的一致性读取,这种方式也叫做多版本并发控制,简称MVCC或MCC
事务隔离度级别
数据库的隔离越严格,并发副作用越小,付出的代价也就越大,隔离的实质就是在一定程度上使事务串行化,这显然与并发是矛盾的。
未提交读(Read uncommitted)
已提交读(Read committed)
可重复读(Repeatable read)
可序列化(Serializable)
innodb和myisam的区别
- innodb支持事务,myisam不支持
- innodb支持行级锁,myisam支持表级锁
- innodb支持外键,myisam不支持
- innodb支持MVCC,myisam不支持
- innodb不支持全文索引,myisam支持
- 应用场景:
MyISAM:做很多count计算、插入不频繁、查询非常频繁、没有事务、查询快
InnoDB:对事务要求比较高、可靠性要求高、表更新相当频繁、并发写入高 - DELETE操作:
MyISAM:先drop表,然后重建表
InnoDB:一行一行删除 - 查询表的行数不同:
MyISAM:只是简单的读出保存好的行数
InnoDB:不保存具体行数,执行count(*)时要扫描一整个表来计算有多少行
innodb引擎的四大特征
- 插入缓存(insert buffer)
- 二次写(double write)
- 自适应哈希索引
- 预读
MyISAM会比InnoDB的查询要快
- 数据块:InnoDB要缓存,MyISAM只缓存索引块
- InnoDB寻址要映射到块再到行,MyISAM记录的直接是文件的OFFSET,定位比InniDB要快
- InnoDB还要维护MVCC一致
varchar和char的区别以及varchar(50)50的含义
区别:char是一种固定长度的类型,varchar则是一种可变长度的类型
含义:最多存放50个字符,varchar(50)和varchar(200)存储hello所占空间一样,但后者在排序时会消耗更多的内存,因为order by col采用fixed_length计算col长度
int(10)10的含义:是指显示字符的长度,最大为255,仍占4字节存储,存储范围不变
int范围
有符号的整型范围是-2147483648~2147483647
无符号的整型范围是0~4294967295(2^32)
int(10)的意思是假设有一个变量名为id,它的能显示的宽度能显示10位。在使用id时,假如我给id输入10,那么mysql会默认给你存储0000000010。当你输入的数据不足10位时,会自动帮你补全位数。假如我设计的id字段是int(20),那么我在给id输入10时,mysql会自动补全18个0,补到20位为止。
innodb的事务与日志实现方式
- 日志种类
错误日志:记录出错信息,也记录一些警告或是正确的信息
查询日志:记录所有对数据请求信息,不论是否得到正确执行
慢查询日志:设置一个阈值,将运行时间超过该值的所有SQL语句都记录进去
二进制日志:记录对数据库执行更改的所有操作 - 事务是如何通过日志实现的
事务日志是通过redo和innodb的存储引擎日志缓冲来实现的,当开始一个事务的时候,
会记录该事务的lsn号,当事务执行时,会往innodb存储引擎日志缓存里面插入事务日志,
当事务提交时,必须将存储引擎的日志缓存写入磁盘,也就是写数据前,要先写日志
innodb引擎的行锁怎么实现的
是基于索引来完成行锁的
例如:select * from a_table where id = 1 for update;
for update 可以根据条件来完成行锁定,并且id是有索引键的列,如果id不是索引键,那么innodb将完成表锁
中间件
消息队列在项目中的使用
在分布式系统中是如何处理高并发的。 由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞。比如说,大量的insert,update之类的请求同时到达数据库MYSQL,直接导致无数的行锁表锁,甚至会导致请求堆积很多。从而触发 too many connections 错误。使用消息队列可以解决【异步通信】
MQ的三个作用
- 异步
使用消息队列可以对业务进行异步处理减少响应时间 - 解耦
- 消息发送者和消息接收者没有直接耦合,达到了解耦的目的
- 消息队列使利用发布-订阅模式工作,消息发送者发布消息,一个或多个消息接受者订阅消息
- 对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计
- 削峰
- 通过异步处理,将短时间并发产生的事务消息存储在消息队列中,从而削平高峰的并发事务
- 在电商一些秒杀,促销活动中,使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。
MQ带来的问题
- 降低系统可用性
- 在加入mq之前业务逻辑是顺序执行的,加入mq业务可以异步执行,但是需要考虑到mq的消息丢失和mq挂掉的问题
- 提高系统复杂性
- 在加入mq之后,需要保证消息没有被重复消费,处理消息丢失的情况,保证消息传递的顺序性等问题
- 存在一致性的问题
- 消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度,但是万一消息真正消费者并没有正确消费消息怎么办,这样反而影响了正常的业务逻辑
JMS和AMQP
JMS消息服务
JMS的客户端之间可以通过JMS服务进行异步的消息传输
JMS的API是一个消息服务的规范,运行应用程序组件基于JavaEE平台创建,发送,接收和读取消息
AMQP:高级消息队列协议
是一个提供统一消息服务的应用层标准,是应用协议的一个开放标准
为面向消息的中间件设计,兼容JMS
基于此协议的客户端与消息中间件可传递消息,不受客户端/中间件不同产品,不同开放语言等限制。
Mybatis
#{}和${}的区别
- #{}解析传递进来的参数数据
- ${}传递进来的参数原样拼接在sql中
- #{}是预编译处理,${}是字符串替换
- 使用#{}可以有效的防止SQL注入,提高系统安全性
mybatis中类的属性名和表中字段名不一样怎么办
- 设置别名
通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类中的属性名一致
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
- 在Mapper映射文件通过
来映射字段名和实体类属性名的一一对应关系
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
<!–用id属性来映射主键字段–>
<id property=”id” column=”order_id”>
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
<result property = “orderno” column =”order_no”/>
<result property=”price” column=”order_price” />
</reslutMap>
- 在全局配置文件中开启驼峰命名规则,将数据库中下划线映射为驼峰命名
如何获取自动生成的主键值
通过lastinsertid()获取刚插入记录的自增主键值,在insert语句执行后,执行select LASTINSERTID()就可以回去自增主键
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO USER(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>
在mapper如何中传递多个参数
- 使用占位符思想
{0},#{1}方式或@param注解方式
- map集合
3.java bean传参
mybatis与Hibernate的区别
- Hibernate属于全自动的ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的,而Mybatis是一个半自动的ORM框架,因为mybatis需要自己编写sql
Mapper接口
- 接口的全限名,就是映射文件中的namespace,接口的方法名就是映射文件中MapperStatement的id值,接口方法内的参数就是传给sql的参数
- Mapper接口没有实现类,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement
接口绑定有几种实现方式,分别是怎么实现的
- 一种是通过注解绑定,就是在接口的方法上加上@Select@Update等注解里面包含Sql语句来绑定
- 另一种是是通过xml里面写sql来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名
Mybatis一级缓存,二级缓存
1)一级缓存: 基于PerpetualCache 的HashMap 本地缓存,其存储作用域为
Session,当Session flush 或close 之后,该Session 中的所有Cache 就
将清空, 默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap
存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,
如Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要
实现Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置
3)对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存
Namespaces)的进行了C/U/D 操作后,默认该作用域下所有select 中的缓存将
被clear。
实现一对一有几种方式
有联合查询和嵌套查询,
联合查询是几个表联合查询,只查询一次,通过resultMap里面配置association节点配置一对一的类就可以完成
嵌套查询是先查一个表,再根据这个表的结果的外键id,去再和另一个表里面的查询数据,也是通过association配置,但另一个表的查询是通过select属性配置。
实现一对多有几种方式,怎么操作的
有联合查询和嵌套查询。
联合查询是几个表联合查询,只查询一次,通过在
resultMap 里面的collection 节点配置一对多的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过
配置collection,但另外一个表的查询通过select 节点配置。
是如何将sql执行结果 封装为目标对象并返回的,都有哪些映射形式
第一种是使用
第二种是使用sql列别名功能,将列的别名书写为对象属性名
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给
对象的属性逐一赋值并返回,那些找不到映射关系的属性, 是无法完成赋值的。
模糊查询like 语句该怎么写
第一种:直接传参法:在java代码添加sql通配符
string wildcardname = "%smi%";
list<name> names = mapper.selectlike(wildcardname);
<select id="selectlike">
select * from foo where bar like #{value}
</select>
第二种:在sql语句中拼接通配符,会引起sql注入
string wildcardname = "smi";
list<name> names = mapper.selectlike(wildcardname);
<select id="selectlike">
select * from foo where bar like "%"${value}"%"
</select>
第三种:CONCAT()函数
MySQL的 CONCAT()函数用于将多个字符串连接成一个字符串,是最重要的mysql函数之一。
List<RoleEntity> selectBykeyWord(@Param("keyword") String keyword);
<select id="selectBykeyWord" parameterType="string" resultType="com.why.mybatis.entity.RoleEntity">
SELECT
*
FROM
t_role
WHERE
role_name LIKE CONCAT('%',#{keyword},'%')
OR
id LIKE CONCAT('%',#{keyword},'%')
OR
role_type LIKE CONCAT('%',#{keyword},'%')
</select>
第四种:MyBatis的bind
List<RoleEntity> selectBykeyWord(@Param("keyword") String keyword);
<select id="selectBykeyWord" parameterType="string" resultType="com.why.mybatis.entity.RoleEntity">
<bind name="pattern" value="'%' + keyword + '%'" />
SELECT
*
FROM
t_role
WHERE
role_name LIKE #{pattern}
OR
id LIKE #{pattern}
OR
role_type like #{pattern}
</select>
Mybatis优缺点
什么是mybatis
mybatis是一个半ORM(对象关系映射)框架,内部封装了jdbc,开发只需要关注sql本身,不需要去处理驱动加载,创建连接,创建statement等繁琐的过程,我们只要编写原生的sql,可以控制sql执行性能,灵活度高
mybatis优点
- sql直接写在xml里面,解除了sql与程序代码的耦合,便于统一管理
- 于jdbc相比,减少了大量的代码,不需要手动开关连接
- 很好的与各种数据库兼容
mybatis的缺点
- sql编写量大,尤其是字段多,关联表多时,对开发人员有一定的sql语句编写功底
- sql依赖于数据库,导致数据库移植性差,不能随意更换数据库
Mybatis的xml映射文件中,不同的xml文件,id是否可以重复
如果配置了namespace,那么当然可以重复,因为我们的Statement实际上就是namespace+id
Mybatis是如何分页的
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
举例:
select * from student
拦截sql后重写为:
select t.* from (select * from student)t limit 0,10
简述Mybatis的插件运行原理
原理:
Mybatis仅可以编写针对ParameterHander、ResultSetHandler、StatementHander、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
以及如何编写一个插件?
实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
Mybatis是否支持延迟加载,如果支持,它的实现原理是什么
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是1对1,collection指的就是1对多查询,在mybatis配置文件中,可以配置是否启动延迟加载lazyLoadingEnabled=true|false
它的原理是,使用CGLIB创建目标对象的代理对象,当调动目标方法时,进入拦截器方法,比如调用a.getB.getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
Mybatis都有那些Executor执行器
- SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
- ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
- BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
使用Mybatis的mapper接口调用时有哪些要求
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
- mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
- Mapper.xml文件中的namespace即是mapper接口的类路径
Spring
Spring 的作用域有什么区别
- singleton 在SpringIOC容器中仅存在一个Bean实例,Bean以单实例的方式存在
- prototype 每次调用getBean()都会返回一个新实例
- request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
- session 同一个HTTP Session共享一个Bean,不同的HTTP Session适用不同的Bean,该作用于仅适用于WebApplicationContext环境
简单介绍一下Spring bean的生命周期
- bean定义:在配置文件里用
来进行定义 - bean初始化:有两种方式初始化
- 在配置文件中通过指定init-method属性来完成
- 实现initializingBean接口
- bean调用:有三种方式可以得到bean实例,并进行调用
- bean销毁:两种方式
- 配置文件指定destroy-method属性
- 实现DisposeableBean接口
spring中常用的注解
注解装配在spring中时默认关闭的,所以需要在spring的核心配置文件中配置一下才能使用基于注解的装配模式。
<context-annotation-config />
常用的注解:
@Component :这将java 类标记为bean。它是任何Spring 管理组件的通
用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境
中。
@Controller :这将一个类标记为Spring Web MVC 控制器。标有它的
Bean 会自动导入到IoC 容器中。
@Service :此注解是组件注解的特化。它不
会对@Component 注解提供任何其他行为。您可以在服务层类中使用
@Service 而不是@Component,因为它以更好的方式指定了意图。
@Repository :这个注解是具有类似用途和功能的@Component 注解的特
化。它为DAO 提供了额外的好处。它将DAO 导入IoC 容器,并使未经检查
的异常有资格转换为Spring DataAccessException。
@Requires注解有什么作用
应用于bean属性的setter方法,此注解仅指示必须在配置时使用bean定义中的显式属性值或者使用自动装配填充受影响的bean属性。如果尚未填充受影响的bean属性,则容器将抛出BeanInitializationException
public class Employee {
private String name;
@Required
public void setName(String name){
this.name=name;
}
public string getName(){
return name;
}
}
@Autowired 注解有什么用?
@Autowired 可以更准确地控制应该在何处以及如何进行自动装配。此注解用于
在setter 方法,构造函数, 具有任意名称或多个参数的属性或方法上自动装配
bean。默认情况下,它是类型驱动的注入。
public class Employee {
private String name;
@Autowired
public void setName(String name) {
this.name=name;
}
public string getName(){
return name;
}
}
qualifier 修饰语 【扩了伐i】
@Qualifier 注解有什么用?
当您创建多个相同类型的bean 并希望仅使用属性装配其中一个bean 时,您可
以使用@Qualifier 注解和@Autowired 通过指定应该装配哪个确切的bean
来消除歧义。
例如,这里我们分别有两个类, Employee 和EmpAccount。在EmpAccount
中, 使用@Qualifier 指定了必须装配id 为emp1 的bean。
public class EmpAccount {
private Employee emp;
@Autowired
@Qualifier(emp1)
public void showName() {
System.out.println(“Employee name : ”+emp.getName);
}
}
@RequestMapping 注解有什么用?
将特定的http请求映射到将处理相应请求的控制器中的特定类,方法。
你可以通过多少方式完成依赖注入
构造函数注入
setter注入
接口注入
spring中,仅使用构造函数和setter注入
Spring支持的常用数据库事务传播属性和事务隔离级别
事务的属性:
-
propagation: 用来设置事务的传播行为
- 事务的传播行为:一个方法运行在一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
- propagation.REQUIRED:默认值,使用原来的事务
- propagation.REAUIRES_NEW: 将原来的事务挂起,开启一个新的事务
-
isolation:用来设置事务的隔离级别
- Isolation.REPEATABLE_READ: 可重复读,MySQL默认的隔离级别
- Isolation.READ_COMMITTED: 读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
-
脏读:
- Transaction01将某条记录的age值从20修改为30
- Transaction02读取了Transaction01更新后的值30
- Transaction01回滚,age值恢复到了20
- Transaction02读取到的30就是一个无效的值
当前事务读取到了其他事务更新还没有提交的数据
-
不可重复读
- Transaction01读取了age的值为20
- Transaction02将age值修改为30
- Transaction01再次读取age的值为30,和第一次读取不一致
-
幻读
- Transaction01读取了STUDENT表中的一部分数据
- Transaction02向STUDENT表中插入了新的行
- Transaction01读取了STUDENT表时,多了一些行
事务的隔离级别
-
读未提交:READ UNCOMMITTED
- 允许Transaction01读取Transaction02未提交的修改
-
读已提交:READ COMMITTED read committed
- 要求Transaction01只能读取Transaction02已提交的修改
-
可重复读:REPEATABLE READ repeatable read
- 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction执行期间禁止其他事务对这个字段进行更新
-
串行化:SERIALIZABLE serializable
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间禁止其他事务对这个表进行添加,更新删除操作,可以避免任何并发问题,但性能低下
SpringMVC常用注解
- requestMapping用于请求url映射
- RequestBody 注解实现接受http请求的json数据,将json数据转换为java对象
- ResponseBody 注解实现将Controller方法返回对象转化为json响应给客户
如何开启注解处理器和适配器
在springmvc-xml中通过开启mvc:annotation-driven来实现注解处理器和适配器的开启
如何解决get和post乱码问题
- 配置charactEncodingFilter过滤器为UTF-8可以解决post乱码
- 解决get请求乱码:修改tomcat配置文件server.xml编码与工程编码一致,即在Connector标签中添加URIEncoding=UTF-8
对参数重新编码
String username = New String(request.getParameter.getBytes("IOS-8859-1"),UTF-8);

浙公网安备 33010602011771号