Java企业级开发框架

  1. 文档设计说明

  1. 前置知识

  • JavaSE (Java编程基础、面向对象、核心API)

  • 数据库(MySQL,OpenGauss,Oracle,...)以及JDBC技术(访问数据库)

  • Git(分布式版本控制工具)&Maven技术应用(项目构建工具)

2.目标设计

  • 掌握企业级核心开发框架Spring Framework,Spring MVC,Spring Boot,MyBatis应用。

  • 掌握企业级项目开发中Restful架构风格的接口设计、Knife4j在线API接口文档、验证框架的应用。

  • 掌握企业级开发框架的核心设计原理(IOC,AOP,MVC)以及常用设计模式。

  • 掌握企业框架应用时核心Bug分析以及解决方案。(BUG集、FAQ)

设计特色及亮点

  • 课程偏重于企业开发中数据库应用及框架应用部分的最核心内容的讲解。

  • 颠覆传统SSM框架讲解方式,基于当前主流的SpringBoot脚手架及案例进行SSM知识分享。

  • 框架部分抽取的是通用能力,是方法论,是举一反三,可为同学们融汇贯通能力的成长进行输血。

  • 助力企业框架源码的分析能力,理解高手编程的思想以及框架中核心设计模式的应用。

如何学习和讲解一个技术?

3w1h方法论=What(是什么)+Where(用在哪里)+Why(为什么要用它)+How(如何应用

2.Spring生态初识

  1. Spring生态概述

Spring生态系统是一个强大的、广泛使用的Java框架集合,它旨在简化企业级应用的开发。Spring生态系统由许多项目组成,这些项目提供了构建现代Java应用所需的各种工具和功能。其官方网址为spring.io.

  1. Spring框架概述

如何理解Spring框架?(What+Where+Why+How)

Spring框架是一个资源整合框架(类似企业运营官),是整个Spring生态的基础,其核心是资源整合,管理对象,然后以一种更加科学的方式对外提供服务,例如提高对象的应用效率,降低系统开销,提高代码的可维护性等等。

  1. Spring Boot 概述

现在的软件生态应用已经形成一定的规模,整个软件架构体系都在升级变化,无论是业务和技术复杂度都越来越高,需要的依赖也越来越多,配置和部署也都越来越复杂。此时,企业就会更注重技术的开箱即用,更更注重轻量级的运维。由此Spring Boot诞生。

Spring Boot是一个全新的Java软件开发框架(很多人现在把它理解为一个脚手架),其设计目的是用来简化Spring项目的初始搭建以及开发过程,并为后面的Spring Cloud 微服务实践提供更加便利条件。该框架使用了特定的注解方式来进行配置,从而使开发人员不再需要大量的xml配置。不再需要大量的手动依赖管理。Spring Boot基于快速构建理念,通过约定大于配置,开箱即用的方式,希望能够在蓬勃发展的快速应用开发领域成为其领导者。

Spring Framework与Spring Boot是什么关系?Spring Framework是一个资源整合框架,Spring Boot是为了助力Spring实现快速资源整合(整合连接池、整合mybatis、整合spring mvc等),而推出的一个脚手架,实现了开箱即用的特性,同时可以更好助力Spring Cloud完成微服务架构生态的建设,之所以Spring Cloud能够快速发展,Spring Boot也起到了一个举足轻重的作用。

  1. Spring Boot核心特性

SpringBoot 框架诞生后,之所以能得到软件开发行业的高度认可,自然离不开它提供给我们的一些关键特性,例如:

  1. 起步依赖(Starter Dependency)-创建项目时,会默认添加基础依赖,简化我们自己查找依赖的过程。

  2. 自动配置(Auto Configuration)-创建项目时,springboot工程添加的默认依赖中提供了很多默认的配置,简化了我们对资源的配置过程。

  3. 嵌入式服务(Tomcat)-springboot工程支持内嵌的web服务,可以将tomcat或jetty这样的服务直接嵌套到web依赖中,简化部署过程。

  4. 健康检查(Actuator)-监控-springboot工程运行时,我们可以打开actuator特性,基于此特性监控spring中的bean,连接池,jvm内存等。

  1. Spring资源整合入门

  2. 准备工作

创建maven项目,项目名称自己定义,并修改其Maven的pom.xml文件内容如下,例如:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>tedu-spring</artifactId>
    <packaging>pom</packaging>
    <!--dependencyManagement这个元素做依赖版本的管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type> <!--执行import操作时,这里的type必须为pom-->
                <scope>import</scope> <!--导入三方依赖,主要是版本的定义-->
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <!---定义当前工程以及子工程所需要的依赖,重点是可以子工程直接使用-->
    <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion><!--这里排除的junit4这个版本的测试引擎-->
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--基于maven的编译插件对项目编译环境进行统一设置。-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  1. 业务描述

在项目中,创建类名为DefaultCache(此类放在SpringBoot工程启动类所在包或子包中)的类,然后将此类对象交给Spring创建并管理,最后通过测试API对类的实例进行测试分析。

  1. API设计分析

基于业务描述,进行API及关系设计,如图所示:

在这张图中描述了一个DefaultCache和DefaultCacheTests单元测试类,这两个类都交给了spring管理,并且DefaultCacheTests类依赖于DefaultCache类型,由spring框架基于@Autowired对属性描述,然后执行依赖注入(将DefaultCache类型对象,注入给DefaultCache类型)。

  1. Bean对象定义及获取

在Spring框架规范中,所有由spring管理的对象都称之为Bean对象,我们现在基于如上业务及API设计图,进行Bean类型代码的编写及测试,其过程如下:

第一步:定义SpringBoot工程启动类

package com.spring;
@SpringBootApplication
public class BootMainApplication{
     public static void main(String[]args){
         SpringApplication.run(BootMainApplication.class,args);
     }
}

第二:定义DefaultCache类,并交给spring管理。

package com.spring.cache;
import org.springframework.stereotype.Component;
/**
 * @Component 注解描述的类,表示此类交给Spring框架管理。
 */
@Component
public class DefaultCache {

}

第三步:定义DefaultCacheTests单元测试类

在测试目录下,创建DefaultCacheTests类型,代码如下:

package com.spring.cache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@SpringBootTest
public class DefaultCacheTests {
 /**
 * @Autowired 注解描述的属性由spring框架按照一定规则为其注入值。
 */ @Autowired
   private DefaultCache defaultCache;
   
    @Test
    void testDefaultCache(){
        System.out.println(defaultCache);
        //FAQ? defaultCache变量引用的对象是由谁创建的,存储 到了哪里?bean pool
    }
}

注意,测试类创建时,也要写在启动类所在包或子包中.

第三步:运行单元测试类进行应用分析

启动运行单元测试方法,检测其输出结果,基于结果分析:

1)Bean对象的构建(默认在Spring启动时负责创建并管理对象)。

