赞
踩
Spring Framework 简称 Spring,是 Java 开发中最常用的框架,地位仅次于 Java API,就连近几年比较流行的微服务框架
SpringBoot,也是基于 Spring 实现的,SpringBoot 的诞生是为了让开发者更方便地使用 Spring,因此 Spring 在 Java
体系中的地位可谓首屈一指。
当然,如果想要把 Spring 所有功能都讲的一清二楚,远远不是一两篇文章能够做到的,但幸运的是,Spring
的基础资料可以很轻易的搜索到,那么我们本讲主要的目的就是把 Spring 中的核心知识点和常见面试题分享给大家,希望对大家能有所帮助。
Spring 是一个开源框架,为了解决企业应用程序开发复杂性而创建的,Spring 的概念诞生于 2002 年,于 2003 年正式发布第一个版本
Spring Framework 0.9。下面一起来看 Spring 各个版本的更新特性和它的发展变化吧。
此版本主要是为了解决企业应用程序开发复杂性而创建的,当时 J2EE 应用的经典架构是分层架构:表现层、业务层、持久层,最流行的组合就是
SSH(Struts、Spring、Hibernate)。
Spring 1.x 仅支持基于 XML 的配置,确保用户代码不依赖
Spring,它主要包含了以下功能模块:aop、beans、ejb、jdbc、jndi、orm、transation、validation、web 等。
Spring 2.x 的改动并不是很大,主要是在 Spring 1.x 的基础上增加了几个新模块,如
ehcache、jms、jmx、scripting、stereotype 等。
Spring 3.x 开始不止支持 XML 的配置,还扩展了基于 Java 类的配置,还增加了
Expression、Instructment、Tomcat、oxm 等组件,同时将原来的 Web 细分为:Portlet、Servlet。
Spring 4.x 扩充了 Groovy、Messaging、WebMvc、Tiles2、WebSocket 等功能组件,同时 Spring 还适配了
Java 版本,全面支持 Java 8.0、Lambda 表达式等。随着 RESTful 架构风格被越来越多的用户所采用,Spring 4.x 也提供了
RestController 等注解新特性。
Spring 5.x 紧跟 Java 相关技术的更新迭代,不断适配 Java 的新版本,同时不断重构优化自身核心框架代码,支持函数式、响应式编程模型等。
Spring 核心包括以下三个方面:
下面分别来看它的这些特性。
控制反转(Inversion of
Control,IoC),顾名思义所谓的控制反转就是把创建对象的权利交给框架去控制,而不需要人为地去创建,这样就实现了可插拔式的接口编程,有效地降低代码的耦合度,降低了扩展和维护的成本。
比如,你去某地旅游不再用自己亲自为订购 A 酒店还是 B
酒店而发愁了,只需要把住店的需求告诉给某个托管平台,这个托管平台就会帮你订购一个既便宜又舒适的酒店,而这个帮你订购酒店的行为就可以称之为控制反转。
依赖注入(Dependency
Injection,DI),是组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IoC 是 Spring 中一个极为重要的概念,而 DI 则是实现 IoC 的方法和手段。
接下来,我们来看依赖注入的常见实现方式有哪些?
依赖注入的常见实现方式如下:
Java 代码:
public class UserController {
// 注入 UserService 对象
private UserService userService;
public void setUserService(UserService userService){
this.userService = userService;
}
}
XML 配置:
Bean 标签的常用属性说明:
Java 代码:
public class UserController {
private UserService userService;
public UserController(UserService userService){
this.userService = userService;
}
}
XML 配置:
@Controller
public class UserController {
// 使用注解自动注入
@Autowired()
private UserService userService;
// do something
}
// 创建依赖对象
@Service
public class UserService {
// do something
}
创建依赖对象的常见注解:@Component、@Controller、@Service、@Repository。
总结 :可以看出注解的方式要比传统的 XML(setter 和构造器注入)实现注入更为方便,同时注解方式也是官方力推的依赖注入最佳使用方式。
面向切面编程(Aspect Oriented
Programming,AOP),它就好比将系统按照功能分类,每一个类别就是一个“切面”,我们再针对不同的切面制定相应的规则,类似开发模式被称为面向切面编程。
AOP 的示例我们就以开车为例,开车的完成流程是这样的:巡视车体及周围情况 → 发动 → 开车 → 熄火 → 锁车。
当然我们的主要目的是“开车”,但在开车之前和开完车之后,我们要做一些其他的工作,这些“其他”的工作,可以理解为 AOP 编程。
package com.learning.aop;
import org.springframework.stereotype.Component;
@Component("person")
public class Person {
public void drive() {
System.out.println("开车");
}
}
package com.learning.aop;
import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class CarAop { @Before("execution(* com.learning.aop.Person.drive())") public void before() { System.out.println("巡视车体及周围情况"); System.out.println("发动"); } @After("execution(* com.learning.aop.Person.drive())") public void after() { System.out.println("熄火"); System.out.println("锁车"); } }
<?xml version="1.0" encoding="UTF-8"?>
<context:component-scan base-package=“com.learning”/>
aop:aspectj-autoproxy/
package com.learning.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class PersonTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Person landlord = context.getBean("person", Person.class);
landlord.drive();
}
}
运行测试代码,执行结果如下:
巡视车体及周围情况
发动
开车
熄火
锁车
答:基于 @Value 的注解可以读取 properties 配置文件,使用如下:
@Value(“#{configProperties[‘jdbc.username’]}”)
private String userName;
以上为读取 configProperties 下的 jdbc.username 配置。
答:Spring 通知类型总共有 5 种:前置通知、环绕通知、后置通知、异常通知、最终通知。
答:Spring IOC
就是把创建对象的权利交给框架去控制,而不需要人为的去创建,这样就实现了可插拔式的接口编程,有效地降低代码的耦合度,降低了扩展和维护的成本。
比如,去某地旅游不再用自己亲自为订购 A 酒店还是 B
酒店而发愁了,只需要把住店的需求告诉给某个托管平台,这个托管平台就会帮你订购一个既便宜又舒适的酒店,而这个帮你订购酒店的行为就可以称之为控制反转。
答:依赖注入是指组件之间的依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
答:IoC 是 Spring 中一个极为重要的概念,提供了对象管理的功能,从而省去了人为创建麻烦,而 DI 正是实现 IoC 的方法和手段。
答:它们的作用对象不同:@Component 作用于类,而 @Bean 注解作用于方法。
@Component 通常是通过类路径扫描来自动侦测和装配对象到 Spring 容器中,比如 @ComponentScan 注解就是定义扫描路径中的类装配到
Spring 的 Bean 容器中;@Bean 注解是告诉 Spring 这是某个类的实例,当我需要用它的时把它给我,@Bean 注解比
@Component 注解自定义性更强,很多地方我们只能通过 @Bean 注解来注册 Bean,比如当我们引用第三方库中的类需要装配到
Spring容器时,则只能通过 @Bean 来实现,比如以下示例,只能通过 @Bean 注解来实现:
public class WireThirdLibClass {
@Bean
public ThirdLibClass getThirdLibClass() {
return new ThirdLibClass();
}
}
答:Spring 中 bean 的作用域有四种类型,如下列表:
Spring 默认的是单例模式。
答:当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean,为了定义 inner Bean,在 Spring 的基于 XML
的配置元数据中,可以在 <property/>
或 <constructor-arg/>
元素内使用 <bean/>
元素,内部 bean
通常是匿名的,它们的 Scope 一般是 prototype。
答:Spring 的注入方式包含以下五种:
其中最常用的是前三种,官方推荐使用的是注解注入,相对使用更简单,维护成本更低,更直观。
答:在 Spring 中操作数据库,可以使用 Spring 提供的 JdbcTemplate 对象,JdbcTemplate
类提供了很多便利的方法,比如把数据库数据转变成基本数据类型或对象,执行自定义的 SQL 语句,提供了自定义的数据错误处理等,JdbcTemplate
使用示例如下:
@Autowired
private JdbcTemplate jdbcTemplate;
// 新增
@GetMapping(“save”)
public String save(){
String sql = “INSERT INTO USER (USER_NAME,PASS_WORD) VALUES (‘laowang’,‘123’)”;
int rows = jdbcTemplate.update(sql);
return “执行成功,影响” + rows + “行”;
}
// 删除
@GetMapping(“del”)
public String del(int id){
int rows= jdbcTemplate.update(“DELETE FROM USER WHERE ID = ?”,id);
return “执行成功,影响” + rows + “行”;
}
// 查询
@GetMapping(“getMapById”)
public Map getMapById(Integer id){
String sql = “SELECT * FROM USER WHERE ID = ?”;
Map map= jdbcTemplate.queryForMap(sql,id);
return map;
}
答:Spring 的 JdbcTemplate 是对 JDBC API 的封装,提供更多的功能和更便利的操作,比如 JdbcTemplate 拥有:
答:Spring 实现事务有两种方式:编程式事务和声明式事务。
编程式事务,使用 TransactionTemplate 或 PlatformTransactionManager 实现,示例代码如下:
private final TransactionTemplate transactionTemplate;
public void add(User user) throws Exception{
// Spring编码式事务,回调机制
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
userMapper.insertSelective(user);
} catch (Exception e) {
// 异常,设置为回滚
status.setRollbackOnly();
throw e;
}
return null;
}
});
}
如果有异常,调用 status.setRollbackOnly() 回滚事务,否则正常执行 doInTransaction() 方法,正常提交事务。
如果事务控制的方法不需要返回值,就可以使用 TransactionCallbackWithoutResult(TransactionCallback
接口的抽象实现类)示例代码如下:
public void add(User user) throws Exception {
// Spring编码式事务,回调机制
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
userMapper.insertSelective(user);
} catch (Exception e) {
// 异常,设置为回滚
status.setRollbackOnly();
throw e;
}
}
});
}
声明式事务,底层是建立在 Spring AOP
的基础上,在方式执行前后进行拦截,并在目标方法开始执行前创建新事务或加入一个已存在事务,最后在目标方法执行完后根据情况提交或者回滚事务。
声明式事务的优点:不需要编程,减少了代码的耦合,在配置文件中配置并在目标方法上添加 @Transactional 注解来实现,示例代码如下:
@Transactional
public void save() {
User user = new User(“laowang”);
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException(“异常”);
}
}
抛出异常,事务会自动回滚,如果方法正常执行,则会自动提交事务。
答:Spring 的注入方式包含以下五种:
默认值为 ISOLATION_DEFAULT 遵循数据库的事务隔离级别设置。
答:可能的原因如下:
答:Spring AOP 的底层实现原理就是动态代理。Spring AOP 的动态代理有两种实现方式,对于接口使用的是 JDK
自带的动态代理来实现的,而对比非接口使用的是 CGLib 来实现的,关于动态代理的详细内容,可参考前面【反射和动态代理】的那篇文章。
答:Spring 中的 Bean 默认是单例模式,Spring 框架并没有对单例 Bean 进行多线程的封装处理,因此默认的情况 Bean
并非是安全的,最简单保证 Bean 安全的举措就是设置 Bean 的作用域为 Prototype(原型)模式,这样每次请求都会新建一个 Bean。
答:Spring 中 Bean 的生命周期如下:
以上几个步骤完成后,Bean 就已经被正确创建了,之后就可以使用这个 Bean 了。
答:Spring 优点如下:
答:Spring 和 Struts 区别如下:
Spring 特性如下:
Struts 特性如下:
答:它们的区别如下:
答:Spring 中使用的设计模式如下:
通过本节内容我们充分的了解了 Spring 的核心:IoC、DI、AOP,也是用代码演示了 Spring 核心功能的示例,其中可以发现的是 Spring
正在从之前的 XML 配置编程变为 Java 注解编程,注解编程让 Spring 更加轻量化简单化了,这一点在我们后面介绍 SpringBoot
的时候,会让你更加感同身受。对于开发者来说,只有真正掌握了 Spring,才能称得上是一名合格的 Java
工程师。当然,学习的目的是为了更好的应用,因此现在就一起动手实践起来吧。
Spring MVC(Spring Web MVC)是 Spring Framework 提供的 Web 组件,它的实现基于 MVC
的设计模式:Controller(控制层)、Model(模型层)、View(视图层),提供了前端路由映射、视图解析等功能,让 Java Web
开发变得更加简单,也属于 Java 开发中必须要掌握的热门框架。
Spring MVC 的执行流程如下:
1. 客户端发送请求至前端控制器(DispatcherServlet)
2. 前端控制器根据请求路径,进入对应的处理器
3. 处理器调用相应的业务方法
4. 处理器获取到相应的业务数据
5. 处理器把组装好的数据交还给前端控制器
6. 前端控制器将获取的 ModelAndView 对象传给视图解析器(ViewResolver)
7. 前端控制器获取到解析好的页面数据
8. 前端控制器将解析好的页面返回给客户端
流程如下图所示:
Spring MVC 的核心组件如下列表所示:
1. **DispatcherServlet** :核心处理器(也叫前端控制器),负责调度其他组件的执行,可降低不同组件之间的耦合性,是整个 Spring MVC 的核心模块。
2. **Handler** :处理器,完成具体业务逻辑,相当于 Servlet 或 Action。
3. **HandlerMapping** :DispatcherServlet 是通过 HandlerMapping 将请求映射到不同的 Handler。
4. **HandlerInterceptor** :处理器拦截器,是一个接口,如果我们需要做一些拦截处理,可以来实现这个接口。
5. **HandlerExecutionChain** :处理器执行链,包括两部分内容,即 Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器设置)。
6. **HandlerAdapter** :处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作包括表单数据的验证、数据类型的转换、将表单数据封装到 POJO 等,这一系列的操作,都是由 HandlerAdapter 来完成,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
7. **ModelAndView** :装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
8. **ViewResolver** :视图解析器,DispatcherServlet 通过它将逻辑视图解析成物理视图,最终将渲染结果响应给客户端。
自动类型转换指的是,Spring MVC 可以将表单中的字段,自动映射到实体类的对应属性上,请参考以下示例。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<form action="add">
名称:<input type="input" name="name"><br>
年龄:<input type="input" name="age"><br>
<input type="submit" value=" 提交 ">
</form>
</body>
</html>
public class PersonDTO { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonController {
@RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")
public String add(PersonVO person) {
return person.getName() + ":" + person.getAge();
}
}
执行结果如下图所示:
业务的操作过程中可能会出现中文乱码的情况,以下是处理中文乱码的解决方案。
第一步,在 web.xml 添加编码过滤器,配置如下:
<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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第二步,设置 RequestMapping 的 produces 属性,指定返回值类型和编码,如下所示:
@RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")
在 Spring MVC 中可以通过配置和实现 HandlerInterceptor 接口,来实现自己的拦截器。
在 Spring MVC 的配置文件中,添加如下配置:
<mvc:interceptors>
<bean class="com.learning.core.MyInteceptor"></bean>
</mvc:interceptors>
拦截器的实现代码如下:
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 拦截器 **/ public class MyInteceptor implements HandlerInterceptor { // 在业务处理器处理请求之前被调用 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); return true; } // 在业务处理器处理请求完成之后,生成视图之前执行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } // 在 DispatcherServlet 完全处理完请求之后被调用 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); } }
配置如下:
<!-- Hibernate 参数验证包 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
在 Spring MVC 的配置文件中,添加如下配置信息:
<mvc:annotation-driven />
代码如下:
import com.google.gson.JsonObject; import com.learning.pojo.PersonDTO; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class PersonController { @RequestMapping(value = "/check", produces = "text/plain;charset=utf-8") public String check(@Validated PersonDTO person, BindingResult bindResult) { // 需要 import com.google.gson.Gson JsonObject result = new JsonObject(); StringBuilder errmsg = new StringBuilder(); if (bindResult.hasErrors()) { List<ObjectError> errors = bindResult.getAllErrors(); for (ObjectError error : errors) { errmsg.append(error.getDefaultMessage()); } result.addProperty("status", -1); } else { result.addProperty("status", 1); } result.addProperty("errmsg", errmsg.toString()); return result.toString(); } }
代码如下:
import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; public class PersonDTO { @NotNull(message = "姓名不能为空") private String name; @Min(value = 18,message = "年龄不能低于18岁") private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
更多验证注解,如下所示:
注解 | 运行时检查 |
---|---|
@AssertFalse | 被注解的元素必须为 false |
@AssertTrue | 被注解的元素必须为 true |
@DecimalMax(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(Value) | 被注解的元素必须为一个数字,其值必须大于等于指定的最小值 |
@Digits(integer=, fraction=) | 被注解的元素必须为一个数字,其值必须在可接受的范围内 |
@Future | 被注解的元素必须是日期,检查给定的日期是否比现在晚 |
@Max(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最大值 |
@Min(value) | 被注解的元素必须为一个数字,其值必须大于等于指定的最小值 |
@NotNull | 被注解的元素必须不为 null |
@Null | 被注解的元素必须为 null |
@Past(java.util.Date/Calendar) | 被注解的元素必须过去的日期,检查标注对象中的值表示的日期比当前早 |
@Pattern(regex=, flag=) | 被注解的元素必须符合正则表达式,检查该字符串是否能够在 match 指定的情况下被 regex |
定义的正则表达式匹配 | |
@Size(min=, max=) | 被注解的元素必须在制定的范围(数据类型:String、Collection、Map、Array) |
@Valid | 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个 map,则对其中的值部分进行校验 |
@CreditCardNumber | 对信用卡号进行一个大致的验证 |
被注释的元素必须是电子邮箱地址 | |
@Length(min=, max=) | 被注解的对象必须是字符串的大小必须在制定的范围内 |
@NotBlank | 被注解的对象必须为字符串,不能为空,检查时会将空格忽略 |
@NotEmpty | 被注释的对象必须不为空(数据:String、Collection、Map、Array) |
@Range(min=, max=) | |
被注释的元素必须在合适的范围内(数据:BigDecimal、BigInteger、String、byte、short、int、long 和原始类型的包装类) | |
@URL(protocol=, host=, port=, regexp=, flags=) | 被注解的对象必须是字符串,检查是否是一个有效的 |
URL,如果提供了 protocol、host 等,则该 URL 还需满足提供的条件 |
执行结果,如下图所示:
访问 Spring MVC 官方说明文档:http://1t.click/H7a
答:前端控制器(DispatcherServlet) 接收请求,通过映射从 IoC 容器中获取对应的 Controller 对象和 Method
方法,在方法中进行业务逻辑处理组装数据,组装完数据把数据发给视图解析器,视图解析器根据数据和页面信息生成最终的页面,然后再返回给客户端。
答:POJO 和 JavaBean 的区别如下:
简而言之,当一个 POJO 可序列化,有一个无参的构造函数,它就是一个 JavaBean。
答:常见的跨域的实现方式有两种:使用 JSONP 或者在服务器端设置运行跨域。服务器运行跨域的代码如下:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的请求规则 registry.addMapping("/api/**"); } }; } }
@RequestMapping(value="/list",params={"age=10"}
public String list(){
// do something
}
A:age 参数不传递的时候,默认值是 10
B:age 参数可以为空
C:age 参数不能为空
D:以上都不对
答:C
题目解析:params={“age=10”} 表示必须包含 age 参数,且值必须等于 10。
答:@RequestMapping 常用属性如下:
@RequestMapping(value="/list")
@ResponseBody
public String list(int id){
return "id="+id;
}
A:id=0
B:id=
C:页面报错 500
D:id=null
答:C
题目解析:页面报错会提示:可选的参数“id”不能转为 null,因为基本类型不能赋值 null,所以会报错。
A:服务器繁忙
B:找不到该页面
C:禁止访问
D:服务器跳转中
答:C
题目解析:常用 HTTP 状态码及对应的含义:
答:forward 和 redirect 区别如下:
@RequestMapping(value="/list")
@ResponseBody
public String list(Integer id){
return "id="+id;
}
A:id=0
B:id=
C:页面报错 500
D:id=null
答:D
题目解析:包装类可以赋值 null,不会报错。
答:在后端代码中可以使用 forward:/index.jsp 或 redirect:/index.jsp 完成页面跳转,前者 URL
地址不会发生改变,或者 URL 地址会发生改变,完整跳转代码如下:
@RequestMapping("/redirect")
public String redirectTest(){
return "redirect:/index.jsp";
}
答:Spring MVC 的常用注解如下:
答:拦截器的典型使用场景如下:
答:在 Spring MVC 的配置文件中,添加 ,用于排除拦截目录,完整配置的示例代码如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- 排除拦截地址 -->
<mvc:exclude-mapping path="/api/**" />
<bean class="com.learning.core.MyInteceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
答:@Validated 和 @Valid 都用于参数的效验,不同的是:
答:Spring MVC 获取 request 有以下三种方式:
① 从请求参数中获取
示例代码:
@RequestMapping("/index")
@ResponseBody
public void index(HttpServletRequest request){
// do something
}
该方法实现的原理是 Controller 开始处理请求时,Spring 会将 request 对象赋值到方法参数中。
② 通过 RequestContextHolder上下文获取 request 对象
示例代码:
@RequestMapping("/index")
@ResponseBody
public void index(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
// do something
}
③ 通过自动注入的方式
@Controller
public class HomeController{
@Autowired
private HttpServletRequest request; // 自动注入 request 对象
// do something
}
本文我们了解了 Spring MVC 运行的 8 个步骤和它的 8 大核心组件,也尝试了 Spring MVC
方面的类型转换,可将表单自动转换为实体对象,也使用 Hibernate 的验证功能优雅地实现了参数的验证,还可以通过配置和实现
HandlerInterceptor 接口来自定义拦截器,相信有了这些知识,可以帮助我们更高效地开发 Web 和接口项目。
Spring Boot 来自于 Spring 大家族,是 Spring 官方团队(Pivotal 团队)提供的全新框架,它的诞生解决了 Spring
框架使用较为繁琐的问题。Spring Boot 的核心思想是约定优于配置,让开发人员不需要配置任何 XML 文件,就可以像 Maven 整合 Jar
包一样,整合并使用所有框架。
Spring Boot 特性
Spring Boot 2 对系统环境的要求
在开始之前,我们先来创建一个Spring Boot 项目。
Spring Boot 有两种快速创建的方式:Spring 官网在线网站创建和 IntelliJ IDEA 的 Spring Initializr
创建,下面分别来看。
在浏览器输入 https://start.spring.io,页面打开如下图所示:
填写相应的项目信息,选择对应的 Spring Boot 和 Java 版本点击 “Generate the project”按钮下载项目压缩文件,解压后用
IDEA 打开即可。
其中 Group 和 Artifact 是 Maven 项目用来确认依赖项目的标识,比如:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
Group 对应的是配置文件的 groupId,相当于项目的包名;而 Artifact 对应的是配置文件的 artifactId,相当于项目名。
① 新建项目 → 选择 Spring Initialzr,如下图所示:
② 点击 Next 按钮,填写对应的项目信息(和在线网站创建的字段基本相同),如下图所示:
③ 点击 Next 按钮,选择相应的依赖信息,如下图所示:
④ 点击 Next 按钮,选择项目保存的路径,点击 Finish 创建项目完成,如下图所示:
1)pom.xml 中添加 Web 模块的依赖,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2)创建后台代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/index")
public String index(String name) {
return "Hello, " + name;
}
}
3)启动并访问项目
项目的启动类是标识了 @Spring BootApplication 的类,代码如下所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootlearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootlearningApplication.class, args);
}
}
启动并访问 http://localhost:8080/index?name=laowang 效果如下:
到目前为止 Spring Boot 的项目就创建并正常运行了。
Spring Boot 的配置文件,是 resources 目录下 application.properties 文件,如下图所示:
可以在配置文件中设置很多关于 Spring 框架的配置,格式如下配置所示:
# 项目运行端口
server.port=8086
# 请求编码格式
server.tomcat.uri-encoding=UTF-8
Spring Boot 的其他功能开发和 Spring 相同(Spring Boot 2 是基于 Spring Framework 5
构建的),本文就不过多的介绍了,[感兴趣的朋友可以点击这里查看](https://docs.spring.io/spring-
boot/docs/current/reference/html/)
Spring Boot 项目的发布方式有两种:
使用窗口命令,在 pom.xml 同级目录下:
mvn clean package -Dmaven.test.skip=true
Dmaven.test.skip=true 表示不执行测试用例,也不编译测试用例类。
后台启动 Java 程序, 命令如下:
nohup java -jar springbootlearning-0.0.1-SNAPSHOT.jar &
停止程序
首先查询 Java 程序的 pid
ps -ef|grep java
再停止程序
kill -9 pid
操作如下图所示:
扩展内容
指定程序运行日志文件
nohup java -jar springbootlearning-0.0.1-SNAPSHOT.jar 1>>logs 2>>errlog &
其中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
将 scope 属性设置为 provided,表示打包不会包含此依赖。
在项目的启动类中继承 Spring BootServletInitializer 并重写 configure() 方法:
@SpringBootApplication
public class PackageApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(PackageApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(PackageApplication.class, args);
}
}
使用窗口命令,在 pom.xml 同级目录下:
mvn clean package -Dmaven.test.skip=true
打包完成会在 target 目录下生成:项目名 + 版本号.war 文件,复制到 Tomcat 的 webapps 目录下,运行 Tomcat 即可。
A:JDK 6
B:JDK 7
C:JDK 8
D:JDK 9
答:C
答:它们都是来自于 Spring 大家庭,Spring Boot 是在 Spring 框架的基础上开发而来,让更加方便使用 Spring;Spring
Cloud 是依赖于 Spring Boot 而构建的一套微服务治理框架。
答:Spring Boot 项目优势如下:
答:在 pom.xml 里设置 <packaging>war</packaging>
。
答:在 pom.xml 文件的 build 节点中,添加 finalName 节点并设置为要的名称即可,配置如下:
<build>
<finalName>warName</finalName>
</build>
答:Ant、Maven、Gradle 是 Java 领域中主要有三大构建工具,它们的区别如下:
Spring Boot 官方支持 Maven 和 Gradle 作为项目构建工具。Gradle 虽然有更好的理念,但是相比 Maven
来讲其行业使用率偏低,并且 Spring Boot 官方默认使用 Maven。
答:在 build 节点下设置 finalName 就是发布的包名,如下代码所示:
<build>
<finalName>biapi</finalName>
</build>
答:Spring Boot 热部署主要有两种方式:Spring Loaded、Spring-boot-devtools。
方式 1:Spring Loaded
在 pom.xml 文件中添加如下依赖:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
<configuration>
<mainClass>此处为入口类</mainClass>
</configuration>
</plugin>
方式 2:Spring-boot-devtools
在 pom.xml 文件中添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
答:Spring Boot 2.0 无法在 Tomcat 7 上运行。因为 Spring Boot 2.0 使用的是 Spring Framework
5,Spring Framework 5 使用的是 Servlet 3.1,而 Tomcat 7 最高支持到 Servlet 3.0,所以 Spring
Boot 2.0 无法在 Tomcat 7 上运行。
答:在 spring-boot-starter-web 移除现有的依赖项,添加 Jetty 依赖,配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
A:Tomcat
B:Jetty
C:Undertow
D:Nginx
答:D
题目解析:Jetty 容器支持如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Undertow 容器支持如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
答:Spring Boot 中有 .properties 和 .yml 两种配置文件格式,它们主要的区别是书写格式不同。
.properties 配置文件格式如下:
app.user.name = hellojava
.yml 配置文件格式如下:
app:
user:
name: hellojava
A:application.properties 的内容会被忽略,只会识别 application.yml 的内容。
B:两个配置文件同时有效,有相同配置时,以 application.properties 文件为主。
C:application.yml 的内容会被忽略,只会识别 application.properties 的内容。
D:两个配置文件同时有效,有相同配置时,以 application.yml 文件为主。
答:B
答:RequestMapping 和 GetMapping 区别如下:
A:@Controller 返回 JSON 数据
B:@RestController 返回 JSON 数据
C:@APIController 返回 JSON 数据
D:以上都对
答:B
答:Spring Cache 常用注解如下:
答:Spring Boot Admin 使用了 Spring Boot Actuator 接口进行 UI
美化封装的监控工具,它以图形化的方式查询单个应用的详细状态,也可以使用 Spring Boot Admin 来监控整个集群的状态。
答:Stater 可以理解为启动器,它是方便开发者快速集成其他框架到 Spring 中的一种技术。比如,spring-boot-starter-data-
jpa 就是把 JPA 快速集成到 Spring 中。
答:常见的 starter 如下:
答:spring-boot-starter-jdbc 是 Spring Boot 针对 JDBC 的使用提供了对应的 Starter 包,在 Spring
JDBC 上做了进一步的封装,方便在 Spring Boot 生态中更好的使用 JDBC。
答:Spring Boot 可以通过 @Value、@Environment、@ConfigurationProperties 这三种方式来读取。
例如,配置文件内容如下:
app.name=中文
① Value 方式
@Value("${app.name}")
private String appName;
② Environment 方式
public class HelloController {
@Autowired
private Environment environment;
@RequestMapping("/index")
public String index(String hiName) {
// 读取配置文件
String appName = environment.getProperty("app.name");
return "Hello, " + hiName + " |@" + appName;
}
}
③ ConfigurationProperties 方式
@ConfigurationProperties(prefix = "app")
public class HelloController {
// 读取配置文件,必须有 setter 方法
private String name;
public void setName(String name) {
this.name = name;
}
@RequestMapping("/index")
public String index(String hiName) {
System.out.println("appname:" + name);
return "Hello, " + hiName + " |@" + appName;
}
}
答:这是因为配置文件的编码格式导致的,需要把编码格式设置为 UTF-8,如下图所示:
设置完成之后,重新启动 IDEA 就可以正常显示中文了。
通过本文我们学习了 Spring Boot 的两种创建方式:在线网站创建和 IntelliJ IDEA 方式创建。知道了 Spring Boot
发布的两种方式:内置容器和外置 Tomcat,知道了 Spring Boot 项目特性,以及配置文件 .properties 和 .yml
的差异,掌握了读取配置文件的三种方式:@Value、@Environment、@ConfigurationProperties。
MyBatis 是一款优秀的 ORM(Object Relational
Mapping,对象关系映射)框架,它可以通过对象和数据库之间的映射,将程序中的对象自动存储到数据库中。它是 Apache
提供的一个开源项目,之前的名字叫做 iBatis,2010 年迁移到了 Google Code,并且将名字改为我们现在所熟知的 MyBatis,又于
2013 年 11 月迁移到了 Github。
MyBatis 提供了普通 SQL 查询、事务、存储过程等功能,它的优缺点如下。
优点 :
缺点 :
总体来说,MyBatis 是一个非常优秀和灵活的数据持久化框架,适用于需求多变的互联网项目,也是当前主流的 ORM 框架。
MyBatis 中的重要组件如下:
MyBatis 完整执行流程如下图所示:
MyBatis 执行流程说明:
1. 首先加载 Mapper 配置的 SQL 映射文件,或者是注解的相关 SQL 内容。
2. 创建会话工厂,MyBatis 通过读取配置文件的信息来构造出会话工厂(SqlSessionFactory)。
3. 创建会话,根据会话工厂,MyBatis 就可以通过它来创建会话对象(SqlSession),会话对象是一个接口,该接口中包含了对数据库操作的增、删、改、查方法。
4. 创建执行器,因为会话对象本身不能直接操作数据库,所以它使用了一个叫做数据库执行器(Executor)的接口来帮它执行操作。
5. 封装 SQL 对象,在这一步,执行器将待处理的 SQL 信息封装到一个对象中(MappedStatement),该对象包括 SQL 语句、输入参数映射信息(Java 简单类型、HashMap 或 POJO)和输出结果映射信息(Java 简单类型、HashMap 或 POJO)。
6. 操作数据库,拥有了执行器和 SQL 信息封装对象就使用它们访问数据库了,最后再返回操作结果,结束流程。
MyBatis 使用分为两个版本:XML 版和 Java 注解版。接下来我们使用 Spring Boot 结合 MyBatis 的 XML
版,来实现对数据库的基本操作,步骤如下。
drop table if exists `t_user`;
create table `t_user` (
`id` bigint(20) not null auto_increment comment '主键id',
`username` varchar(32) default null comment '用户名',
`password` varchar(32) default null comment '密码',
`nick_name` varchar(32) default null,
primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8;
在项目添加对 MyBatis 和 MySQL 支持的依赖包,在 pom.xml 文件中添加如下代码:
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
mybatis-spring-boot-starter 是 MyBatis 官方帮助我们快速集成 Spring Boot 提供的一个组件包,mybatis-
spring-boot-starter 2.1.0 对应 MyBatis 的版本是 3.5.2。
在 application.yml 文件中添加以下内容:
spring:
datasource:
url: jdbc:mysql://localhost:3306/learndb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.interview.mybatislearning.model
其中:
注:如果配置文件使用的是 application.properties,配置内容是相同的,只是内容格式不同。
public class UserEntity implements Serializable { private static final long serialVersionUID = -5980266333958177104L; private Integer id; private String userName; private String passWord; private String nickName; public UserEntity(String userName, String passWord, String nickName) { this.userName = userName; this.passWord = passWord; this.nickName = nickName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } }
mybatis-config.xml (基础配置文件):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer"/>
<typeAlias alias="Long" type="java.lang.Long"/>
<typeAlias alias="HashMap" type="java.util.HashMap"/>
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
<typeAlias alias="ArrayList" type="java.util.ArrayList"/>
<typeAlias alias="LinkedList" type="java.util.LinkedList"/>
</typeAliases>
</configuration>
mybatis-config.xml 主要是为常用的数据类型设置别名,用于减少类完全限定名的长度,比如:resultType="Integer"
完整示例代码如下:
<select id="getAllCount" resultType="Integer">
select
count(*)
from t_user
</select>
UserMapper.xml (业务配置文件):
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.interview.mybatislearning.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.interview.mybatislearning.model.UserEntity" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="username" property="userName" jdbcType="VARCHAR" /> <result column="password" property="passWord" jdbcType="VARCHAR" /> <result column="nick_name" property="nickName" jdbcType="VARCHAR" /> </resultMap> <sql id="Base_Column_List" > id, username, password, nick_name </sql> <sql id="Base_Where_List"> <if test="userName != null and userName != ''"> and userName = #{userName} </if> </sql> <select id="getAll" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM t_user </select> <select id="getOne" parameterType="Long" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM t_user WHERE id = #{id} </select> <insert id="insert" parameterType="com.interview.mybatislearning.model.UserEntity" > INSERT INTO t_user (username,password,nick_name) VALUES (#{userName}, #{passWord}, #{nickName}) </insert> <update id="update" parameterType="com.interview.mybatislearning.model.UserEntity" > UPDATE t_user SET <if test="userName != null">username = #{userName},</if> <if test="passWord != null">password = #{passWord},</if> nick_name = #{nickName} WHERE id = #{id} </update> <delete id="delete" parameterType="Long" > DELETE FROM t_user WHERE id =#{id} </delete> </mapper>
以上配置我们增加了增删改查等基础方法。
此步骤我们需要创建一个与 XML 对应的业务 Mapper 接口,代码如下:
public interface UserMapper {
List<UserEntity> getAll();
UserEntity getOne(Long id);
void insert(UserEntity user);
void update(UserEntity user);
void delete(Long id);
}
在启动类中添加 @MapperScan,设置 Spring Boot 启动的时候会自动加载包路径下的 Mapper。
@SpringBootApplication
@MapperScan("com.interview.mybatislearning.mapper")
public class MyBatisLearningApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisLearningApplication.class, args);
}
}
经过以上步骤之后,整个 MyBatis 的集成就算完成了。接下来我们写一个单元测试,验证一下。
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatislearningApplicationTests {
@Resource
private UserMapper userMapper;
@Test
public void testInsert() {
userMapper.insert(new UserEntity("laowang", "123456", "老王"));
Assert.assertEquals(1, userMapper.getAll().size());
}
}
通过本文我们知道 MyBatis 是一个优秀和灵活的数据持久化框架,MyBatis 包含 Mapper 配置、Mapper
接口、Executor、SqlSession、SqlSessionFactory 等几个重要的组件,知道了 MyBatis 基本流程:MyBatis
首先加载 Mapper 配置和 SQL 映射文件,通过创建会话工厂得到 SqlSession 对象,再执行 SQL 语句并返回操作信息。我们也使用 XML
的方式,实现了 MyBatis 对数据库的基础操作。
MyBatis 最初的设计是基于 XML 配置文件的,但随着 Java 的发展(Java 1.5 开始引入注解)和 MyBatis 自身的迭代升级,终于在
MyBatis 3 之后就开始支持基于注解的开发了。
下面我们使用 Spring Boot + MyBatis 注解的方式,来实现对数据库的基本操作,具体实现步骤如下。
drop table if exists `t_user`;
create table `t_user` (
`id` bigint(20) not null auto_increment comment '主键id',
`username` varchar(32) default null comment '用户名',
`password` varchar(32) default null comment '密码',
`nick_name` varchar(32) default null,
primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8;
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
在 application.yml 文件中添加以下内容:
spring:
datasource:
url: jdbc:mysql://localhost:3306/learndb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package: com.interview.model
public class UserEntity implements Serializable { private static final long serialVersionUID = -5980266333958177105L; private Integer id; private String userName; private String passWord; private String nickName; public UserEntity(String userName, String passWord, String nickName) { this.userName = userName; this.passWord = passWord; this.nickName = nickName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } }
public interface UserMapper { @Select("select * from t_user") @Results({ @Result(property = "nickName", column = "nick_name") }) List<UserEntity> getAll(); @Select("select * from t_user where id = #{id}") @Results({ @Result(property = "nickName", column = "nick_name") }) UserEntity getOne(Long id); @Insert("insert into t_user(username,password,nick_name) values(#{userName}, #{passWord}, #{nickName})") void insert(UserEntity user); @Update("update t_user set username=#{userName},nick_name=#{nickName} where id =#{id}") void update(UserEntity user); @Update({"<script> ", "update t_user ", "<set>", " <if test='userName != null'>userName=#{userName},</if>", " <if test='nickName != null'>nick_name=#{nickName},</if>", " </set> ", "where id=#{id} ", "</script>"}) void updateUserEntity(UserEntity user); @Delete("delete from t_user where id =#{id}") void delete(Long id); }
使用 @Select、@Insert、@Update、@Delete、@Results、@Result 等注解来替代 XML 配置文件。
在启动类中添加 @MapperScan,设置 Spring Boot 启动的时候会自动加载包路径下的 Mapper。
@SpringBootApplication
@MapperScan("com.interview.mapper")
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
userMapper.insert(new UserEntity("laowang", "123456", "老王"));
Assert.assertEquals(1, userMapper.getAll().size());
}
}
答:MyBatis 优缺点如下:
优点:
缺点:
总体来说,MyBatis 是一个非常不错的持久层解决方案,它专注于 SQL 本身,非常灵活,适用于需求变化较多的互联网项目,也是当前国内主流的 ORM
框架。
A:可以灵活的编辑 SQL 语句
B:很好的支持不同数据库之间的迁移
C:能够很好的和 Spring 框架集成
D:提供映射标签支持对象和数据库的字段映射
答:B
题目解析:因为 MyBatis 需要自己编写 SQL 语句,但每个数据库的 SQL 语句有略有差异,所以 MyBatis
不能很好的支持不同数据库之间的迁移。
答:MyBatis 和 Hibernate 都是非常优秀的 ORM 框架,它们的区别如下:
$
”有什么区别?答:“#”是预编译处理,“$”是字符替换。 在使用“#”时,MyBatis 会将 SQL 中的参数替换成“?”,配合 PreparedStatement 的
set 方法赋值,这样可以有效的防止 SQL 注入,保证程序的运行安全。
答:通常的解决方案有以下两种方式。
① 在 SQL 语句中重命名为实体类的属性名,可参考以下配置:
<select id="selectorder" parametertype="int" resultetype="com.interview.order">
select order_id id, order_no orderno form order where order_id=#{id};
</select>
② 通过 <resultMap>
映射对应关系,可参考以下配置:
<resultMap id="BaseResultMap" type="com.interview.mybatislearning.model.UserEntity" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="username" property="userName" jdbcType="VARCHAR" />
<result column="password" property="passWord" jdbcType="VARCHAR" />
<result column="nick_name" property="nickName" jdbcType="VARCHAR" />
</resultMap>
<select id="getAll" resultMap="BaseResultMap">
select * from t_user
</select>
答:可以在 Java 代码中添加 SQL 通配符来实现 like 查询,这样也可以有效的防治 SQL 注入,具体实现如下:
Java 代码:
String name = "%wang%":
List<User> list = mapper.likeName(name);
Mapper 配置:
<select id="likeName">
select * form t_user where name like #{name};
</select>
答:MyBatis 的分页方式有以下两种:
答:RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据。因为 MyBatis 是对 JDBC 的封装,在 JDBC
驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在执行 next()
的时候,去查询更多的数据。 就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,要取 4 次才能把钱取完。对于 JDBC
来说也是一样,这样做的好处是可以有效的防止内存溢出。
答:因为使用 HashMap 或 Hashtable 作为查询结果集直接输出,会导致值类型不可控,给调用人员造成困扰,给系统带来更多不稳定的因素。
答:动态 SQL 是指可以根据不同的参数信息来动态拼接的不确定的 SQL 叫做动态 SQL,MyBatis 动态 SQL
的主要元素有:if、choose/when/otherwise、trim、where、set、foreach 等。 以 if 标签的使用为例:
<select id="findUser" parameterType="com.interview.entity.User" resultType="com.interview.entity.User">
select * from t_user where
<if test="id!=null">
id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</select>
答:因为事务的滥用会影响数据的 QPS(每秒查询率),另外使用事务的地方还要考虑各方面回滚的方案,如缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
答:只需要在 mybatis-config.xml 设置 <setting name="lazyLoadingEnabled" value="true"/>
即可打开延迟缓存功能,完整配置文件如下:
<configuration>
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
</configuration>
答:MyBatis 缓存如下:
手动开启二级缓存,配置如下:
<configuration>
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
答:可直接在 XML 中配置开启 EhcacheCache,代码如下:
<mapper namespace="com.interview.repository.ClassesReposirory">
<!-- 开启二级缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache" >
<!-- 缓存创建以后,最后一次访问缓存的时间至失效的时间间隔 -->
<property name="timeToIdleSeconds" value="3600"/>
<!-- 缓存自创建时间起至失效的时间间隔-->
<property name="timeToLiveSeconds" value="3600"/>
<!-- 缓存回收策略,LRU 移除近期最少使用的对象 -->
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
<select id="findById" parameterType="java.lang.Long" resultType="com.interview.entity.Classes">
select * from classes where id = #{id}
</select>
</mapper>
答:MyBatis 提供的连接器有以下 4 种。
拦截功能具体实现如下:
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TestInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); //被代理对象 Method method = invocation.getMethod(); //代理方法 Object[] args = invocation.getArgs(); //方法参数 // 方法拦截前执行代码块 Object result = invocation.proceed(); // 方法拦截后执行代码块 return result; } public Object plugin(Object target) { return Plugin.wrap(target, this); } }
通过本文可以看出 MyBatis 注解版和 XML 版的主要区别是 Mapper 中的代码,注解版把之前在 XML 的 SQL 实现,全部都提到
Mapper 中了,这样就省去了配置 XML 的麻烦。
答:消息队列的应用场景如下。
答:RabbitMQ 的优点如下:
答:RabbitMQ 包含以下三个重要的角色:
答:RabbitMQ
包含的重要组件有:ConnectionFactory(连接管理器)、Channel(信道)、Exchange(交换器)、Queue(队列)、RoutingKey(路由键)、BindingKey(绑定键)
等重要的组件,它们的作用如下:
运行流程,如下图所示:
答:消息持久化是把消息保存到物理介质上,以防止消息的丢失。
答:RabbitMQ 要实现消息持久化,必须满足以下 4 个条件:
答:消息持久化的缺点是很消耗性能,因为要写入硬盘要比写入内存性能较低很多,从而降低了服务器的吞吐量。可使用固态硬盘来提高读写速度,以达到缓解消息持久化的缺点。
答:使用 Java 代码连接 RabbitMQ 有以下两种方式:
方式一:
public static Connection GetRabbitConnection() {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(Config.UserName);
factory.setPassword(Config.Password);
factory.setVirtualHost(Config.VHost);
factory.setHost(Config.Host);
factory.setPort(Config.Port);
Connection conn = null;
try {
conn = factory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
方式二:
public static Connection GetRabbitConnection2() {
ConnectionFactory factory = new ConnectionFactory();
// 连接格式:amqp://userName:password@hostName:portNumber/virtualHost
String uri = String.format("amqp://%s:%s@%s:%d%s", Config.UserName, Config.Password, Config.Host, Config.Port,
Config.VHost);
Connection conn = null;
try {
factory.setUri(uri);
factory.setVirtualHost(Config.VHost);
conn = factory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
答:代码如下:
public static void main(String[] args) { publisher(); // 生产消息 consumer(); // 消费消息 } /** * 推送消息 */ public static void publisher() { // 创建一个连接 Connection conn = ConnectionFactoryUtil.GetRabbitConnection(); if (conn != null) { try { // 创建通道 Channel channel = conn.createChannel(); // 声明队列【参数说明:参数一:队列名称,参数二:是否持久化;参数三:是否独占模式;参数四:消费者断开连接时是否删除队列;参数五:消息其他参数】 channel.queueDeclare(Config.QueueName, false, false, false, null); String content = String.format("当前时间:%s", new Date().getTime()); // 发送内容【参数说明:参数一:交换机名称;参数二:队列名称,参数三:消息的其他属性-routing headers,此属性为MessageProperties.PERSISTENT_TEXT_PLAIN用于设置纯文本消息存储到硬盘;参数四:消息主体】 channel.basicPublish("", Config.QueueName, null, content.getBytes("UTF-8")); System.out.println("已发送消息:" + content); // 关闭连接 channel.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } } /** * 消费消息 */ public static void consumer() { // 创建一个连接 Connection conn = ConnectionFactoryUtil.GetRabbitConnection(); if (conn != null) { try { // 创建通道 Channel channel = conn.createChannel(); // 声明队列【参数说明:参数一:队列名称,参数二:是否持久化;参数三:是否独占模式;参数四:消费者断开连接时是否删除队列;参数五:消息其他参数】 channel.queueDeclare(Config.QueueName, false, false, false, null); // 创建订阅器,并接受消息 channel.basicConsume(Config.QueueName, false, "", new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String routingKey = envelope.getRoutingKey(); // 队列名称 String contentType = properties.getContentType(); // 内容类型 String content = new String(body, "utf-8"); // 消息正文 System.out.println("消息正文:" + content); channel.basicAck(envelope.getDeliveryTag(), false); // 手动确认消息【参数说明:参数一:该消息的index;参数二:是否批量应答,true批量确认小于index的消息】 } }); } catch (Exception e) { e.printStackTrace(); } } }
答:RabbitMQ 消费类型也就是交换器(Exchange)类型有以下四种:
RabbitMQ 默认的是 direct 方式。
答:RabbitMQ 使用 ack 消息确认的方式保证每个消息都能被消费,开发者可根据自己的实际业务,选择 channel.basicAck()
方法手动确认消息被消费。
答:RabbitMQ 接收到消息之后可以不消费,在消息确认消费之前,可以做以下两件事:
A:cn.mq.rabbit.*
B:#.error
C:cn.mq.*
D:cn.mq.#
答:C
题目解析:“*”用于匹配一个分段(用“.”分割)的内容,“#”用于匹配 0 和多个字符。
A:topic 交换器
B:fanout 交换器
C:direct 交换器
D:以上都不是
答:C
题目解析:fanout 和 topic 都是广播形式的,因此无法获取历史消息,而 direct 可以。
答:RabbitMQ 包含事务功能,主要是对信道(Channel)的设置,主要方法有以下三个:
答:RabbitMQ 的事务在 autoAck=true 也就是自动消费确认的时候,事务是无效的。因为如果是自动消费确认,RabbitMQ
会直接把消息从队列中移除,即使后面事务回滚也不能起到任何作用。
答:Kafka 不能脱离 ZooKeeper 单独使用,因为 Kafka 使用 ZooKeeper 管理和协调 Kafka 的节点服务器。
答:Kafka 有两种数据保存策略:按照过期时间保留和按照存储的消息大小保留。
答:这个时候 Kafka 会执行数据清除工作,时间和大小不论哪个满足条件,都会清空数据。
答:以下情况可导致 Kafka 运行变慢:
答:Kafka 集群使用需要注意以下事项:
答:MySQL 执行一条查询的流程如下:
答:MySQL 查询缓存功能是在连接器之后发生的,它的优点是效率高,如果已经有缓存则会直接返回结果。
查询缓存的缺点是失效太频繁导致缓存命中率比较低,任何更新表操作都会清空查询缓存,因此导致查询缓存非常容易失效。
答:MySQL 的常用引擎有 InnoDB、MyISAM、Memory 等,从 MySQL 5.5.5 版本开始 InnoDB 就成为了默认的存储引擎。
答:InnoDB 和 MyISAM 最大的区别是 InnoDB 支持事务,而 MyISAM 不支持事务,它们其他主要区别如下:
答:普通索引查询到主键索引后,回到主键索引树搜索的过程,我们称为回表查询。
答:不是,如果把主键删掉了,那么 InnoDB 会自己生成一个长度为 6 字节的 rowid 作为主键。
答:如果这张表的引擎是 MyISAM,那么 ID=4,如果是 InnoDB 那么 ID=2(MySQL 8 之前的版本)。
答:共享表空间指的是数据库的所有表数据,索引文件全部放在一个文件中,默认这个共享表空间的文件路径在 data 目录下。
独立表空间:每一个表都将会生成以独立的文件方式来进行存储。
共享表空间和独立表空间最大的区别是如果把表放再共享表空间,即使表删除了空间也不会删除,因此表依然很大,而独立表空间如果删除表就会清除空间。
A:delete from t
B:delete t
C:drop table t
D:truncate table t
答:D
题目解析:truncate 清除表数据不会写日志,delete 要写日志,因此 truncate 的效率要高于 delete。
答:唯一索引和普通索引的性能对比分为以下两种情况:
答:left join 和 right join 的区别如下:
答:最左匹配原则也叫最左前缀原则,是 MySQL
中的一个重要原则,指的是索引以最左边为起点任何连续的索引都能匹配上,当遇到范围查询(>、<、between、like)就会停止匹配。
生效原则来看以下示例,比如表中有一个联合索引字段 index(a,b,c):
select * from t where num=10 or num=20;
答:如果使用 or 查询会使 MySQL 放弃索引而全表扫描,可以改为:
select * from t where num=10
union
select * from t where num=20;
答:事务是一系列的数据库操作,是数据库应用的基本单位。
在 MySQL 中只有 InnoDB 引擎支持事务,它的四个特性如下:
答:MySQL 中有四种事务隔离级别,分别是:
MySQL 默认使用 repetable read 的事务隔离级别。
答:MySQL 事务隔离级别 mysql.cnf 文件里设置的(默认目录 /etc/my.cnf),在文件的文末添加配置:
transaction-isolation = REPEATABLE-READ
可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。
答:解决 MySQL 中文乱码的问题,可以设置全局编码或设置某个数据库或表的编码为 utf8。 设置全局编码:
set character_set_client='utf8';
set character_set_connection='utf8';
set character_set_results='utf8';
设置数据库的编码:
alter database db character set utf8;
设置表的编码:
alter table t character set utf8;
答:因为 B 树、Hash、红黑树或二叉树存在以下问题。
答:MySQL 对待死锁常见的两种策略:
答:全局锁就是对整个数据库实例加锁,它的典型使用场景就是做全量逻辑备份,这个时候整个库会处于完全的只读状态。
答:使用全局锁会使整个系统不能执行更新操作,所有的更新业务会出于等待状态;如果你是在从库进行备份,则会导致主从同步严重延迟。
答:InnoDB 的锁算法包括以下三种:
答:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则 InnoDB 将使用表锁。使用 for update 来实现行锁,具体脚本如下:
select * from t where id=1 for update
其中 id 字段必须有索引。
答:MySQL 最重要的性能指标有以下两个:
这些性能指标可以通过 show status 来查询当前数据库状态的结果信息中估算出来,show status 会有 300
多条状态信息记录,其中以下这些信息 QPS 和 TPS 有关系:
① 错误日志 :用来记录 MySQL 服务器运行过程中的错误信息,比如,无法加载 MySQL
数据库的数据文件,或权限不正确等都会被记录在此,还有复制环境下,从服务器进程的信息也会被记录进错误日志。默认情况下,错误日志是开启的,且无法被禁止。默认情况下,错误日志是存储在数据库的数据文件目录中,名称为
hostname.err,其中 hostname 为服务器主机名。在 MySQL 5.5.7
之前,数据库管理员可以删除很长时间之前的错误日志,以节省服务器上的硬盘空间, MySQL 5.5.7
之后,服务器将关闭此项功能,只能使用重命名原来的错误日志文件,手动冲洗日志创建一个新的,命令为:
mv hostname.err hostname.err.old
mysqladmin flush-logs
② 查询日志 :查询日志在 MySQL 中被称为 general log(通用日志),查询日志里的内容不要被“查询日志”误导,认为里面只存储
select 语句,其实不然,查询日志里面记录了数据库执行的所有命令,不管语句是否正确,都会被记录,具体原因如下:
因此都会产生日志,在并发操作非常多的场景下,查询信息会非常多,那么如果都记录下来会导致 IO 非常大,影响 MySQL
性能。因此如果不是在调试环境下,是不建议开启查询日志功能的。
查询日志的开启有助于帮助我们分析哪些语句执行密集,执行密集的 select
语句对应的数据是否能够被缓存,同时也可以帮助我们分析问题,因此,可以根据自己的实际情况来决定是否开启查询日志。
查询日志模式是关闭的,可以通过以下命令开启查询日志:
set global general_log=1
set global log_output=‘table’;
general_log=1 为开启查询日志,0 为关闭查询日志,这个设置命令即时生效,不用重启 MySQL 服务器。
③ 慢日志 :慢查询会导致 CPU、IOPS、内存消耗过高,当数据库遇到性能瓶颈时,大部分时间都是由于慢查询导致的。开启慢查询日志,可以让
MySQL
记录下查询超过指定时间的语句,之后运维人员通过定位分析,能够很好的优化数据库性能。默认情况下,慢查询日志是不开启的,只有手动开启了,慢查询才会被记录到慢查询日志中。使用如下命令记录当前数据库的慢查询语句:
set global slow_query_log=‘ON’;
使用 set global slow_query_log=‘ON’ 开启慢查询日志,只是对当前数据库有效,如果 MySQL
数据库重启后就会失效。因此如果要永久生效,就要修改配置文件 my.cnf,设置 slow_query_log=1 并重启 MySQL 服务器。
④ redo log(重做日志) :为了最大程度的避免数据写入时,因为 IO 瓶颈造成的性能问题,MySQL
采用了这样一种缓存机制,先将数据写入内存中,再批量把内存中的数据统一刷回磁盘。为了避免将数据刷回磁盘过程中,因为掉电或系统故障带来的数据丢失问题,InnoDB
采用 redo log 来解决此问题。
⑤ undo log(回滚日志) :用于存储日志被修改前的值,从而保证如果修改出现异常,可以使用 undo log 日志来实现回滚操作。
undo log 和 redo log 记录物理日志不一样,它是逻辑日志,可以认为当 delete 一条记录时,undo log 中会记录一条对应的
insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录,当执行 rollback 时,就可以从 undo
log 中的逻辑记录读取到相应的内容并进行回滚。undo log 默认存放在共享表空间中,在 ySQL 5.6 中,undo log
的存放位置还可以通过变量 innodb_undo_directory 来自定义存放目录,默认值为“.”表示 datadir 目录。
⑥ bin log(二进制日志) :是一个二进制文件,主要记录所有数据库表结构变更,比如,CREATE、ALTER TABLE
等,以及表数据修改,比如,INSERT、UPDATE、DELETE 的所有操作,bin log 中记录了对 MySQL
数据库执行更改的所有操作,并且记录了语句发生时间、执行时长、操作数据等其他额外信息,但是它不记录 SELECT、SHOW 等那些不修改数据的 SQL 语句。
binlog 的作用如下:
除了上面介绍的几个作用外,binlog 对于事务存储引擎的崩溃恢复也有非常重要的作用,在开启 binlog 的情况下,为了保证 binlog 与 redo
的一致性,MySQL 将采用事务的两阶段提交协议。当 MySQL 系统发生崩溃时,事务在存储引擎内部的状态可能为 prepared(准备状态)和
commit(提交状态)两种,对于 prepared 状态的事务,是进行提交操作还是进行回滚操作,这时需要参考 binlog,如果事务在 binlog
中存在,那么将其提交;如果不在 binlog 中存在,那么将其回滚,这样就保证了数据在主库和从库之间的一致性。
binlog 默认是关闭状态,可以在 MySQL 配置文件(my.cnf)中通过配置参数 log-bin = [base-name] 开启记录 binlog
日志,如果不指定 base-name,则默认二进制日志文件名为主机名,并以自增的数字作为后缀,比如:mysql-
bin.000001,所在目录为数据库所在目录(datadir)。
通过以下命令来查询 binlog 是否开启:
show variables like ‘log_%’;
binlog 格式分为 STATEMENT、ROW 和 MIXED 三种。
redo log(重做日志)和 binlog(归档日志)都是 MySQL 的重要的日志,它们的区别如下:
最开始 MySQL 里并没有 InnoDB 引擎,MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe
的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe
能力的,因此 InnoDB 使用另外一套日志系统,也就是 redo log 来实现 crash-safe 能力。
答:慢查询日志的常见获取方式如下。
slow-query-log=On
开启慢查询,慢查询默认时长为 10s,默认存储文件名为 host_name-slow.log。答:使用 MySQL 中的 explain 分析执行语句,比如:
explain select * from t where id=5;
如下图所示:
其中:
其中最重要的就是 type 字段,type 值类型如下:
答:MySQL 中常见的读写分离方案通常为以下两种:
答:通常保证主备数据库无延迟有以下三种方法。
答:MySQL 多实例就是在同一台服务器上启用多个 MySQL
服务,它们监听不同的端口,运行多个服务进程,它们相互独立,互不影响的对外提供服务,便于节约服务器资源与后期架构扩展。 多实例的配置方法有两种:
「参考答案」常见的大表优化策略如下。
「参考答案」数据库分片方案有哪些? 答:数据库创建的分片方案有两种方式:客户端代理方式和中间件代理方式。
「参考答案」常见优化方案如下:
「参考答案」可能是积累的长连接导致内存占用太多,被系统强行杀掉导致的异常重启,因为在 MySQL
中长连接在执行过程中使用的临时内存对象,只有在连接断开的时候才会释放,这就会导致内存不断飙升,解决方案如下:
答:Redis 使用场景如下:
答:Redis 功能如下:
答:Redis 支持的数据类型如下:
答:Redis 相比 Memcached 优势如下:
答:Redis 淘汰策略如下:
答:Redis 官方是不支持 Windows 版的,因为目前 Linux 版本已经相当稳定,如果开发 Windows 版本,反而会带来兼容性等问题。
答:因为 Redis 的瓶颈最有可能是机器内存或者网络带宽,而非单线程,既然单线程不是 Redis 的性能瓶颈,并且单线程又比较容易实现,所以 Redis
就选择使用单线程来实现。
单线程并不代表运行速度就慢,比如,Nginx 和 NodeJs 都是单线程高性能的代表。
答:Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘,这样 Redis 就拥有了快速查询和数据持久化等特征。
答:最大容量 512 MB,官方说明如下图所示:
答:Jedis 和 Redisson 的区别如下:
答:Redis 通过 expire() 方法设置过期时间,语法:redis.expire(key, expiration)。当 expire
的过期时间设置为 -1 时,表示永不过期。
答:可使用以下方法来保证 Redis 的数据一致性:
答:Redis
的数据结构是跳跃表,跳跃表是一种基于链表的扩展,跳跃表还是一个链表,是一个有序的链表,在遍历的时候基于比较,但普通链表只能遍历,跳跃表加入了一个层的概念,层级越高元素越少,每次先从高层查找,直到找到合适的位置,从图中可以看到高层的节点远远少于底层的节点数,从而实现了跳跃式查找。
跳跃表优点:
跳跃表缺点:
答:第一是因为红黑树存储比较复杂,调整涉及到多个节点的并发修改;第二是越接近根节点的地方越容易产生竞争,即使是不同叶子节点的操作由于平衡操作也可能逐级向上涉及到接近根的节点,而跳跃表可以用
CAS(Compare And Swap)来并发操作节点,比较容易实现,且更加局部化。
答:缓存穿透是指查询一个一定不存在的数据,由于缓存中没有,因而每次需要从数据库中查询,但数据库也没有相应的数据,所以不会写入缓存,这就将导致每次请求都会去数据库查询,这种行为就叫缓存穿透。
解决方案是不管查询数据库是否有数据,都缓存起来,只不过把没有数据的缓存结果的过期时间设置为比较短的一个值,比如 3 分钟。
答:指缓存由于某些原因,比如,宕机或者缓存大量过期等,从而导致大量请求到达后端数据库,进而导致数据库崩溃的情况。
解决缓存雪崩的方案如下:
答:缓存预热是指系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。
缓存预热的实现方式,可分为以下两种:
答:在 Java 程序中可使用 Jedis 来操作 Redis,使用步骤如下:
1)添加 Jedis 引用
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>x.x.x</version>
</dependency>
2)连接并操作 Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
// 存值
jedis.set("hello","world");
// 取值
jedis.get("hello");
// 关闭连接
jedis.close();
答:Redis 持久化是指将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 Redis 有以下两种持久化方案:
Redis 默认支持的持久化方式是 RDB 方式。
答:RDB 和 AOF 的区别如下:
总体来说如果对数据的完整性要求不高,RDB 是最好的解决方案,反之则选择 AOF。
答:常用的 Redis 监控工具如下:
答:使用 slowlog get 来定位慢查询操作,如下所示:
127.0.0.1:6379> slowlog get
- (integer) 0
- (integer) 1565937939
- (integer) 28003
- “lpush”
2) "list"
- 1
3) "1"
- 1
4) "2"
- 1
5) "6"
- 1
6) "3"
- 1
7) "4"
- 1
8) "9"
- 1
9) "8"
- 1
其中:
表示慢查询记录 id
表示发起命令的时间戳
表示命令耗时,单位为微秒
表示该条记录的命令及参数
答:SAVE 和 BGSAVE 都是用于 Redis 持久化的,它们的区别如下:
答:Redis 可以实现主从同步和从从同步。当第一次同步时,主节点做一次 BGSAVE,并同时将后续修改操作记录到内存中,待完成后将 RDB
文件全量同步到复制节点,复制节点接受完成后将 RDB
镜像加载到内存,加载完成后再通知主节点将期间修改的操作记录,同步到复制节点进行重放,这样就完成了同步过程。
答:Redis 不像 MySQL 等关系型数据库那样有数据库的概念,不同的数据存在不同的数据库中,Redis
数据库是由一个整数索引标识,而不是一个数据库名称,默认情况下客户端连接到数据库 0,可以在配置文件中控制数据库总数,默认是 16 个。
可以使用 select index 来切换数据库,如下所示:
127.0.0.1:6379> select 0
OK
答:Redis 集群策略有以下 3 种:
答:Redis 集群实现方案如下:
答:把相关的信息整体存储,而不是把每个信息独立存储,这样就可以有效的减少内存使用。
答:通常分布式锁在设计时,需同时满足以下四个约束条件。
答:集群的实现原理和集群的实现方式有关,如下所述:
答:Redis 常见性能问题如下:
lo",“world”);
// 取值
jedis.get(“hello”);
// 关闭连接
jedis.close();
答:Redis 持久化是指将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 Redis 有以下两种持久化方案:
Redis 默认支持的持久化方式是 RDB 方式。
答:RDB 和 AOF 的区别如下:
总体来说如果对数据的完整性要求不高,RDB 是最好的解决方案,反之则选择 AOF。
答:常用的 Redis 监控工具如下:
答:使用 slowlog get 来定位慢查询操作,如下所示:
127.0.0.1:6379> slowlog get
- (integer) 0
- (integer) 1565937939
- (integer) 28003
- “lpush”
2) "list"
- 1
3) "1"
- 1
4) "2"
- 1
5) "6"
- 1
6) "3"
- 1
7) "4"
- 1
8) "9"
- 1
9) "8"
- 1
其中:
表示慢查询记录 id
表示发起命令的时间戳
表示命令耗时,单位为微秒
表示该条记录的命令及参数
答:SAVE 和 BGSAVE 都是用于 Redis 持久化的,它们的区别如下:
答:Redis 可以实现主从同步和从从同步。当第一次同步时,主节点做一次 BGSAVE,并同时将后续修改操作记录到内存中,待完成后将 RDB
文件全量同步到复制节点,复制节点接受完成后将 RDB
镜像加载到内存,加载完成后再通知主节点将期间修改的操作记录,同步到复制节点进行重放,这样就完成了同步过程。
答:Redis 不像 MySQL 等关系型数据库那样有数据库的概念,不同的数据存在不同的数据库中,Redis
数据库是由一个整数索引标识,而不是一个数据库名称,默认情况下客户端连接到数据库 0,可以在配置文件中控制数据库总数,默认是 16 个。
可以使用 select index 来切换数据库,如下所示:
127.0.0.1:6379> select 0
OK
答:Redis 集群策略有以下 3 种:
答:Redis 集群实现方案如下:
答:把相关的信息整体存储,而不是把每个信息独立存储,这样就可以有效的减少内存使用。
答:通常分布式锁在设计时,需同时满足以下四个约束条件。
答:集群的实现原理和集群的实现方式有关,如下所述:
答:Redis 常见性能问题如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。