面试讲解3

Spring MVC的五大核心组件

1. DispatcherServlet

前端控制器,用于统一接受请求并分发,组织处理请求的流程

  • 如果是使用web.xml文件配置项目的web环境的项目,需要显示的在web.xml配置它,例如影射的请求路径,并确保它是web容器(例如tomcat)在启动时就初始化的,并在初始化时会加载spring环境。
  • 如果是使用spring注解来配置springMVC环境的先买个,则不需要直接配置它,而是通过自定义一个AbstractAnnotationConfigDispatcherServletInitializer的子类来直接配置,例如配置它映射的请求路径
  • 如果是使用SpringBoot框架的web项目,甚至可以不需要知道它的存在,默认映射的路径是/*,当然,如果认为由必要的话,也可以配置为其他值。

2. HandlerMapping

它是一个接口,spring MVC框架内置了简单的实现类:SimpleUrlHandlerMapping,用于影射请求路径与处理请求的控制器,但是,在实际应用中,并不会直接使用这个实现,二十使用@RequestMapping注解,或者进阶的@PostMapping、@GetMapping,直接配置请求路径与处理请求的方法的映射关系。

3. Controller

由开发人员创建的,实际处理请求的控制器

  • 通常由多个,一般根据需要处理的请求所涉及的核心数据类型来区分,例如与用户相关的请求则创建UserController,与订单相关的请求则创建OrderController
  • 这些类必须放在组件扫描的包或其子孙包下,并添加@Controller注解,或添加@RestController注解,由于主流的开发模式是服务器端向客户端响应正文,通常使用的是@RestController
  • 一般还会在这些类的声明之前添加@RequestMapping注解,以统一设计URL中的前缀路径
  • 每个控制类中将有若干个处理请求的方法

4. ModelAndView

Controller组件处理完请求后得到的结果,由数据与视图名称组成

  • 在实际开发中,由于springMVC提供了更加便捷的API,通常并不直接使用这个类型作为方法的返回值,当需要转发数据时,可以在方法的参数列表中添加ModelMap对象用于封装需要转发的数据,并使用string类型的返回值表示视图的名称,如果需要重定向,则返回以redirect:作为前缀的string即可。
  • 主流的开发模式时服务器端向客户端响应正文,完全不需要使用该类型的对象。

5. ViewResolver

视图解析器,可根据视图名称(由ModelAndView返回)确定需要使用的视图组件

  • Spring MVCM架内置了简单的实现类:InternalResourceViewResolver,当使
    用JSP文件作为视图时将使用它,主要配置prefix和suffix这2个属性,Spring
    MVC框架会将prefix的值、控制器返回的视图名称、suffix的值拼接起来,以确
    定JSP文件的位置并使用
  • 使用Thymeleaf的模版页面作为视图组件时,则配置ThymeleafViewResolver,
    配置思想大致相当,但需要另外配置Thymeleaf的模版引擎和模版解析器
  • 主流的开发模式是服务器端向客户端响应正文,完全不需要使用该组件
    在这里插入图片描述

tips:要完全了解五个核心组件的使用还需学习《基于XML配置的springMVC》扩展视频教程,技术较老。

springbean的生命周期

简单生命周期

  • 通常,需要讨论生命周期的类型,都是由开发人员编写,但不由开发人员自行管
理的,例如Java EE中的Servlet组件,它是由Web容器(例如Tomcat)进行管理
的,而Spring Bean是由Spring框架进行管理的。

  • 无论是Servlet,还是Spring Bean,都不由开发人员管理,所以,开发人员需要知道它们的生命周期,即:什么时候会被创建出来,什么时候会被销毁,并且,在什么情景下会被调用什么方法,以Servlet为例,幵发人员必须知道Servlet中的init()方法会在什么时候被调用,还有service()方法、destroy()方法,只有了解了生命周期方法,才能明确在哪个方法里编写什么代码。

    • 例如:当你在使用Servlet时,如果处理数据需要使用到某个流对象,那么,你
      不应该在Servlet的init()方法中调用流对象的close()方法
  • Spring允许你在类中自定义初始化和销毁的2个生命周期方法

    • 初始化方法:将在构造方法之后被调用
    • 销毁方法:将在销毁之前被调用
  • 这2个方法的声明:

    • 公有权限
      -被外部调用,应该使用公有权限
    • 返回值类型使用void
    • 方法名称自定义
    • 参数列表为空
      -可以添加boolean类型的参数
  • 仅符合单例特性的Spring Bean才需要讨论生命周期

    • 即:scope偵为singleton (默认)

      -不要称之为单例模式

    • 如果scope值为prototype,对象按需创建(通过开发人员的代码,什么时候获
取Bean什么时候创建),用完即销毁(通常是局部变量,方法运行结束后即销
毁),开发人员可以完全控制创建后、销毁前的操作,无需Spring管理。

完整的生命周期

请添加图片描述

  • 提示:Spring Bean的完整生命周期更加复杂,绝大部分环节是由spring框架自动完成,开发人员能够干预的部分非常有限,与自定义生命周期方法相差不大。
生命周期流程
  1. 实例化Bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于Applicationcontext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  2. 设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。

  3. 处理Aware接口: Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean

    • 实现BeanNameAware接口

      • 调用setBeanName(String beanld)方法,传递的是Bean的id
    • 实现BeanFactoryAware接口

      • 调用 setBeanFactory(BeanFactory bean Factory)方法,传递的是 Spring 工厂自身
    • 实现ApplicationContextAware接口

      • 调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
    • 小结:无论实现了哪种xxxAware接口,Spring都会自动调用其对应的方法,并且自动传递方法的参数

  4. BeanPostProcessor:如果想对Bean进行一些自定义的处理,可以使Bean实现BeanPostProcessor接口,该接口中包含会在初始化之前、初始化之后的2个方法:

    • Object postProcessBeforelnitialization(Object bean, String beanName) throws BeansException

      • 此时会调用
    • Object postProcessAfterlnitialization(Object bean, String beanName) throws BeansException

      • 后续会调用
  5. InitializingBean:如果实现了该接口,则调用afterPropertiesSet()方法

  6. 自定义初始化:如果Bean配置了初始化方法(自定义的),则会自动调用

    • 如果需要干预,直接操作这一步就行,之前的交给spring框架
  7. BeanPostprocessor:尝试调用postProcessAfterlnitialization()方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
    注意:至此,Bean就已经被正确创建了,之后就可以使用这个Bean了。

  8. DisposableBean:当Bean不再需要时(例如Spring Applicationcontext销毁之前),将准备销毁,如果Bean实现了DisposableBeanli口,会调用destroy()方法。

  9. 自定义销毁:如果Bean配置了销毁方法(自定义的),则自动调用。

面试总结

  • 讨论Spring Bean的生命周期是因为Bean由Spring框架进行管理,以致于我们能够明确Bean会在什么时候创建、什么时候销毁,并且,当有必要的情况下,能够在创建之后、销毁之前进行干预,执行自定义的一些代码,只有作用域为“单例”(即scope值为singleton)时才讨论这个问题,否则,当作用域为prototype时,Bean会在获取对象时创建,局部的Bean会在方法执行结束后被销毁,其间的过程我们可以完全把控,则不需要讨论生命周期的问题。

  • Spring Bean的完整生命周期大致有9个阶段:
    ① 实例化Bean
    ② 设置对象属性(依赖注入)
    ③ 【如果实现了接口】
    ④ 【如果实现了接口】
    ⑤ 【如果实现了接口】
    ⑥ 自定义初始化
    处理Aware接口
    BeanPostProcessor.postProcessBeforelnitialization()
    InitializingBean.afterPropertiesSetO
    ⑦ 【如果实现了接口】
    ⑧ 【如果实现了接口】
    ⑨ 自定义销毁

  • 比较常用的是自定义初始化方法和销毁方法,它们将分别在构造方法之后和销毁之前被调用

    • 如果使用XML文件配置<bean>,可以配置<bean>的init-method和destroy-method属性以应用这2个自定义方法。
    • 如果使用配置类的@Bean方法创建对象,可以在@Bean注解中配置init-method和d est roy- m eth od属性以应用这2个自定义方法。
    • 如果项目使用了javax包,还可以选择分别在这2个自定义方法之前添加@PostConstructffl@PreDestroy 注解。

事务

什么是事务

事务是数据库中保证一系列的写操作要么全部成功,么全部失败的机制

  • 如果只有1次写操作,可以不是事务性的
  • 如果只有读操作,可以不是事务性的
  • 如果有2次或以上次数的写操作,必须是事务性的

事务的传播表现

事务的传播表现为:某个数据访问过程中调用了另一个事务,事务应该如何执行

  • 当前数据访问过程可以不是事务性的
  • 当前数据访问过程也可以是事务性的,即:X事务调用Y事务
  • 例如某种传播方式为:暂停当前事务,执行另一个事务,完毕后继续执行当前
    事务
    在这里插入图片描述

注:绝大多数使用默认的传播方式即可

关于@Transactional注解

  • 在使用Spring管理事务的项目中,可以使用@Transactiona[注解,以表示某个方法是事务性的。

  • 通过@Transactional的propagation属性可以配置事务的传播方式。

    • 该注解是spring-tx.jar中的
      在这里插入图片描述
  • 可以添加在接口的声明之前,或添加在接口的实现类的声明之前

    • 推荐添加在接口的声明之前,因为实现类可能变化
    • 将作用于接口中声明的所有方法
  • 可以添加在接口中的抽象方法的声明之前,或添加在接口的实现类中实现的方法的声明之前

    • 推荐添加在接口中的抽象方法的声明之前,因为实现类可能变化
    • 仅作用于当前方法
    • 如果接口/实现类和方法都添加了该注解,并配置不同,则方法级的配置会覆盖接口/类级的配置
  • 通常应用于业务层,并且,业务层必须有接口和其实现类

    • Spring管理事务是基于接口代理的
      • 如果没有接口,却使用了(©Transactional,启动项目时会报错
      • 当出现类内部的方法之间的调用,被调用的方法之前的@Transactional无效
  • Spring框架管理事务,帮助幵发人员解决了不同数据库编程技术在处理事务时需
    要编写的代码不同的问题

    • JDBC:需要使用Connection对象调用setAutoCommit(false)、commit。、
      rollback。
    • JPA:需要使用EntityManager对象调用getTransaction()得到Transaction对
      象,然后使用Transaction对象调用begin()、commit()、rollback()。
    • Hibernate:需要使用会话对象调用beginTransaction(),并使用Transaction
      对象调用commit()、 rollback()
    • 还有其它数据库编程技术,需要编写的代码也不相同
  • Spring提取了本地事务和分布式事务的区别,在编写业务代码时,只需要掌握@Transactional注解的正确使用即可,无需关注事务是本地的还是分布式的

    • 如果需要使用分布式事务,配置分布式事务的事务管理器即可

spring管理事务的原理

  • Spring会生成接口的代理对象,并通过AOP实现管理事务的

    • 你的业务将被包裹在代理中

    • 使用"around"类型的Advice

  • Spring管理事务的过程中添加的事务管理行为:

    • 在执行你的业务方法之前,开启事务
    • 如果业务方法顺利执行结束,则提交事务
    • 如果业务方法抛出异常,则回滚事务
    • 默认情况下,仅当抛出RuntimeException时回滚,可以通过@Transactional注解的rollbackFor和noRollbackFor属性进行配置

事务总结

  1. 事务的传播方式共有了种:

    • MANDATORY
    • NEVER
    • NONSUPPORTED
    • SUPPORTS
    • REQUIRED
    • REQUIRES_NEW
    • NESTED
  2. 事务注解是@TranscationaL 这是Spring框架(spring-tx.jar,当添加spring-jdbc.jar时会依赖)定义的注解。

  3. Spring框架会基于接口代理模式来管理事务,其实现原理上使用了AOP技术,使用Around类型的Advice包裹了业务方法,以添加事务管理行为。

  4. Spring框架管理事务,帮助开发人员解决了不同数据库编程技术在处理事务时需
    要编写的代码不同的问题,也提取了本地事务和分布式事务的区别,当需要使用
    分布式事务时,只需要配置事务管理器即可,业务代码的编写方式不变。

  5. Spring框架管理事务时,会在代理对象调用业务方法之前幵启事务,如果业务方
    法顺利执行结束,则提交事务,如果业务方法抛出异常,则回滚事务,默认情况
    下,仅当抛出RuntimeException时回滚,可以通过@Transactional注解的
    rollbackFor 和 noRollbackFor 属性进行配置。

  6. 实际使用原则:

    • 通常在业务层处理事务,当使用Spring管理事务时,由于它是基于接口代理模式的,所以必须先创建业务接口,再创建业务实现类,如果某个业务方法涉及多次(超过1次)写操作,那么,它需要是事务性的,则可以在方法的声明之前添加@Transactional注解,或者,也可以将注解添加在业务接口的声明之前,这会使得当前接口中的所有方法都是事务性的,当然,还可以将注解添加在业务实现类的声明之前,或业务实现类中实现的方法的声明之前,由于实现是可能调整的,所以更推荐在接口中使用这个注解。

    • 当需要管理事务的传播方式时,配置@Transactional注解的propagation属性即可,在绝大部分情况下,没有必要刻意的设置事务的传播方式,使用默认的REQUIRED即可,它表现为:如果当前无事务,将创建新的事务,如果当前已存在事务,则使用当前事务。

Mybatis

Mybatis的分页实现

Mybatis本身没有对分页的专门处理,开发人员在编写SQL语句时使用LIMIT子句自行分页即可:

List<User> findList(@Param("offset")Integer offset,
                   @Param("count")Integer count);
<select id="findList" resutMap="BaseResultMap">
	SELECT * FROM user LIMIT #{offset}, #{count}
</select>
产生的问题
  • 查询的数据大多需要响应到客户端,当客户端显示数据列表时,仅仅响应一个数
    据列表是不够的,通常还需要分页相关的多个数据,例如:
    • 当前第几页
      • 查询的已知条件
    • 共几页(总页数)
    • 其它数据
  • 为了满足客户端显示数据列表的需求,还需要自行查询总记录数,并计算总页数
    及相关数据
PageHelper 插件
  • PageHelper是一款专门用于Mybatis的分页插件

    • https://github.com/pagehelper/Mybatis-PageHelper
    • 提示:之所以将PageHelper称之为“插件”,是因为它是基于Mybatis拦截器
      实现的,而Mybatis将拦截器定位为Plugin (插件)
  • 你只需要在执行查询之前设置页码与查询结果最大数量即可:

    //查询结果最大数量,也是每页数量,应该是相对固定的值
    //可能来自配置文件,或用于配置的数据表
    private Integer count;
    
    public PageInfo<User> findList(Integer page) {
        PageHelper.startPage (page, count);
        List<User> list = userMapper.findList();
        PageInfo<User> pagelnfo = new Pagelnfo<>(list);
        return pageInfo;
    }
    
  • 按以上方式执行查询,得到的结果(Pagelnfo对象)中将封装了许多实用的分页
    数据,例如:

    • 查询得到的List集合
    • 最大页数
      • 它在执行你的查询之前,会先查询总记录数
    • 当前页码
    • 是否有上一页
    • 是否有下一页
    • 是否为第1页
    • 是否为最后一页
    • 其它
  • PageHelper对数据访问层代码是”非侵入性“的

    • 你不需要调整你在使用Mybatis时编写的任何数据访问层的代码,只需要在业务逻辑层调用数据访问层之前添加代码即可实现分页。
面试题考察点
  • 关于“Mybatis的分页实现”问题的考察点应该在于:Mybatis的拦截器。
拦截器:
  1. Mybatis提供了拦截器(Interceptor)接口,允许幵发者自定义执行拦截器:
@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, 
                        args = {Connection.classr Integer.class}),
			 @Signature(method = "query", type = Executor.class,
                        args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class})})
public class CustomMybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Th rowable {
//编写你的实现
return invocation.proceed(
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
@Override
public void setProperties(Properties properties) {
}
}
  1. 在配置SqlSessionFactoryBean时应用拦截器

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)
    				throws Exception {
        SqlSessionFactoryBean SqlSessionFactoryBean = new SqlSessionFactoryBean();
        SqlSessionFactoryBean.setDataSource(dataSource);
        SqlSessionFactoryBean.setMapperLocations(mapperLocations);
        SqlSessionFactoryBean,setPlugins(new CustomMybatisInterceptor());
        return sqlSessionFactoryBean;
    }
    
  2. 如果已经配置好了SqlSessionFactory,添加拦截器:

    @Configuration
    @ConditionalOnBean(SqlSessionFactory.class)
    @AutoConfigureAfter(Mybatis的配置类.class)
    public class MybatisInterceptorConfiguration {
        @Autowired
        private List<SqlSessionFactory> sqlSessionFactoryList;
        
        @PostConstruct
        public void addlnterceptor() {
            for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
                sqlSessionFactory.getConfigurationf).addlnterceptorf
                    new CustomMybatisInterceptor());
            }	
        }	
    }
    
  • Mybatis的拦截器可以做到分页效果,但是,实现成本比较高:

    • 需要创建拦截器
    • 需要配置应用拦截器
    • 拦截器内部设定相关规则
      • 对哪些方法执行拦截并分页
      • 分页参数(offset/count)如何获取
  • 所以,一般不手动编写拦截器实现分页,而是使用PageHelper

  • PageHelper也是通过Mybatis拦截器实现的
    在这里插入图片描述

  • 如果在Springs Mybatis框架下使用PageHelper,必须自行配置它的Mybatis拦截器

  • 如果在Spring Boot项目中添加的是pagehelper-spring-boot-starter依赖,则它已经自动注册了 Mybatis拦截器,你可以直接使用。

  • 注意:PageHelper在处理分页参数时,使用了ThreadLocal,即分页参数与线程
    是绑定的,所以是线程安全的,但PageHelper.startPage()与调用持久层查询必
    须是连续的2条语句:

面试参考
  • 如果在面试时,面试官出了这道题,其考察的知识点应该是:
    • 是否了解过Mybatis的拦截器
    • 是否知道PageHelper的实现原理
  • 为避免被面试官的后续问题难倒,建议:
    • 专门学习Mybatis的拦截器
      • 需对Mybatis的执行原理有一定了解
    • 查看PageHelper的拦截器相关源代码
      • 难度不算大
总结
  1. 在使用Mybatis编程时,可以在编写SQL语句时添加LIMIT子句,并在查询方法中添加LIMIT子句所需的参数,即可实现分页查询,但是,分页查询可能还需要总页数等其它数据,需要专门查询总记录数并计算,操作相对繁琐。
  2. 在实际幵发时,可以使用PageHelper分页插件,它是专门用于Mybatis框架的,本质上是使用Mybatis拦截器实现的,如果在Spring- Mybatis框架下使用PageHelper,必须自行配置它的Mybatis拦截器,如果在Spring Boot项目中添加的是pagehelper-spring-boot-starter依赖,则它已经自遂注册了Mybatis拦截器。
  3. 使用PageHelper插件时,持久层代码不需要关心分页相关的内容,仅需要调用持久层查询之前调用PageHelper.startPage()方法即可实现分页,并且,还可以将查询到的结果用于创建Pagelnfo对象,该对象中封装了许多实用的分页数据,例如当前页数、总页数等。

Mybatis缓存机制

缓存的概念
  • 将数据临时存储在某个位置,并且,通常伴随特定的清除临时存储的数据的机制
    • 通常,从临时存储位置获取数据的成本更低
      • 使用Mybatis查询数据时,完整来说是查询数据库中的数据,则需要连接到数据库服务器,并且从数据库服务器的硬盘中查询所需的数据,无论是连接,还是查询硬盘数据,都是比较耗时的,则可以事先(第1次查询时,或者更早时……)将数据查出来并保存到应用服务器的内存中,则需要数据时直接使用即可。
缓存的作用
  • 提高获取数据的效率
    • 在绝大部分应用程序中,查询在所有数据访问(增、删、改、查)中的占比可
      能超过80%,因此,查询效率非常重要
缓存机制

Mybatis有2种缓存机制,分别称之为一级缓存和二级缓存,当需要查询数据时,会优先从二级缓存中查询数据,如果没有命中,则从一级缓存中获取,如果仍未命中,则从数据库中查询数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

Mybatis的一级缓存
  • 会话缓存:基于SqISession的缓存

    • SqISession:类似JDBC的Connection,但它在Mybatis中负责许多操作,例如处理SQL语句中的参数、执行SQL语句、获取执行结果、处理事务……
    • 仅同一会话、执行相同查询、参数相同时生效
      -例如:在同一会话中多次执行select count(*) from user ,或多次执行
      select * from user where id=? 且 ?的值相同
  • 会话缓存的清理

    • 会话结束
      -自然结束,或显式的调用了SqlSession的close。方法
    • 执行了任何写操作(增、删、改)之后
      -一旦执行了写操作,此前缓存的结果可能不准确
    • 执行了任何清理缓存的方法
      -例如:执行了SqlSession的dearCache()方法
  • 会话缓存的特点:

    • 由Mybatis自行管理
      -通常,幵发人员不需要干预,绝大部分过程也无法干预
    • 主要适用于同一会话短时间内连续的相同查询
      -不同会话、不同查询,甚至查询参数不同时,均不适用,所以应用场景较少
  • 练习指导(基于Spring Boot的项目):

    • 在application.properties中将日志级别设置为较低,例如trace

      • Mybatis默认会岀输出日志,包括执行的SQL语句、参数、执行结果等,只是日志级别较低,默认并不显示

      • logging.level.当前项目根包=trace

    • 在测试类中进行测试

      • 自动装配Sq(SessionFactory
      • 在测试方法中:
        -调用SqISessionFactory的openSession()方法,得到SqISession对象
        -调用SqISession对象的getMapper。方法,得到Mapper接口的代理对象
    • 反复执行相同的查询,可通过观察到:只执行了 1次SQL语句
      -如果是较慢的查询,仅首次较慢,后续每一次几乎无耗时

    • 更换查询参数(值必须不同),可以观察到:再次执行了SQL语句
      -如果是较慢的查询,更换参数后的首次查询仍较慢

    • 通过SqlSessionFactory得到新的SqlSession,并再次获取Mapper接口的对象,再查询,可以观察到:即使此前已经执行完全相同的查询,仍再次执行了SQL语句

Mybatis的二级缓存
  • namespace缓存:基于Mapper接口的缓存

    • 默认未幵启
      • 整合环境下,全局开启,各namespace未开启
    • 无视是否同一会话
  • 开启namespace缓存

    1. 添加节点

      <mapper namespace="xxxxxxx.OrderMapper">
      	<cache/>
          ......
      </mapper>
      
    2. 封装结果的类实现Serializable

  • 关闭某些查询的缓存

    在select语句配置加上useCache=“false”。

    <select id="xxx" resultType="xxxx.xxentity" useCache="false"></select>
    
  • namespace缓存的特点:

    • 除非某些<select>节点配置了useCache=“false”,否则,当前namespace所有查询结果都会缓存
    • 所有写操作(增、删、改)都会清理缓存
    • 默认使用LRU (Least Recently Used:最近最少使用)算法清除
缓存总结
  • 一级缓存:

    • 由Mybatis自行管理,一定程度人为不可控
    • 主要适用于同一会话短时间内连续的相同查询,适用场景少
  • 二级缓存

    • 不同会话均可共用
    • 需要手动开启
    • 不需要手动管理,默认使用LRU算法进行清理
    • 同一个namespace中某些查询可配置为不缓存,但同一个namespace中幵启的多种查询的缓存的配置相同
  • 无论是哪种缓存(包括Mybatis以外的其它缓存机制),都是“牺牲空间、换取时间”的做法

    • 牺牲更多的存储空间(占用原本不会占用的空间),提高执行效率,以换取更多的可用时间
  • 缓存的“效率”和“有效”难两全

    • 即使缓存了数据,其它操作可能更新了数据
      -如果不同步更新缓存,则缓存的数据不准确,甚至可能是无效数据
      -如果同步更新缓存,则需要牺牲一定的效率
  • 使用缓存之前,你必须考虑:

    • 你是否愿意牺牲缓存将占用的空间
      -内存空间一般是比较宝贵的
    • 希望缓存的数据的使用频率与更新频率
      -如果更新频率非常低,而使用频率非常高,则是划算的
    • 你是否需要保证缓存数据的有效性
      -并不是所有数据都必须完全精准
      -例如:某些榜单
  • Mybatis的一级缓存基本上“不用管”

  • Mybatis的二级缓存基本上“不好用”

    • 同一个namespace下的所有查询的缓存配置均相同
    • 自动使用算法清理,无法精准控制
    • 每执行写操作后都会清理
    • 应用服务器的内存可能本来就已经很紧张,再牺牲一定空间用于缓存,可能影响应用服务器处理数据的效率
  • 由于Mybatis的二级缓存“不好用”,在实际幵发中,一般不使用Mybatis的二级缓存,而是使用专门的缓存技术,还可以使用专门的缓存服务器,甚至缓存服务器集群,以更全面、更细致的处理缓存

    • 例如:使用 Memcached、Redis等

数据库

数据库视图

视图的作用
  • 在绝大部分的关联查询中,都存在某些表中某些字段的数据是不需要被查询出来的,使用视图时,可以完全不关心这些的字段,甚至都不需要知道它们在真实的数据表中是否存在,所以可用于隐藏部分数据

    • 即使只查某1张表,也经常不需要查询全部的字段
    CREATE VIEW v_user_info AS select id, username, day_of_birth
    from user;
    
    select * from v_user_info;
    
  • 通过只查询部分字段,还可以使得MySQL中的某些用户被授权访问视图,却不授权访问原始数据表,使得这些用户无法访问某些数据,一定程度上提高了安全性

    • 在MySQL中(或其它数据库系统中),可以创建多个用户,并分配不同权限
  • 视图还可以跨表甚至跨域,可连接多张表甚至多台数据库

  • 在小型应用的开发中可能并没有以上这些需求

  • 由于视图并不是真实存在的数据表,物理上是不存在数据的,每次通过视图进行
    查询时,本质上还是查询了真实的数据表,所以,它并不能提高查询效率,同时,
    由于视图本身也不包含数据,所以,不可以对视图进行任何类型的写操作。

完整格式
CREATE
[OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPLABLE}]
[DEFINER = user]
[SQL SECURITY { DEFINER | INVOKER }]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]

总结
  1. 可以通过 CREATE VIEW view_name AS select_statement 格式的语句创建视图

  2. 可以将一些常用的、相对固定的复杂查询创建对应的视图,以减少反复编写长篇的、相同的SQL语句的开发量。

    • 合理的使用视图,可以提高安全性。
  3. 视图还可以跨表甚至跨域,可连接多张表甚至多台数据库。

    • 在小型应用的开发中可能并没有以上这些需求。
  4. 由于视图并不是真实存在的数据表,物理上是不存在数据的,每次通过视图进行查询时,本质上还是查询了真实的数据表,所以,它并不能提高查询效率,同时,由于视图本身也不包含数据,所以,不可以对视图进行任何类型的写操作。

数据库的索引

索引的概念

在关系数据库中,索引(Index)是一种单独的、物理的対数据库表中一列或多列的值进行排序的一种存储结构。

索引的作用
  • 明显提高查询数据的效率
    • 以3000万条数据的表为例,对“非索引”字段查询时,耗时可能超过5秒,对“索引”字段查询时,一般在毫秒级
      -不同硬件可能略有不同
创建索引
  • 创建和删除索引的基本语法示例:

    CREATE INDEX index_username ON user (username);
    
    DROP INDEX index_username ON user;
    

    index_username 自定义索引名称

    user 数据表名称

    username 需要创建索引的字段名称

    • 注意:创建索引是比较耗时的操作
      • 当前表中的数据越多,创建索引耗时越久
      • 创建索引是一次性操作
索引的原理
  • 当没有索引时,MySQL只能从头到尾的尝试匹配要查询的数据,如果数据表中有3000万条数据,则会把这3000万条数据全部查一遍,且由于数据并不是集中存放在磁盘的某一个位置,而是散列存储的,所以在查询过程中会进行频繁的IO操作,导致查询效率低下!

  • •当创建索引时,MySQL会将对应字段的数据全部排序,并在索引中记录下每条数据的位置信息,索引就相当于“书的目录”,当需要查询数据时,会先翻这个“目录”,从而找到数据所在的“页码”(数据在磁盘中的位置),然后精准定位到数据在磁盘中的位置,所以查询效率非常高!

  • 索引的本质是一种B+Tree结构(是一种树型结构)的数据,InnoDB存储引擎中页的大小为16KB,即使使用BIGINT类型的主键,也只占8字节,在B+Tree中使用的指针一般4~8字节,每个主键与指针形成B+Tree中的一个节点,则每个节点占用16字节,那么,每页至少可存储16KB/16B = 1000个节点,一个深度为3的B+Tree至少可以记录1000 x 1000 x 1000 = 1000 000 000个节点,并对应到实际的数据,即能够维护10亿条数据,所以,使用这种结构,只需要执行次10操作即可发现需要查询的数据,然后查询对应的数据即可。

  • 在MySQL中,PRIMARY KEY (主键索引)、UNIQUE (唯一索引)、INDEX (索引)、FULLTEXT (全文索引)均使用B+Tree结构。

注意事项
  • 基于索引的特性,在使用时必须注意:

    • 索引不会包含有NULL值的列
    • 数据量非常少的表没有必要创建索引,例如绝大部分字典表
    • 数据会经常变动的表不要创建索引,因为会频繁更新索引,导致写入效率低下
    • 查询时字段值需要运算会导致索引失效,例如where age + 1 > 18B寸age的索引将失效
    • 左侧模糊查询无法使用索引,因为索引是基于对数据进行排序得到的
  • 不同的企业对索引的使用可能有更高的要求,例如:

    • 如果varchar的长度超过一定范围,不允许使用索引
    • 类型为text的字段不允许使用索引
    • 其它要求
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值