2)Bean对象的获取(从Spring容器获取对象,在当前案例中,测试类需要,底层会执行依赖查找和注入)

说明:这个案例中描述了Spring IOC设计思想(控制反转-转移对象的控制权),项目中将对象交给Spring管理(我们饭来张口,衣来伸手,Spring框架实现对象控制),并由spring完成对象的依赖查找(DL)和依赖注入(DI)的过程(这个种设计最重要的是让初学者规避对象管理的风险,对象由专业人士进行控制)。

  1. @Import注解分析及应用

问题:

Spring Boot工程启动时,系统底层会从启动类所在包或子包中查找由Spring注解(例如@Component)描述的一些类,假如我们自己写的类没有创建在启动类所在包或子包,那Spring如何查找我们的类?

解决方案

借助@Import注解将某个类引入到启动类或者是启动类所在包或子包的配置上类,可以将这个类做为spring管理的一个对象,经常应用于第三方框架资源的导入(第三资源不是我们写的,可能不在我们启动类的包体系下)。

案例演示:

第一步:定义一个类(这个类没在启动类所在包或子包中),例如:

package com.example;
public class LogCache {
    public LogCache(){
        System.out.println("LogCache()");
    }
}

第二步:在项目启动类上添加@Import(LogCache.class)注解。

package com.spring;
@Import(LogCache.class)
@SpringBootApplication
public class BootMainApplication{
     public static void main(String[]args){
         SpringApplication.run(BootMainApplication.class,args);
     }
}

第三步:创建单元测试类,检测是否可以拿到LogCache对象。

package com.spring.cache;

import com.example.LogCache;
import com.example.SimpleCache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@SpringBootTest
public class LogCacheTests {
    @Autowired
    private LogCache logCache;

    @Test
    void testLogCache(){
        System.out.println(logCache);
    }
}

通过测试结果分析,LogCache对象是否交给了Spring管理.

  1. @Configuration&@Bean注解应用

问题描述

假如现在有一个类,但是这个类没在启动类所在包或子包中,假如我们不使用@Import注解,那如何将这个类的对象交给spring管理呢?

解决方案

可以在启动类所在包或子包中定义一个配置类(@Configuration注解描述配置类),然后在配置类中定义方法,在方法内部构建对象并返回,而且这个方法要使用@Bean注解描述。

案例演示

第一步:定义一个SimpleCache对象(不再启动类所在的包体系中),例如

package com.example;

import org.springframework.stereotype.Component;


public class SimpleCache {
    public SimpleCache(){
        System.out.println("SimpleCache()");
    }
}

第二步:定义个配置类(这个配置类要写在启动类所在包或子包中),例如:

package com.spring.config;

import com.example.SimpleCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * @Configuration 注解描述类,
 * 表示是spring中的一个配置类。
 */
@Configuration
public class CacheConfig {//注意这个类的位置,以及类上的注解
    /**
     * @Bean 注解描述的方法一般会写在@Configuration注解描述的类中,
     * 用于自己创建对象,并把对象交给spring管理,spring会默认为
     * 这个bean起个名字(默认是方法名),
     * @return
     */
     @Bean//没有为bean起名字,默认为方法名
    //@Bean("myCache")//这里是自定义bean的名字
    public SimpleCache simpleCache(){
         System.out.println("CacheConfig.simpleCache()");
         return new SimpleCache();
    }
}

第三步:编写单元测试类(测试类也要写到test目录中与启动类包体系相同的包中),检测是否可以拿到SimpleCache对象

package com.spring.cache;

import com.example.SimpleCache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SimpleCacheTests {
    @Autowired
    private SimpleCache simpleCache;

    @Test
    void testSimpleCache(){
        System.out.println(simpleCache);
    }
}

思考:@Configuration&@Component注解有什么不同?

第一:@Configuration是一个特殊的@Component注解。

第二:@Configuration注解一般用于描述配置类,@Component用于描述一般组件(例如一个Cache对象)。

第三:@Configuration注解描述的配置类中,假如使用@Bean注解描述了方法,无论此方法被调用多少次,这个方法只执行一次(前提是方法返回的对象是一个单例对象)。

第四:假如实用@Component注解描述配置类,@Bean注解描述的方法,每次调用方法,都会重新创建对象。

  1. SpringBoot中Bean对象特性分析

这里要强调一点,这个Bean对象是由Spring管理的,而不是由Spring Boot,SpringBoot只是提供基础环境的配置.

Bean对象设计

第一步:添加业务类ObjectPool(可以简单理解为一个对象池),代码如下:

package com.cy.pj.common.pool;
@Component
public class ObjectPool{//假设此对象为一个对象池
    public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
      Systemd.out.println("ObjectPool()")
    }
}

第二步:定义单元测试,代码如下:

package com.cy.pj.pool;
import com.cy.pj.common.pool.ObjectPool;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ObjectPoolTests {

    @Autowired
    private ObjectPool objectPool01;
    @Test
    void testObjectPool01(){
       System.out.println(objectPool01);//true
    }
}

Bean对象延迟加载

现在思考一个问题,对于ObjectPool这个类,假如项目启动以后,暂时不会用到这个池对象,是否有必要对其进行创建(默认是会创建的)?我们知道没必要,因为占用内存。那如何在启动时不创建此类对象呢?借助Spring框架提供的延迟加载特性进行实现。例如,我们可以在需要延迟加载的类上使用@Lazy注解进行描述,代码如下:

package com.spring.common.pool;
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
    public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
      Systemd.out.println("ObjectPool()")
    }
}

此时,我们再去运行运行启动类,检测ObjectPool对象是否创建了,假如没有创建,说明延迟加载生效了。此时,我们总结一下,什么对象适合使用延迟加载特性呢?大对象,稀少用(项目启动以后,暂时用不到)的对象。

注意:延迟加载并不是延迟对类进行加载,而是在启动时,暂时不创建类的实例。假如想看一下内存中的类是否被加载了,可以通过JVM参数进行检测,参数为-XX:+TraceClassLoading。

Bean对象作用域分析

在实际的项目中内存中的对象有一些可能要反复应用很多次,有一些可能用完以后再也不用了或者说应用次数很少了。对于经常要重复使用的对象我可考虑存储到池中(例如交给spring框架进行管理),应用次数很少的对象那就没必要放到池中了,用完以后让它自己销毁就可以了。在Spring项目工程中为了对这样的对象进行设计和管理,提供了作用域特性的支持,具体应用:

package com.spring.common.pool;

@Scope("singleton")
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
    public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
      Systemd.out.println("ObjectPool()")
    }
}

其中,在上面的代码中,我们使用了@Scope注解对类进行描述,用于指定类的实例作用域。不写@Scope默认就是单例(singleton)作用域,这个作用域会配合延迟加载(@Lazy)特性使用,表示此类的实例在需要时可以创建一份并且将其存储到spring的容器中(Bean池),需要的时候从池中取,以实现对象的可重用。假如一些对象应用次数非常少,可以考虑不放入池中,进而使用@Scope(“prototype”)作用域对类进行描述,让此类的对象何时需要何时创建,用完以后,当此对象不可达了,则可以直接被GC系统销毁。基于代码进行测试分析,例如:

package com.cy.pj.pool;
import com.cy.pj.common.pool.ObjectPool;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ObjectPoolTests {

    @Autowired
    private ObjectPool objectPool01;
    @Autowired
    private ObjectPool objectPool02;
    @Test
    void testObjectScope(){
       System.out.println(objectPool01==objectPool02);//true 表示单例作用域,false表示prototype作用域
    }
}

对象生命周期方法

程序中的每个对象都有生命周期,对象创建,初始化,应用,销毁的这个过程称之为对象的生命周期。在对象创建以后要初始化,应用完成以后要销毁时执行的一些方法,我们可以称之为生命周期方法。但不见得每个对象都会定义生命周期方法。在实际项目中往往一些池对象通常会定义这样的一些生命周期方法(例如连接池)。那这样的方法在spring工程中如何进行标识呢?通常要借助@PostConstruct和@PreDestroy注解对特定方法进行描述,例如:

package com.spring.common.pool;
@Scope("singleton")
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
    public ObjectPool(){
      Systemd.out.println("ObjectPool()")
    }
    @PostConstruct
    public void init(){//对象创建后可以通过此方法进行初始化
       System.out.println("init()");
    }
    @PreDestroy
    public void destory(){//对象销毁前前可以通过此方法进行资源释放
     System.out.println("destory()");
    }
}

其中:

1)@PostConstruct 注解描述的方法为生命周期初始化方法,在对象构建以后执行.

2)@PreDestroy 注解描述的方法为生命周期销毁方法,此方法所在的对象,假如存储到了spring容器,那这个对象在从spring容器移除之前会先执行这个生命周期销毁方法(prototype作用域对象不执行此方法).

为什么要将对象交给Spring管理呢?

Spring框架管理对象时,为对象提供一些更加科学的特性,例如延迟加载(Lazy),作用域(Scope),生命周期方法以及对象与对象直接的解耦,通过这些特性的植入,可以让我们更好的应用对象.

  1. SpringBoot 工程依赖注入分析

案例设计

为了更好理解sping框架的底层注入机制,现在进行案例API设计,理解API的依赖注入过程,如图所示:

在这个案例中单元测试类CacheTests中定义一个Cache接口类型的属性,然后由Spring框架完成对cache类型属性值的注入。

代码编写及测试分析

第一步:定义Cache接口,代码如下:

package com.spring.common.cache;
public interface Cache {
 
}

第二步:定义Cache接口实现类SoftCache,代码如下:

package com.spring.common.cache;
 
@Component
public class SoftCache implements Cache{

}

第三步:定义Cache接口实现类WeakCache,代码如下:

package com.spring.common.cache;
 
@Component
public class WeakCache implements Cache{

}

第四步:定义CacheTests单元测试类,代码如下:

package com.spring.common.cache;
import org.junit.jupiter.api.Test;

@SpringBootTest        
public class CacheTests {
        @Autowired
        @Qualifier("weakCache")
        private Cache cache;
        
        @Test
        public void testCache() {
                System.out.println(cache);
        }
}

如何@Autowired注解的?

其中,@Autowired由spring框架定义,用于描述类中属性或相关方法(例如构造方法)。Spring框架在项目运行时假如发现由他管理的Bean对象中有使用@Autowired注解描述的属性或方法,可以按照指定规则为属性赋值(DI)。其基本规则是:首先要检测容器中是否有与属性或方法参数类型相匹配的对象,假如有并且只有一个则直接注入。其次,假如检测到有多个,还会按照@Autowired描述的属性或方法参数名查找是否有名字匹配的对象,有则直接注入,没有则抛出异常。最后,假如我们有明确要求,必须要注入类型为指定类型,名字为指定名字的对象还可以使用@Qualifier注解对其属性或参数进行描述(此注解必须配合@Autowired注解使用)。

第五步:运行CacheTests检测输出结果,基于结果理解其注入规则。

总结(Summary)

本小节为springboot技术入门章节,主要讲述了SpringBoot工程下,spring中bean对象的编写,特性以及依赖注入的规则,希望通过这一小节的讲解,同学们能够理解我们为什么要将对象交给spring管理,spring管理对象有什么优势,我们在springboot工程中应该如何配置这些对象。

  1. 整合MyBatis初步分析

  1. 概述

Mybatis是一个优秀的持久层框架(官网mybatis.org/mybatis-3),底层基于JDBC实现与数据库的交互。并在JDBC操作的基础上做了封装和优化,它借助灵活的SQL定制,参数及结果集的映射方式,更好的适应了当前互联网技术的发展。Mybatis框架的简单应用架构,如图所示:

在当今的互联网应用中项目,mybatis框架通常会由spring框架进行资源整合,作为数据层技术实现数据交互操作。

  1. 数据初始化

准备工作

这里以一个简易博客(资讯平台)为例,进行数据的操作.

DROP DATABASE IF EXISTS `jyblog`;
CREATE DATABASE `jyblog` DEFAULT CHARACTER SET utf8mb4;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
use `jyblog`;

DROP TABLE IF EXISTS `tb_users`;
CREATE TABLE `tb_users`
(
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL COMMENT '用户名',
    `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
    `password` varchar(255) NOT NULL COMMENT '密码',
    `mobile` varchar(20) DEFAULT NULL COMMENT '电话号码',
    `status` tinyint(4) NOT NULL COMMENT '账号是否被锁住,0-》禁用,1-》启用',
    `created_time` datetime NOT NULL COMMENT '注册时间',
    `modified_time` datetime NOT NULL COMMENT '注册时间',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;


DROP TABLE IF EXISTS `tb_tags`;
create table `tb_tags`
(
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(50) default 'java',
    `remark` varchar(100) default '',
    `created_time` datetime NOT NULL COMMENT '注册时间',
    `modified_time` datetime NOT NULL COMMENT '注册时间',
    PRIMARY KEY (`id`) USING BTREE
);

DROP TABLE IF EXISTS `tb_articles`;
create table `tb_articles`
(
    id             bigint(20)     unsigned auto_increment    comment 'ID',
    title          varchar(50)    not null          comment '标题',
    type           char(1)        not null          comment '类型(1 原创 2 转载 3翻译)',
    content        varchar(500)   default null      comment '文章内容',
    status         char(1)        default '0'       comment '状态(1审核  2通过  3关闭)',
    user_id        bigint(20)     unsigned not null comment '用户id',
    created_time   datetime                         comment '创建时间',
    modified_time  datetime                         comment '更新时间',
    primary key (id)
) engine=innodb auto_increment=1 comment = '文章表';

DROP TABLE IF EXISTS `tb_article_tags`;
create table `tb_article_tags`
(
    id    bigint(20)     unsigned auto_increment    comment 'ID',
    article_id bigint(20)  unsigned comment '文章 ID',
    tag_id bigint(20)  unsigned comment '标签 ID',
    created_time   datetime          comment '创建时间',
    modified_time  datetime            comment '更新时间',
    primary  key (id)
)

  1. 初始化mybatis环境

创建springboot项目或在已有项目中执行如下操作:

第一步:添加MySQL数据库驱动依赖(mvnrepository.org)。

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

第二步:添加mybatis启动依赖。

首先打开mybatis官网中Spring框架部分(参考官网 mybatis.org/spring)

在SpringBoot项目中,添加mybatis对用版本依赖即可。例如:

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>

注意:在添加此依赖时,一定指定其版本(version),因为在springboot默认配置中为设置mybatis框架版本。

我们添加了mybatis依赖以后,spring框架启动时会对mybatis进行自动配置。例如SqlSessionFactory工厂对象的创建。

第三步:Mybatis简易配置实现。

假如需要对mybatis框架进行简易配置,可以打开application.properties文件,在此文件中进行基本配置(可选,暂时可以不配置),例如:

#设置连接数据库的url、username、password,这三部分不能省略
spring.datasource.url=jdbc:mysql://localhost:3306/jyblog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#设置池中最小连接数
spring.datasource.hikari.minimum-idle=5
#设置池中最大连接数
spring.datasource.hikari.maximum-pool-size=15
#设置事务自动提交
spring.datasource.hikari.auto-commit=true
#设置空间链接多长时间后被释放
spring.datasource.hikari.idle-timeout=30000
#设置连接超时时间(客户端一直请求不到连接时,在这个时间以后会抛出连接超时)
spring.datasource.hikari.connection-timeout=30000
#连接测试
spring.datasource.hikari.connection-test-query=SELECT 1

# mybatis执行sql时的超时限制
mybatis.configuration.default-statement-timeout=30
# 驼峰命名规则
mybatis.configuration.map-underscore-to-camel-case=true
# 映射文件的路径
mybatis.mapper-locations=classpath:/mapper/*.xml

  1. 创建启动类

假如是新的maven项目,要创建一个项目启动类,假如是在已有springboot项目进行实践,这个类就不需要创建了。

package com.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 构建SpringBoot启动类
 */
@SpringBootApplication
public class BootApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }
}
  1. 测试代码实现

在src/test/java目录中添加测试类,对mybatis框架整合进行基本测试,代码如下:

package com.spring.dao;
import java.sql.Connection;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MyBatisTests {
   /**
   这里的SqlSession为MyBatis与数据库进行通讯的一个会话对象
   */
   @Autowired
   private SqlSession sqlSession;
   
   @Test
   public void testGetConnection() {
     Connection conn=sqlSession.getConnection();
     System.out.println("connection="+conn);
   }
}

在SpringBoot脚手架工程中,Spring框架会基于MyBatis框架底层配置,创建SqlSessionFactory对象,然后再通过此工厂对象创建SqlSession,最后基于Spring框架为测试类注入SqlSession对象,接下来,我们可以通过SqlSession对象实现与数据库的会话了。

  1. 整合MyBatis完成用户数据操作

  1. 业务描述

基于SpringBoot脚手架工程对MyBatis框架的整合,实现对用户表数据的操作。

  1. 知识点设计

基于本业务实现MyBatis基本操作,掌握MyBatis中相关API(SqlSession,@Mapper,@Param)以及常用动态SQL(if,choose,where,…)的应用

  1. 用户表设计

用户表的设计如下(假如库中已经存在这个表了,不需要再创建了),例如:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `tb_users`
(
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL COMMENT '用户名',
    `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
    `password` varchar(255) NOT NULL COMMENT '密码',
    `mobile` varchar(20) DEFAULT NULL COMMENT '电话号码',
    `status` tinyint(4) NOT NULL COMMENT '账号是否被锁住,0-》禁用,1-》启用',
    `created_time` datetime NOT NULL COMMENT '注册时间',
    `modified_time` datetime NOT NULL COMMENT '注册时间',
     PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

  1. Pojo对象设计

这里的POJO对象就时一个普通的Java对象,这种对象内部会通过属性存储数据,通过set/get等方法修改或获取属性对应的数据,在pojo对象的类型设计中假如细分的话还可以包括一些VO对象(用于封装响应到客户端的数据)、PO对象(用于封装要写入到数据库表的数据,一般会与表中字段有一一对应关系),DTO对象(数据传输对象,用于封装从客户端传递到服务器端的数据)等,这里我们设计一个User对象,通过此对象封装用户相关信息,对象的具体内容如下:

package com.spring.pojo;

import java.io.Serializable;
import java.util.Date;

public class User{//UserDto,UserVo,UserPo
    private Integer id;
    private String username;
    private String nickname;
    private String password;
    private String mobile;
    private Integer status;
    private Date createdTime;
    private Date modifiedTime;

    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 getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Date getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }

    public Date getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", nickname='" + nickname + '\'' +
                ", password='" + password + '\'' +
                ", mobile='" + mobile + '\'' +
                ", status=" + status +
                ", createdTime=" + createdTime +
                ", modifiedTime=" + modifiedTime +
                '}';
    }
}

  1. Dao接口设计

基于MyBatis规范设计用户数据访问接口,例如:

package com.spring.dao;

import com.spring.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserDao {//UserMapper

    /**添加新的User信息*/
    int insert(User user);

    /**基于创建时间(注册时间)查询用户信息*/
    List<User> list(String createdTime);
    
    /**
     * 修改用户状态
     * @param ids 用户id
     * @param status 用户状态
     * @return
     */
    int validById(@Param("ids") Integer[] ids,
                  @Param("status") Integer status);

    /**更新用户信息*/
    int update(User user);

    /**基于用户名查询用户信息*/
    @Select(" select id,username,password,nickname,mobile,status " +
             "  from tb_users " +
             "  where username=#{username}")
    User selectByUsername(String username);

}

其中:

1)@Mapper注解由MyBatis框架提供,用于描述数据层接口,告诉系统底层为此接口创建其实现类,在实现类中定义数据访问逻辑,执行与数据库的会话(交互)。

2)@Param注解由MyBatis框架提供,通常用于描述接口方法参数,定义参数名,早期JDK版本,不能直接通过反射技术获取方法参数名。所谓为了实现向前兼容,方法中多个参数时,尽量实用@Param注解进行参数名定义。

  1. Dao接口映射文件

在项目的resources目录下创建mapper目录,并在目录下创建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">
<!--mybatis的映射文件,这个文件中要写sql映射-->
<mapper namespace="com.spring.dao.UserDao">

    <!--这里的每SQL元素都会映射为一个MappedStatement对象-->
    <select id="list"
            resultType="com.spring.pojo.User">
        select id,username,nikename,mobile,status,created_time,modified_time
        from tb_users
        <where>
              <if test="createdTime!=null and createdTime!=''">
                  created_time > #{createdTime}
              </if>
        </where>
    </select>

    <update id="validById">
        update tb_users
        set status=#{status}
        <where>
         <choose>
            <when test="ids!=null and ids.length>0">
                id in <!--(1,2,3,4,5)-->
                <foreach collection="ids" open="(" close=")" separator="," item="id">
                    #{id}
                </foreach>
            </when>
            <otherwise>
                1=2
            </otherwise>
          </choose>
        </where>
    </update>

    <update id="update" parameterType="com.spring.pojo.User">
        update  tb_users
        <set>
           <if test="username!=null and username!=''">username=#{username},</if>
           <if test="nickname!=null and nickname!=''">nickname=#{nickname},</if>
           <if test="mobile!=null and mobile!=''">mobile=#{mobile},</if>
           <if test="modifiedTime!=null">modified_time=#{modifiedTime}</if>
        </set>
        where id=#{id}
    </update>

    <insert id="insert" parameterType="com.spring.pojo.User">

        insert into tb_users
        (username,nickname,password,mobile,status,created_time,modified_time)
        values
        (#{username},#{nickname},#{password},#{mobile},#{status},#{createdTime},#{modifiedTime})

    </insert>

</mapper>

有时面试官可能会问,你觉得MyBatis最大的优势是什么?

  1. Dao单元测试实现

在test目录下,创建UserDao接口的单元测试类,代码如下:

package com.spring.dao;

import com.spring.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.List;

@SpringBootTest
public class UserDaoTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testInsert(){
        User user=new User();
        user.setUsername("Jack");
        user.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        user.setNickname("Jack");
        user.setMobile("1391112121");
        user.setStatus(1);
        user.setCreatedTime(new Date());
        user.setModifiedTime(new Date());
        userDao.insert(user);
    }

    @Test
    void testList(){
        List<User> list = userDao.list("2020/12/12");
        for(User user:list){
            System.out.println(user);
        }
    }
    @Test
    void testValidById(){
       int rows= userDao.validById(new Integer[]{}, 0);
        System.out.println(rows);
    }

    @Test
    void testUpdate(){
        User user=new User();
        user.setId(1);
        user.setNickname("Pony-001");
        user.setMobile("1234569098");
        user.setStatus(2);
        user.setModifiedTime(new Date());
        userDao.update(user);
    }

    @Test
    void testSelectUserByUsername(){
        User user = userDao.selectByUsername("Pony");
        System.out.println(user);
    }
}

  1. 整合MyBatis完成标签业务操作

  1. 业务描述

基于SpringBoot脚手架工程对MyBatis框架的整合,实现对文章标签进行操作。

  1. 知识点设计

本业务中重点讲解@Select,@Insert,@Update,@Delete注解应用。

  1. Tag表的设计

标签表设计如下(这个表已经存在则无虚创建

create table tb_sys_tags
(
    id bigint(20) unsigned auto_increment,
    name varchar(50) default 'java',
    remark varchar(100) default '',
    created_time datetime,
    modified_time datetime,
    primary key (id)
);
  1. Pojo对象设计

创建标签对象,用于存储标签信息,例如:

package com.spring.pojo;

import java.util.Date;

public class Tag {

    private Long id;
    private String name;
    private String remark;
    private Date createdTime;
    private Date modifiedTime;

    public Tag(){}
    public Tag(Long id,String name){
        this.id=id;
        this.name=name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }

    public Date getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    @Override
    public String toString() {
        return "Tag{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", remark='" + remark + '\'' +
                ", createdTime=" + createdTime +
                ", modifiedTime=" + modifiedTime +
                '}';
    }
}

  1. Dao对象设计

定义文章标签数据层接口对象及方法,例如:

package com.spring.dao;

import com.spring.pojo.Tag;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface TagDao {

    @Select("select * from tb_tags")
    List<Tag> list();

    @Insert("insert into tb_tags(name,remark,created_time,modified_time) " +
            " values (#{name},#{remark},#{createdTime},#{modifiedTime})")
    int insert(Tag tag);

    @Update("update tb_tags set name=#{name},remark=#{remark},modified_time=#{modifiedTime} " +
            " where id=#{id}")
    int update(Tag tag);

    @Delete("delete from tb_tags where id=#{id}")
    int deleteById(Long id);

}

  1. Dao单元测试实现

在src/java/test目录下定义测试类,对TagDao对象进行应用测试。

package com.spring;
import com.spring.dao.TagDao;
import com.spring.pojo.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.List;
import java.util.function.Consumer;

@SpringBootTest
public class TagDaoTests {
    @Autowired
    private TagDao tagDao;
    @Test
    void testList(){
        List<Tag> list = tagDao.list();
        for(Tag tag:list){
            System.out.println(tag);
        }
    }

    @Test
    void insert(){
        Tag tag=new Tag();
        tag.setName("mysql");
        tag.setRemark("mysql..");
        tag.setCreatedTime(new Date());
        tag.setModifiedTime(new Date());
        tagDao.insert(tag);
    }
    @Test
    void update(){
        Tag tag=new Tag();
        tag.setId(2L);
        tag.setName("TiDB");
        tag.setRemark("TiDB..");
        tag.setModifiedTime(new Date());
        tagDao.update(tag);
    }

    @Test
    void deleteById(){
        tagDao.deleteById(100L);
    }
}
  1. 底层SqlSession应用分析

MyBatis API 对象应用过程分析,如图所示:

图中,展示业务设计中API对象的一种调用关系。例如我们的数据访问对象调用MyBatis API,然后MyBatis API底层通过实用JDBC API(两大部分:java.sql.*,javax.sql.*)访问数据库。

  1. 底层SqlSessionTemplate应用分析

在SpringBoot脚手架工程中,系统底层整合MyBatis框架,为了保证其线程的并发安全,会创建SqlSessionTemplate对象,此对象是一个线程安全的SqlSession对象,我们可以基于此对象实现与数据库的会话。这个过程是线程安全的

  1. 常见BUG分析

  • NPE(空指针异常)。

  • NoSuchBeanDefinition异常(找不到对应的bean对象)。

  • SQL错误:BadSqlGrammarException。

  • Junit错误:ParameterResolutionException。

  • Junit错误:单元测试方法编写错误,检查访问修饰符。

  1. 整合MyBatis完成文章业务操作

  1. 业务描述

基于需求实现文章的发布和查询,要求在发布文章时,要给文章指定标签,还有在查询文章时,将文章所有的标签也要查询出来。通过这个案例掌握mybatis中的一些更加高级应用技巧。

  1. 知识点设计

本业务中重点讲解Insert元素的高级特性以及Select元素中基于ResultMap实现高级映射(例如一对多)。

  1. 文章表的设计

文章表的设计如下(假如表已经存在则无需创建)

create table `tb_articles`
(
    id             bigint(20)     unsigned auto_increment    comment 'ID',
    title          varchar(50)    not null          comment '标题',
    type           char(1)        not null          comment '类型(1 原创 2 转载 3翻译)',
    content        varchar(500)   default null      comment '文章内容',
    status         char(1)        default '0'       comment '状态(1审核  2通过  3关闭)',
    user_id        bigint(20)     unsigned not null comment '用户id',
    created_time   datetime                         comment '创建时间',
    modified_time  datetime                         comment '更新时间',
    primary key (id)
) engine=innodb auto_increment=1 comment = '文章表';

基于业务创建文章、标签关系表(文章和标签是一种多对多的关系,这种关系需要一张关系表。)

package com.spring.pojo;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class Article {
    private Long id;
    private String title;
    private String type;
    private String status;
    private String content;
    private Long userId;
    private Date createdTime;
    private Date modifiedTime;
    private List<Tag> tags;
    private Integer[] tagIds;
    private User author;

    public void setTagIds(Integer[] tagIds) {
        this.tagIds = tagIds;
    }

    public Integer[] getTagIds() {
        return tagIds;
    }

    public User getAuthor() {
        return author;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Date getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }

    public Date getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public void setTags(List<Tag> tags) {
        this.tags = tags;
    }

    @Override
    public String toString() {//alt+insert
        return "Article{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", type='" + type + '\'' +
                ", status='" + status + '\'' +
                ", content='" + content + '\'' +
                ", userId=" + userId +
                ", author=" + author +
                ", createdTime=" + createdTime +
                ", modifiedTime=" + modifiedTime +
                ", tags=" + tags +
                ", tagIds=" + Arrays.toString(tagIds) +
                '}';
    }
}

  1. Dao接口设计

定义文章表数据访问的接口对象,例如:

package com.spring.dao;

import com.spring.pojo.Article;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ArticleDao {
    int insert(Article article);
    /**
     * 基于文章id查询博客内容以及博客对应的标签信息
     * @param id
     * @return
     */
    Article selectById(Long id);
    List<Article> list();
}

定义文章标签关系表的数据访问对象,例如:

package com.spring.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface ArticleTagDao {
    /**
     * 将文章和标签关系数据写入数据库
     * @param articleId
     * @param tagIds
     * @return
     */
    int insert(@Param("articleId") Long articleId,
                         @Param("tagIds") Long[]tagIds);
}
  1. Dao接口映射文件设计

定义ArticleDao的映射文件,代码如下

<?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.spring.dao.ArticleDao">

    <!--添加文章:
        1)useGeneratedKeys="true"表示使用自增主键值。
        2)keyProperty="id" 这里表示将主键值赋值给参数中的id属性-->
    <insert id="insert"
            parameterType="com.spring.pojo.Article"
            useGeneratedKeys="true"
            keyProperty="id">
        insert into tb_articles
        (title,type,content,status,user_id,created_time,modified_time)
        values
        (#{title},#{type},#{content},#{status},#{userId},now(),now())
    </insert>
    <!--ResultMap是mybatis中用于实现高级映射的元素-->
    <resultMap id="articleTag" type="com.spring.pojo.Article">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
        <result property="type" column="type"></result>
        <result property="content" column="content"></result>
        <result property="status" column="status"></result>
        <result property="userId" column="user_id"></result>
        <result property="createdTime" column="created_time"></result>
        <result property="modifiedTime" column="modified_time"></result>
         <!--Collection对应one2one或者many2one关系映射-->
        <association property="author" javaType="com.spring.pojo.User">
            <id property="id" column="user_id"></id>
            <result property="username" column="username"></result>
            <result property="nickname" column="nickname"></result>
        </association>
        <!--Collection对应one2many关系映射-->
        <collection property="tags" ofType="com.spring.pojo.Tag">
             <id property="id" column="tagId"></id>
             <result property="name" column="tagName"></result>
        </collection>
    </resultMap>

    <!--基于sql元素可以对映射语句中的共性进行提取,其它地方需要的再使用include进行包含即可-->
    <sql id="selectArt">
        select ar.id,ar.title,ar.type,ar.content,ar.status,ar.user_id,ar.created_time,ar.modified_time,
               tag.id tagId,tag.name tagName,u.username,u.nickname
        from tb_articles  ar join tb_users u on ar.user_id=u.id
                             left join tb_article_tags art on ar.id=art.article_id
                             left join tb_tags tag on art.tag_id =tag.id
    </sql>
    
    <!--基于文章id查询文章信息以及文章对应的tag信息-->
    <select id="selectById" resultMap="articleTag">
       <include refid="selectArt"></include>
       where ar.id=#{id}
    </select>

    <!--基于文章id查询文章信息以及文章对应的tag信息-->
    <select id="list" resultMap="articleTag">
       <include refid="selectArt"></include>
    </select>


</mapper>

MyBatis是如何实现高级映射的?(借助ResultMap元素)

MyBatis中执行insert操作时,如何拿到表中自增主键值?(借助useGeneratedKeys和keyProperty这两个属性

定”ArticleTagDao映射文件,例如:

<?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">
<!--mybatis的映射文件,这个文件中要写sql映射-->
<mapper namespace="com.spring.dao.ArticleTagDao">

     <insert id="insert">
          insert into tb_article_tags
          (article_id,tag_id)
          values <!--(1,2),(1,3),(1,4)-->
          <foreach collection="tagIds" separator="," item="tagId">
               (#{articleId},#{tagId})
          </foreach>
     </insert>
     
</mapper>
  1. 单元测试的BUG及问题分析

定义单元测试类,对发布文章,查询文章进行单元测试,代码如下:

package com.spring.dao;

import com.spring.pojo.Article;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.List;

@SpringBootTest
public class ArticleDaoTests {

    @Autowired
    private ArticleDao articleDao;

    @Autowired
    private ArticleTagDao articleTagDao;

    @Test
    void insert(){
     Article article=new Article();
     article.setTitle("Spring Boot");
     article.setContent("Very Good");
     article.setType("1");
     article.setStatus("1");
     article.setUserId(1L);
     article.setCreatedTime(new Date());
     article.setModifiedTime(new Date());
     //将文章自身信息写入到数据库
     System.out.println("article.insert.before.id="+article.getId());
     articleDao.insert(article);
     System.out.println("article.insert.after.id="+article.getId());
     //将文章和标签关系数据写入到数据
     articleTagDao.insert(article.getId(),new Integer[]{1,3});
    }

    @Test
    void testSelectById(){
        Article article = articleDao.selectById(3L);
        System.out.println(article);
    }
    @Test
    void testList(){
        List<Article> list = articleDao.list();
        list.forEach(System.out::println);
    }
}

  1. Spring MVC分层架构设计分析

  1. 分层设计

在大型软件系统设计时,业务一般会相对复杂,假如所有业务实现的代码都纠缠在一起,会出现逻辑不清晰、可读性差,维护困难,改动一处就牵一发而动全身等问题。为了更好解决这个问题就有了我们现在常说的分层架构设计。

分层设计的本质其实就是将复杂问题简单化,首先基于单一职责原则(SRP-Single responsibility principle)让每个对象各司其职,各尽所能。然后再基于“高内聚,低耦合”的设计思想实现相关层对象之间的交互。这样可以更好提高程序的可维护性和可扩展性,例如生活中的楼宇设计,生日蛋糕设计,企业的组织架构设计等。在计算机通讯领域还有典型的OSI参考模型(Open System Interconnection Reference Model),TCP/IP参考模型等,如图所示:

其中,TCP/IP参考模型是OSI参考模型的一种网络简化通讯模型,这里不对每一层进行介绍,具体有关OSI和TCP/IP的知识请自行进行查阅。

基于Java的互联网架构,在进行分层设计时,首先要对整体的系统分层架构有一个基本认识,如图-35所示:

而整体应用分层架构中的应用软件层我们还会按照一定的规则继续分层,而在这一层最常用的规则或设计思想就MVC。

  1. MVC设计思想

MVC(Model–view–controller)是软件工程中的一种软件架构模式,基于此模式把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。目的是通过这样的设计使程序结构更加简洁、直观,降低问题的复杂度。其中MVC各个组成部分为:

  • 视图(View) - UI设计人员进行图形界面设计,负责实现与用户交互,例如html、css、js等都属于这一层的技术

  • 控制器(Controller)- 负责获取请求,处理请求,响应结果,在java中充当控制器对象的一般是servlet。

  • 模型(Model) - 实现业务逻辑,数据逻辑实现。(@Service-描述业务层对象,@Repository-描述数据层对象)

思考一下:海底捞中的MVC?(菜单-View,服务员-Controller,厨师-Model)

  1. JavaEE规范应用中的MVC设计(了解)

在JavaEE(Java的企业级开发平台)技术体系中,基于MVC设计思想,各个核心组件的应用关系如图-36所示:

在图-36中,Filter和Servlet都是JavaEE这个平台中的技术组件,Filter用于对请求进行预处理,可以理解为是JavaEE规范中的拦截器,Servlet充当MVC中的Controller(控制器)负责调用model处理业务,负责转发或重定向某个页面(View),在页面上呈现数据。

  1. Spring框架中Web模块的MVC设计

Spring MVC 是Spring 框架中基于MVC设计思想,实现的一个用于处理Web请求的框架。这个框架封装了对Servlet的技术的应用,简化了程序员对请求和响应过程中数据的处理,其简易架构分析如图所示:

图中,核心组件分析:

  • DispatcherServlet :前端控制器, 处理请求的入口。

  • HandlerMapping:映射器对象, 用于管理url与对应执行链(ExecutionChain)的映射关系。

  • HandlerInterceptor:拦截器,实现请求响应的共性处理(例如对controller方法的访问进行限制)。

  • HandlerAdapter:处理器适配器,用于实现不同Handler(Controller)类型之间的适配

  • Controller:后端控制器-handler, 负责处理请求的控制逻辑。

  • ModelAndView: 用于封装数据信息和页面信息的一个对象。

  • ViewResolver:视图解析器,解析对应的视图关系(前缀+viewname+后缀)。

  • ....

备注:假如希望了解Spring MVC的详细处理流程可以基于断点调试法进行跟踪。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值