Java:70-项目实战后续(课程管理模块)

项目实战后续(课程管理模块)

项目介绍:
前面我们完成了初步的项目,接下来实现更高等级的作用(基于框架的项目编写)
页面原型展示:

在这里插入图片描述

技术选型:
前端技术选型:

在这里插入图片描述

后端技术选型:

在这里插入图片描述

项目开发环境 :
开发工具:
后端:IDEA 2019
前端:VS code
数据库客户端工具: SQLYog
开发环境:
JDK 11
Maven 3.6.3
idea自带maven,但一般不使用,因为对应仓库默认放在c盘的,若你使用也没有问题
只是自己配置的有利于理解,且自己配置的基本可以设置仓库路径
idea自带的很难设置仓库路径,或者不可以设置,除非更改更加底层的配置(也可以说是文件),或者代码
MySQL 5.7
Maven进阶使用(Maven聚合工程) :
maven基础知识回顾 :
maven介绍
maven 是一个项目管理工具,主要作用是在项目开发阶段对Java项目进行依赖管理和项目构建
依赖管理:就是对jar包的管理,通过导入maven坐标,就相当于将仓库中的jar包导入了当前项目中
项目构建:通过maven的一个命令就可以完成项目从清理、编译、测试、报告、打包,部署整个过程

在这里插入图片描述

maven的仓库类型:
本地仓库
远程仓库
maven中央仓库(地址:http://repo2.maven.org/maven2/)
maven私服(公司局域网内的仓库,需要自己搭建)
其他公共远程仓库(例如apache提供的远程仓库,地址:http://repo.maven.apache.org/maven2/)
本地仓库—》maven私服—》maven中央仓库(若改变了则使用改变的远程仓库)
maven常用命令:
clean: 清理
compile:编译
test: 测试
package:打包
install: 安装,将项目打包的jar包放入仓库里面
maven坐标书写规范:

在这里插入图片描述

Maven的仓库搭建:
Maven软件的下载
使用 Maven 管理工具,我们首先要到官网去下载它的安装软件
http://maven.apache.org/download.cgi
版本自行选择
Maven 下载后,将 Maven 解压到一个没有中文没有空格的路径下,比如:H:\software\maven 下面
解压后目录结构如下:

在这里插入图片描述

bin:存放了 maven 的命令
boot:存放了一些 maven 本身的引导程序,如类加载器等
conf:存放了 maven 的一些配置文件,如 setting.xml 文件
lib:存放了 maven 本身运行所需的一些 jar 包
Maven环境变量配置:
配置 MAVEN_HOME ,变量值就是你的 maven 安装的路径(bin 目录之前一级目录)

在这里插入图片描述

将MAVEN_HOME 添加到Path系统变量:

在这里插入图片描述

Maven 软件版本测试 :
通过 mvn -v命令检查 maven 是否安装成功
看到 maven 的版本为 3.6.3 及 java 版本为 jdk-11 即为安装 成功
打开命令行,输入 mvn –v命令(配置了环境变量,那么可以任意位置了),如下图:

在这里插入图片描述

Maven 仓库:
Maven中的仓库是用来存放maven构建的项目和各种依赖的(Jar包)
本地仓库:位于自己计算机中的仓库, 用来存储从远程仓库或中央仓库下载的插件和 jar 包,
远程仓库:需要联网才可以使用的仓库,阿里提供了一个免费的maven 远程仓库。
中央仓库(默认的远程仓库,所以通常叫做中央仓库):在 maven 软件中内置一个远程仓库地址 http://repo1.maven.org/maven2
它是中央仓库,服务于整个互联网,它是由 Maven 团队自己维护,里面存储了非常全的 jar 包
它包含了世界上大部分流行的开源项目构件

在这里插入图片描述

Maven 本地仓库的配置 :
maven仓库默认是在 C盘 .m2 目录下,我们最好不要将仓库放在C盘(除非c盘足够大)
我们可以创建一个目录用来存放这些jar包,如H:\software\repository 目录
在maven安装目录中,进入 conf文件夹,可以看到一个 settings.xml 文件中,我们在这个文件中,进行本地仓库的配置

在这里插入图片描述

打开 settings.xml文件,进行如下配置如下:

在这里插入图片描述

指定的路径,就是我们创建的存放jar的目录文件
配置阿里云远程仓库:
Maven默认的远程仓库是在国外,所以下载jar包时速度会非常慢,这里推荐大家使用我大天朝的阿里云仓库
打开 settings.xml,找到 < mirrors> 标签 , 下面的内容复制到 < mirrors> 中 即可
<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>
        http://maven.aliyun.com/nexus/content/groups/public/
    </url>
    <mirrorOf>central</mirrorOf>        
</mirror>
以上就是配置仓库的步骤
maven的依赖传递 :
什么是依赖传递 :
在maven中,依赖是可以传递的,假设存在三个项目,分别是项目A,项目B以及项目C
假设C依赖B,B依赖A,那么我们可以根据maven项目依赖的特征不难推出项目C也依赖A,即依赖传递
即项目C既可以用到项目B中的资源,也可以用到项目A中的资源,xml读取时有先后顺序,后面会说明
注意:他们的传递等级划分是对应父子工程来的,真正的jar包没有等级划分,这句话在后面说明时,及其重要

在这里插入图片描述

注意:在maven中,使用< packaging >war</ packaging >注意指定自己下载的maven
idea默认的maven操作不了(除非设置一些编译,百度可以找到)
导入依赖:
 <!--spring mvc-->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
下面是pom.xml导入jar包时(spring-webmvc包),根据依赖传递,从而进行自动导入的其他依赖包
所以发现,除了spring-webmvc包,还有其他包
当然,在项目里面都是同一级,而maven里显示包依赖级别,但都是一种显示方式
项目显示(真正的jar包没有等级划分):

在这里插入图片描述

maven级别显示(等级划分,父子工程的划分):

在这里插入图片描述

由于我们导入依赖时,是指定项目的,即< artifactId>spring-webmvc</ artifactId>,artifactId是定义实际项目名称
那么我们前面说的,maven导入jar依赖包,实际上是通过maven来获得其项目的对应jar包
所以一般就称作导入依赖或者导入某某(项目)依赖(项目之间的依赖),依赖也可以叫做jar包或者获取jar包
也可以将项目名称叫做依赖
当我们导入依赖时,就可能会出现依赖传递
图解:

在这里插入图片描述

通过上面的图可以看到,我们的web项目直接依赖了spring-webmvc(没有标明spring-jcl,但也是依赖传递,即间接依赖)
而spring-webmvc依赖了sping-aop、spring-beans等
最终的结果就是在我们的web项目中间接依赖了spring-aop、spring-beans等
依赖冲突:
如下:
 <!--spring mvc-->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!--spring aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>

    </dependencies>
我们在spring-webmvc的基础上添加spring-aop
图解:

在这里插入图片描述

由于依赖传递现象的存在, spring-webmvc 依赖 spirng-beans-5.1.5,spring-aop 依赖 springbeans-5.1.6
但是发现spirng-beans-5.1.5 加入到了工程中
因为对应spring-webmvc在前面,按照先后顺序作为对应包,需要是同一等级的依赖,如他们都是对应第二等级的依赖
而我们希望 spring-beans-5.1.6 加入工程
这就造成了依赖冲突(一般不知道会造成冲突,所以也就是通常放在后面,使得对应包不起作用)
依赖冲突:一般的有些操作需要对应版本,否则可能会报错,假如某些操作不支持对应包的5.1.5版本,而支持5.1.6版本
而我们导入对应包的5.1.6版本时,放在后面,使得明明加入了对应版本,任然使得不支持了,即可能会报错
或者导入的对应包放前面覆盖了后面对应依赖jar包,而这个导入的我们不支持,即也可能会报错
这就是依赖冲突的出现方式(主要是前面一种,因为一般我们都只将导入的依赖jar包位置,放在后面,而不是前面)
注意:这里非常重要,他们的操作依赖的传递,而不是单独的放在pom.xml里面,所以是上面的作用
但是,若都是在pom.xml里面,则后面的覆盖前面的,而不是前面的覆盖后面的,这里要记住,正是因为在pom.xml只有该一点
所以这里说的,基本都是依赖的传递,即后面也是如此
如何解决依赖冲突:
在前面虽然说明真正的jar包没有等级划分,但是读取顺序由xml决定,之后才没有等级划分
使用maven提供的依赖调解原则(xml读取顺序,同样的后面就不读取了) :
第一声明者优先原则(优先原则,一般是相同的等级来使用这个原则)
路径近者优先原则(高等级优先,如上面的spring-aop是5.1.6版本,而不是5.1.5版本
排除依赖操作
锁定版本操作
这都是maven规定的原则,也就是说,在获得jar包后,先不导入
而是先根据这个原则进行操作,然后一次性全部导入
依赖调节原则——第一声明者优先原则:
在 pom 文件中定义依赖,以先声明的依赖为准
其实就是根据坐标导入的顺序来确定最终使用哪个传递过来的依赖

在这里插入图片描述

结论:通过上图可以看到,spring-aop和spring-webmvc都传递过来了spring-beans,但是因为spring-aop在前面
所以最终使用的spring-beans是由spring-aop传递过来的,而spring-webmvc传递过来的spring-beans则被忽略了(但还是被导入的,所以当我们不需要他占用资源时,一般操作排除)
依赖调节原则——路径近者优先原则:

在这里插入图片描述

总结:直接依赖大于依赖传递
排除依赖:
可以使用exclusions标签将传递过来的依赖排除出去

在这里插入图片描述

最后补充:一般直接依赖>父依赖>子依赖,在相同时,那么操作如上
所以如果父依赖有对应的信息,那么无论是多少层级,一般操作父依赖
版本锁定(一般使用这个方式来解决依赖冲突):
采用直接锁定版本的方法确定依赖jar包的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径
以锁定的版本为准添加到工程中,但不会锁定已经在maven存在的依赖,即一级直接依赖(写在pom.xml里的)
所以当锁定的依赖与一级直接依赖一样时,一级直接依赖为主(有版本的情况下)
即相当于版本锁定失效,但是若一级直接依赖没有指定版本
那么这个版本锁定就会起作用,即使用版本锁定的那个版本,此方法在企业开发中经常使用
dependencyManagement中定义的只是依赖的声明,并不实现引入,所以还是需要我们写上依赖,通常dependencyManagement操作版本,是在Spring Boot项目中操作依赖管理的主要角色
版本锁定的使用方式:
第一步:在dependencyManagement标签中锁定依赖的版本,且该标签只能存在一个,否则基本报错
第二步:在dependencies标签中声明需要导入的maven坐标
在dependencyManagement标签中锁定依赖的版本:

在这里插入图片描述

在dependencies标签中声明需要导入的maven坐标:

在这里插入图片描述

properties标签的使用:
<!--当需要修改某个框架的版本时,我们通常需要手动去修改
    但是一般对应的jar包有非常多个,那么我们就要修改非常多次,这是非常麻烦的
    于是就出现了以下操作
    -->
    <properties>
        <!--
        这个标签是自己声明的,也就是说,标签名是可以随便改变的(特殊符号不可,如/)
        其他的随便写的操作也基本上有些特殊符号不可操作,这是每个语言的隐藏含义,除了这些隐藏含义
        那么通常说随便写也是可以的
        -->
        <spring.version>5.1.4.RELEASE</spring.version>
        <!--基本所有的配置都可以操作,如dependencies里面的操作
即不止只有他dependencyManagement里面的操作-->
    </properties>
    <!--一般会默认先执行该标签,所以无论写在前面还是后面都可以,只要不在其他标签里面即可,位置不限制-->
    
<!--且可以与其他依赖的该标签合并
注意:一般只能是父工程和同样的dependencyManagement里面的合并,普通依赖可能不会合并
相当于其他依赖的标签内容在这里面,当前项目不写properties标签时
默认是空的properties标签,那么若其他依赖有,那么就会加上,否则就是空的properties标签-->

    <!--锁定jar包版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${spring.version}</version> <!--这是根据名称来操作-->
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

<!--这样就可以操作一个地方,从而其他地方(这里是版本,但通常用来操作版本)改变,而不用手动修改很多地方了

只是绑定,不是固定,所以可以被覆盖,一般只是操作依赖的而已,一般没有固定的,也不会被允许,具体可以百度
-->
maven聚合工程(分模块,主要作用是一个命令全部操作) :
概念:
在现实生活中,汽车厂家进行汽车生产时,由于整个生产过程非常复杂和繁琐,工作量非常大
所以厂家都会将整个汽车的部件分开生产,最终再将生产好的部件进行组装,形成一台完整的汽车

在这里插入图片描述

分模块构建maven工程分析 :
在企业项目开发中,由于项目规模大,业务复杂,参与的人员比较多
一般会通过合理的模块拆分将一个大型的项目拆分为N多个小模块,分别进行开发
而且拆分出的模块可以非常容易的被其他模块复用
常见的拆分方式有两种:
第一种:按照业务模块进行拆分,每个模块拆分成一个maven工程
例如将一个项目分为用户模块,订单模块,购物车模块等,每个模块对应就是一个maven工程
第二种:按照层进行拆分,例如持久层、业务层、表现层等,每个层对应就是一个maven工程
不管上面那种拆分方式,通常都会提供一个父工程,将一些公共的代码和配置提取到父工程中进行统一管理和配置

在这里插入图片描述

maven工程的继承:
首先我们也要注意,工程实际上也是文件夹,只是idea让他有对应作用而已
在Java语言中,类之间是可以继承的,通过继承,子类就可以引用父类中非private的属性和方法
同样,在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.lagou</groupId>
    <artifactId>maven_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>maven_children</module>  <!--代表里面的,所以../s这个s与这个同级别-->
        <module>../s</module>
        <!--对应工程不在父工程路径下-->
        <!--
		这个标签,在添加子工程时出现的,内容是对应的子工程名
			若有多个子工程,那么对应的<module>标签就多几个
注意是子工程,有些项目创建不会是子工程,如spring boot工程
需要自己动手修改或添加,因为他默认的是对应的Spring Boot父工程
一般需要他,否则对应的spring boot可能启动不了,所以对应的父工程也最好有对应的依赖或者自己有依赖,注意即可,注意即可
所以一般我们都会创建maven,而加上依赖完成操作spring boot,而不是直接的创建spring boot项目,让他自动的加依赖

	若创建后,删除这个,那么对应的子工程会变成一个文件夹,而不是一个项目
若没有指定路径的文件夹,那么就会报错
这是为了进行聚合打包,而进行这样操作的,所以可以不是对应子工程也是可以加上的,如
<module>../maven_advanced</module>,那么本项目进行打包时
这个maven_advanced(与本项目同级的,即有对应路径显示)也会进行打包
当然了,打包也会受到打包方式影响的,所以一般父工程是打包不了的
因为父工程一般是pom打包方式的,而子工程是可以设置jar或者war的
当然若操作其他的操作,如clean等等也会一起操作,即modules除了与parent进行连接外(groupId连接,一样的)
也会与他们对应的命令进行联系
就算不移除使得对应文件显示不同也是一样的,因为删除了就是删除了,只是不想让他的显示消失而已
		-->
    </modules>


    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <packaging>pom</packaging> <!--当创建子工程时,会默认添加上,也可以手动添加-->
     <!--且当你是父工程时,这个最好加上
	否则一般会有报错提示(实际上是因为modules标签的存在,使得出现错误提示,可能也没有)
更有可能使得打包报错,因为modules标签的存在
且是使用parent可以导入的原因之一,否则可能不会使用父工程的依赖,所以父工程,通常只是定义依赖,而不会操作打包
打包子项目时,通常会将父工程依赖给子项目(工程)
-->

     <!--统一的版本锁定及依赖管理-->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
    </dependencies>
</project>
子工程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">
    <parent> <!--有这个标签,那么父工程就是这个里面对应的项目名称-->
        <artifactId>maven_parent</artifactId>
        <groupId>com.lagou</groupId> <!--子工程继承这些信息,且子工程也必须设置了自己的名称来覆盖这个-->
        <!--否则这个pom.xml基本不起作用,虽然有红色报错,但刷新没问题-->
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>maven_children</artifactId> <!--多出来的这个,就是子工程名称-->
	<!--可以发现,我们导入依赖时,也是使用这个标签,就如前面所说,这个代表了实际项目名称-->
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>
我们创建子工程方法,一般如下:

在这里插入图片描述

发现,上面指定了对应父工程,这样创建后,就会到该父工程里(前提路径要正确),即

在这里插入图片描述

这样路径要在对应父目录下,否则就是这样的,看如下:
<?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">
    <parent>
        <artifactId>maven_parent</artifactId>
        <groupId>com.lagou</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../maven_parent/pom.xml</relativePath>
       <!--不在父工程下,一般就要指定使用对应pom.xml
否则一般我们是不需要指定的,因为在父工程下,可以使用父工程的pom.xml
且设置这个,他就是从指定路径获取对应依赖的包,注意是指定,即没有后续操作
等等对应包操作完毕,后面需要特殊指定的都是如此,如去父工程获取包
所以该操作使得一般会去父工程获取包,而其他依赖的一般再去仓库,这就是为什么子工程可以直接使用对应父工程的坐标了
因为会去自己父工程去找(当前项目,通常是idea或者maven的原因(一般也包括不是父子的),若在服务器或者操作打包命令,基本不会,而是先去仓库,从本地仓库开始,然后是远程仓库,而不会是当前工程),找到modules,然后根据指定项目名称,确定父子关系
然后获得对应依赖包,madules有对应项目名称
若删掉,则默认是../pom.xml,也就是父工程了(虽然可能也不是父工程,只是路径是父的)
若值是空的,那么默认去仓库里找
其实主要的还是命令的操作,他指定了命令的调用,所以当是空值时
这时虽然刷新不会报错,但执行安装命令时,那么就会出现找不到的错误(通过他来确定父子之间命令的联系)
-->
    
        <!--而子工程之间,基本是直接使用dependencies标签来操作,而不是使用parent
只有groupId时,才能导入

所以注意:必须要有联系,否则对应的dependencies是不会起作用的(也就是没有groupId)
若你加上groupId,那么也是会有作用的

最后:无论dependencies里面的是否存在,只要起作用,那么对应的包就会出现,只是不存在的会报错
且他实际上是先去看看上下文(maven工程)是否有对应的项目,有就导入对应类(不是jar包),否则去仓库导入
而打包基本必须有对应项目的jar包,也就是说,对应项目需要打包,这是后面jar包使用的主要原因

动态分析:com/aa/bb,这时一个项目的路径,当有jar包时,里面是com/cc/dd
那么合并后就是com/aa cc/bb dd,那么上下文得到的就是这些类,当打包时,这些类被其他人导入合并,也是如此


所以由上面所述:我们一般通过groupId来确定包的添加位置,但是都是从仓库里找,且都放在一起(不好维护)
而使用父子工程,我们可以定义一个总体包,其余只要是子工程,那么都可以导入了
那么我们在添加时,虽然工程总体包多了(多个工程出现一样的包)
对于子工程来说,代码少了(基本不用再次导入父工程里的依赖了)
因为一般包里面包含自己写的其他包,且是分开的,容易维护
而子工程之间,也可以进行依赖操作,使用dependencies来操作(这是肯定可以的)

最主要的就是好维护和对应子工程代码的减少,缺点是总体包变多
而模块的分离,一般都需要父子工程的这样的操作,否则我们就需要手动添加很多包
所以使用父子工程分开,那么包的添加基本全部存放在一个模块里了(父工程),就不用全部模块一起维护了
而模块分离,使得每人负责一个地方(编译也就一个模块编译,代码少编译快)
最后打包成jar包时,那个jar包,就存放了这些类的信息,使得可以使用,所有我们导入的jar包,实际上就是对应的类操作
使得分离后,也可以互相访问,通过pom.xml来访问
这就是图片中的直接依赖方式(我们进行命令mvn instal就有对应jar包了)
所以其他的依赖导入后的jar包中的类,我们也是可以手写的,只是非常麻烦而已,所以我们基本都是导入jar包来使用
那么这样就分工明确,若不进行模块的分离,代码就全部放在一起了,不好维护,且编译时需要全部编译(编译慢)


可以按照步骤来理解:首先传统的项目,是全部代码放在一起,所有依赖放在一个pom.xml
这时我们有很多的人,那么他们都操作一个项目,这时非常麻烦的,于是就出现了分离模块
分离后,进行分工操作,其他模块要使用,可以导入对应的jar包,这时就出现一个问题,每个模块都有pom.xml
那么不可能所有的pom.xml都编写所有的依赖,那么我们可以将所有依赖放入父工程里,其他pom.xml继承即可
这样就不用每个pom.xml都编写所有依赖了,你可能会想问,为什么不是每个模块用对应的jar包呢
实际上父工程依赖是每个子工程都需要的依赖
也就是说将同样的依赖就放入父工程,而不用每个子工程都写一遍
而子工程对应模块自己的依赖就一般放在自己的pom.xml中了
-->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>s</artifactId> <!--这里创建s名称的工程来实验-->

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>
当然,父子工程中,相同的包有多个的,也就是说,从父得到的相当于复制一份,当然,内容也是(其实内部是模块的知识,说成是idea操作的也行(包括依赖))
最后打包时,才会统一
我们在父工程里添加如下配置:
 <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
    </dependencies>
配置刷新后,看如图

在这里插入图片描述

发现子工程也有对应依赖了,因为也是使用着父工程的pom.xml的,所以父工程pom.xml刷新使用后,也会对子工程进行操作
依次类推,子工程也可以当父工程,使得他的pom.xml导入的包,可以被他的子工程获得,但是不会让父工程获得
也就是说,父工程先进行导入jar包,然后子工程导入父工程的jar包,当然了子工程之间的jar包的传递
一般需要对应的子工程的打包,使得可以去仓库里获得
而这样的,实际上我们操作了很多次,比如上面的导入代码,先看图,再看代码

在这里插入图片描述

可以发现指定了对应Maven组织名称,通常是公司名(com.lagou)
指定了项目名称,这里是untitled
指定了对应版本,这里是1.0-SNAPSHOT
那么接下来看代码:
 <!--我们进行解析-->
<!--父类对应导入代码-->
<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId><!--对应组织名称-->
            <artifactId>mybatis</artifactId> <!--对应项目名-->
            <version>3.5.5</version> <!--对应版本-->
        </dependency>
    <!--发现实际上也是导入一个项目,即工程,只不过是经过仓库的导入,这是maven的工作-->
    </dependencies>
<!----------------------分割线---------------------->
<!--子类对应代码-->
 <parent> 
        <artifactId>maven_parent</artifactId> <!--对应项目名-->
        <groupId>com.lagou</groupId> <!--对应组织名称-->
        <version>1.0-SNAPSHOT</version> <!--对应项目名-->
    </parent>
<!--也可以发现,也是导入对应项目,即工程,只是由于parent标签,那么使用的就是父工程的依赖包导入-->


注意:maven的刷新,是所有工程的pom.xml刷新
maven工程的聚合:
在maven工程的pom.xml文件中可以使用标签将其他maven工程聚合到一起,聚合的目的是为了进行统一操作
例如拆分后的maven工程有多个,如果要进行打包,就需要针对每个工程分别执行打包命令,操作起来非常繁琐
这时就可以使用标签将这些工程统一聚合到maven父工程中,需要打包的时候
只需要在此工程中执行一次打包命令,其下被聚合的工程就都会被打包了(会根据对应的项目依赖来进行打包顺序)
如进行聚合操作时,dependencies标签里的对应项目没有他的jar包,那么就会等待,直到出现才进行安装打包
若最后不存在,则报错
而一些打包命令,如mvn install命令,必须是有对应包存在才可进行操作,否则报错,所有命令和dependencies是有区别的
命令只操作项目打包的jar包,而dependencies操作仓库以及本maven工程的jar包
上面的解释:

在这里插入图片描述

这里还需要注意一点:导入的依赖或者说包,若该包中的类与你自己的类名冲突(包括包路径的),编译器会使用当前作用域中定义的类,而不是导入的类,这可能导致导入的类被隐藏,不会直接导致冲突报错(这个隐藏是特殊的,是idea处理时的隐藏,或者编译器自身的其他作用导致,了解即可)
maven聚合工程:
工程整体结构如下:
lagou_edu_home_parent为父工程,其余工程为子工程,都继承父工程lagou_edu_home_parent
lagou_edu_home_parent工程将其子工程都进行了聚合
子工程之间存在依赖关系:
ssm_domain依赖ssm_utils
ssm_dao依赖ssm_domain
ssm_service依赖ssm_dao
ssm_web依赖ssm_service
图解:
直接依赖就是对应模块的使用mvn install命令后,出现的jar包,即其他模块导入时,就可以使用这些类了
注意:jar包一般存放的是class文件,以及一些资源
查看方式:将jar后缀名改成zip,然后解压,那么就可以看到对应的内容了

在这里插入图片描述

注意:创建时需要指定名称,也就是Location这个地方
否则直接的存在的名称和名称/会有别名,当然指定路径有不存在的,那么就会创建出来
最后:我们可以发现pom.xml实际上也是被读取而设置的配置文件(由maven读取),使得通过内容进行依赖的操作
实际上idea这个软件的设置是一般是操作我们目录的.idea的配置文件的,因为idea软件也只是给我们进行方便操作罢了
根据图片进行父子工程模块的创建以及对应依赖编写:
父工程lagou_edu_home_parent构建:
修改pom.xml,添加依赖
<properties>
        <spring.version>5.1.5.RELEASE</spring.version>
        <springmvc.version>5.1.5.RELEASE</springmvc.version>
        <mybatis.version>3.5.1</mybatis.version>
    </properties>
    <!--锁定jar版本-->
    <dependencyManagement>
        <dependencies>
            <!-- Mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <!-- springMVC -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>${springmvc.version}</version>
            </dependency>
            <!-- spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-expression</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId> 
                <artifactId>spring-beans</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>${spring.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--mybatis坐标-->
        <dependency>
            <!--mysql驱动-->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <!--连接池-->
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.15</version>
        </dependency>
        <dependency>
            <!--引入mybatis依赖,如工厂的一些类-->
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        <dependency>
            <!--@Test测试-->
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--spring坐标-->
        <dependency>
             <!--Spring容器,即IOC容器的使用需要这个,比如ApplicationContext-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <!--切点表达式的识别-->
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <!--spring使用连接池的,包括一些下面的tx操作
当需要下面tx的一些操作时(如事务传播),那么可以导入tx-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
        <dependency>
            <!--tx的相关事务操作-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <dependency>
            <!--Spring整合@Test,使得可以使用注解指定配置类或者配置文件-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <!--mybatis整合spring坐标-->
        <dependency>
            <!--可以使用注解,配置mybatis-->
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--springMVC坐标-->
        <dependency>
            <!--有对应的类操作页面,如前端控制器-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
             <!--servlet坐标,有对应类,如HttpServletRequest-->
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <!--jsp的操作,实际上只是用来提示的,也就是说,没有这个,那么使用
${pageContext.request.contextPath}时,打出pageContext后面的.没有提示,但运行时,是使用服务器的操作的
即删除他运行也是没有问题,只是我们操作时会没有提示
-->
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <!--jstl表达式,没有他,那么如
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
这个就会报红,即到这个页面时,基本就会报错
-->
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        	<!--对应操作json需要的包,基本操作的是springmvc的注解-->
        <dependency>
              <!--这个必须要,后面两个的可以不写,但最好写上,防止其他情况-->
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.8</version></dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <!-- 分页助手 mybatis的分页操作,即对应类方法调用-->
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.6</version>
        </dependency>
        <!--   Beanutils 给出了集合数据封装到类的操作等等-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.3</version>
        </dependency>
        <dependency>
            <!--  文件上传 需要的类,有个commons-io的没有加,那么就说明现在还不需要他的操作-->
             <!--初始化操作-->
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- js解决跨域问题所需依赖 使得服务器操作这些类后,可以跨域-->
        <!--比如用ajax向一个不同的域请求数据时
		只要协议、域名、端口有任何一个不同,都被当作是不同的域-->
        <dependency>
            <groupId>com.thetransactioncompany</groupId>
            <artifactId>cors-filter</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <!--maven的插件依赖,这里是对应编译插件,设置参数-->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <!--编译级别-->
                  <source>1.8</source><!--开发中的jdk版本-->
                <target>1.8</target><!--开发后的,即class文件的jdk版本-->
                <encoding>UTF-8</encoding><!--当前操作的整个代码文件使用UTF-8编码-->

      <!--
具体说明可以到这里去看:
https://blog.csdn.net/gelingxian/article/details/124694741
-->

                </configuration>
            </plugin>
        </plugins>
    </build>
<!--还有一些在xml里使用了命名空间操作和对应约束路径,如aop-->
继承的jar包是第一点五等级级别,低于第一级别,高于第二级别
子工程ssm_utils构建 :
用来存放工具类的代码
子工程ssm_utils依赖基本不用操作编写,因为基本用来被其他工程导入
子工程ssm_domain构建:
用来存放实体类的代码
配置ssm_domain工程的pom.xml文件:
<!-- 依赖ssm_utils -->
<dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>ssm_utils</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
实体类:
package com.lagou.domain;

/**
 *
 */
public class Test {
    private Integer id;
    private String name;

    @Override
    public String toString() {
        return "Text{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

子工程ssm_dao构建:
用来存放Dao层的代码,也就是数据库相关代码
配置ssm_dao工程的pom.xml文件:
 <!--依赖ssm_domain-->
    <dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>ssm_domain</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
dao接口:
package com.lagou.dao;




import com.lagou.domain.Test;

import java.util.List;

/**
 *
 */
public interface TestMapper {
    /*
    对test表进行查询所有
    可以发现,在我们进行对ssm-domain进行install命令使得变成jar包后,导入了进来
    使得可以使用对应的Text了,jar包里面存放了对应的class文件,导入后maven根据一系列操作
    使得可以被提示,以及导入操作,而我们复制过来的class,一般很难在编译的时候,放入对应类里面
    所有基本操作不了(没有导入对应包)
    当自己写的类于jar包的类同名时,自己的类会先加载,根据class加载顺序
    当自己写的类先加载时,那么后面就不会加载同名类了
     */

  public List<Test> findAllTest();




}

创建DAO接口和Mapper映射文件:
<?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.lagou.dao.TestMapper">
    <select id="findAllTest" resultType="com.lagou.domain.Test">
        select *
        from test;
    </select>
</mapper>

在resources目录下创建spring配置文件applicationContext-dao.xml,后面加上了-dao
这样做,是为了防止jar包同名,使得不起作用,因为优先加载当前的配置,所以加载后,对应jar包同名的就不加载了
我们可以理解为,导入jar包,就是将对应的包文件放入我们的对应位置,同样的包名称就重合
所以也就相当于我们写了一遍对应代码,即我们导入后,就可以使用对应信息的,只是不会显示对应类给我们看而已
这时java读取jar包的方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--导入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
    <!--spring整合mybatis-->
<!--数据连接池信息,简称数据源-->

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

    <!--sqlSession的创建-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/><!--连接池的读取,可以操作对应连接了-->
        <property name="typeAliasesPackage" value="com.lagou.domain"></property>
        <!--设置别名,即这个包里的类,可以不用全路径了,而可以直接用类来表示-->
        <property name="configLocation" value="classpath:sqlMapConfig.xml"></property>
        <!--引入加载mybatis核心配置文件-->
        <!--虽然是class文件,但还是有提示的,因为我们最终结果就是操作class,而不是java-->
    </bean>

    <!--映射的读取-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.lagou.dao"/>
</bean>
    <!--从上面可以发现,对应的class实际上是加载的jar包里的对应class类-->

</beans>
对应的jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=123456
sqlMapConfig.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>
    <settings>
       <!-- 是否开启自动驼峰命名规则(camel case)映射
一般查询用到,因为查询需要映射到类,而其他的增删改基本只需要对应数据就可以了
       即从数据库列名 a_column 到属性名 aColumn 的类似映射 如a_name到aName,下划线被省略了,并操作了对应的(下划线后面)大写,但基本只是操作自动的映射,即resultType,则有作用
因为resultMap是直接的指定,所以并没有关系,而又是因为resultType使得set后面大小写忽略
所以说是否大写并无关系,还是可以操作
本来由原来的自动封装,变成了自动去下划线封装,实际上并不是真正的驼峰,只是去除了数据库字段下划线来进行映射
而是根据变量和对应set方法,忽略大小写,进行赋值
有变量无set,创建set执行,有set无变量,调用set执行,都没有则不赋值(mybatis中61博客的补充介绍)

注意:使用spring和mybatis整合时,对应set会调用两次,也就是赋值两次,由spring多调用一次(检查一遍)
-->
       <setting name="mapUnderscoreToCamelCase" value="true"/>
     
     </settings>


</configuration>
子工程ssm_service构建:
用来存放Service层的代码,也就是使用数据库代码的业务代码
配置ssm_service工程的pom.xml文件:
<dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>ssm_dao</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>
创建TestService接口和实现类:
package com.lagou.service;



import com.lagou.domain.Test;


import java.util.List;

/**
 *
 */
public interface TestService {

    /*
    对test表进行查询所有
     */
    //可以发现也可以使用Test类的,这是因为依赖是有传递的
    //当dao得到domain的jar包时,那么service得到dao就也会得到这个jar包
    //但需要dao先得到,然后service才可得到,所以说,最底层的一定是jar包的存放处
    //其实可以将导入理解成一个文件,文件里有文件
    //将其所有jar包放在这个工程里面(当然只会显示文件,不会显示对应jar包)
    //那为什么不显示我们导入的包呢,那是因为这是当前maven创建的jar包,当前maven是知道的,所以就不需要显示
    //实际上模块是在同一文件夹,可以直接导入
    //当去其他maven导入时,就会出现,不在同一文件夹了
    
    //我们也经常看到项目里有.idea的文件夹(相对路径),那么就说明我们是从这里开始进行编译java的(编译操作)
    //开发工具一般会帮我们指定对应路径去编译(main方法所在类),main方法编译,基本会带动其他类编译
    //而一般的我们用记事本只能在同一个目录下(没有设置),也可以指定路径,若不知道的话,百度里有答案
    //开发工具基本帮我们操作了这些(实现多个路径的编译,可能百度里也没有,只给出一个路径的编译)
    //然后将编译的放入内存,这时对应的类一般会声明包,使得可以被导入
    //而不在这里的,那么需要指定对应路径(开发工具帮我们进行了,编译后路径也是一样的,这是肯定的)
    //最后都放入内存中(包来决定位置),要导入类需要指定包(特别是编写的类,若只操作一个类,那么包名可以不写)
    //即需要其他类,就需要导入类,而这个类就要声明包,否则导入不了(即这个包是基本路径,或者标识)
    
    //当然,在没有jar包时,也说过,对应导入会从当前maven上下文里进行导入对应类
    //但需要导入当前maven上下文里进行导入对应类的坐标时
    //才可操作,实际上这是将类操作,相当于jar没有被打包的直接合并
    //使得需要对应路径,那么导入坐标就是一个指定路径的一个形式
    
    //一般没打包时编译主要是当前的父工程.idea造成的,打包后,则由打包中的某个文件进行操作编译
    
    //实际上无论什么运行,都会有对应的class出现,也就是target文件,所以实际上也就是class的操作
    //所以部署时,也需要对应class操作
    //而由于maven可以通过坐标来执行对应获得对应class,部署时就是单纯的class(没有对应的.idea)
    //所以一般需要jar包,打包安装好的(也就是war包,里面有对应jar包,使得可以使用对应class)
    //java会将jar包解压后的目录进行合并(maven直接得到,也就不需要jar包了)
    //所以说jar包是存放class,可以进行项目跳转的包
    //然后java执行对应class放入内存(默认项目的classes路径下开始编译)
    //使得操作整个项目,这是部署时的操作
    
    //所以一般都需要打包安装后进行war包的部署(maven有对应导入,可以不进行打包安装)
    
    //从上所述:jar包就可以说成class类,即必须要有对应类才可以操作,而导入就是为了对应java操作
    //所以在maven里面直接使用,和部署在服务器里实际上都是操作class类,区别就是java对应的指定路径方式
    
    //当然pom.xml虽然可以帮你导入到项目里,但删除对应配置刷新时,也是会帮你移除的
    public List<Test> findAllTest();
}

package com.lagou.service.impl;

import com.lagou.dao.TestMapper;
import com.lagou.domain.Test;
import com.lagou.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;
  /*
    对test表进行查询所有
     */
    @Override
    public List<Test> findAllTest() {
        List<Test> allTest = testMapper.findAllTest();
        return allTest;
    }
}

创建spring配置文件applicationContext-service.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--进行service的相关配置-->
    <!--配置IOC相关操作:开启注解扫描,-->
    <context:component-scan base-package="com.lagou.service">
    </context:component-scan>
    
    <!--引入applicationContext-dao.xml,也就相当于他的配置我们也操作了-->
    <import resource="classpath:applicationContext-dao.xml"/>
    <!--这个也是可以进行传递加载的-->

</beans>
<!--
那么在这里说明一下,加载实际上就是给出一个新的地址,然后去加载(同一个程序地方进行)
所以pom.xml也是差不多的,所以也就有了等级划分,即依赖传递(对应maven来说的分等级)
实际上是检测到对应的父子工程而出现的,而真正的对于包来说是没有等级的
-->
子工程ssm_web构建:
用来存放Web层的代码,也就是操作调用业务层代码与前端交互的
配置ssm_web工程的pom.xml文件:
<packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>ssm_service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>
创建Controller:
package com.lagou.controller;

import com.lagou.domain.Test;
import com.lagou.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController //相当与在类上加上@Controller,在所有方法上加上@ResponseBody
//现在补充一点,前面说过@ResponseBody操作集合变json
//实际上是操作所有对象,包括集合,都变成json
//而字符串就是字符串,也包括字符串数组,不会变成json
//这是他的底层作用,所以也可以说,@ResponseBody就是操作对象,变成json
//实际上变成json可以看成将有键值对的进行变化
//也就是说,对象一般都会被解析,或者键值对(map),而集合一般会加上[],字符串不会变化
//且是依次解析过去的,所以list集合中,若有map集合,普通集合,类,字符串,那么就会出现多种不同的场景
//实际上@RestController就相当于在类上面加上@Controller和@ResponseBody
//而@ResponseBody在类上面就相当于给该类的所有方法加上@ResponseBody
//之所以这样可以点进去看看他的结构(ctrl+鼠标左键),可以发现有上面两个注解修饰
//所有在扫描到这个注解时,获取内容时,也会操作他们两个注解的操作
//所以说,操作注解时,未必只操作他的内容,也会操作他里面注解
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestService testService;
  /*
    对test表进行查询所有
     */
    @RequestMapping("/findAllTest")
    public List<Test> findAllTest(){

        List<Test> allTest = testService.findAllTest();
        return allTest;
    }
}


创建springmvc配置文件springmvc.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--1.组件扫描:只扫描controller-->
<context:component-scan base-package="com.lagou.controller"></context:component-scan>
<!--
在这里说明一下,前面说过,前端控制器,只操作自身的容器
那么实际上spring和springmvc的扫描是可以重复的,而不会出现注入冲突
但是也为了空间,所以一般最好扫描到合适的位置,而不扫描不需要的实例
-->

    <!--注解增强:使得@ResponseBody可以被json操作,即使用这个操作-->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--视图解析器,现在不进行配置-->

    <!--静态资源放行-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
</beans>
编写applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
      http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
          http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
         http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--引入:applicationContext-service.xml-->
    <import resource="classpath:applicationContext-service.xml"/>

</beans>
配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--中文乱码过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--前端控制器-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>  <!--名称不能变-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--配置spring监听器,将IOC容器放入ServletContext域中-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name> <!--名称不能变-->
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!--跨域过滤器,使得js可以跨域-->
    <filter>
        <filter-name>corsFilter</filter-name>
        <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>corsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
上面都初步完成的基础架构创建,接下来开始写真正的代码了
课程管理模块功能分析:
在本次的项目中,首先完成课程管理模块
课程管理模块实现以下功能:
课程需要的操作:
多条件查询
图片上传
新建课程信息
回显课程信息,进行修改
修改课程信息
课程状态管理
课程内容展示,展示章节信息和课时信息,可以通过这些信息实现前端操作回显
回显章节对应的课程信息
章节需要的操作:
新建章节信息
回显章节信息,进行修改,这里前端可以操作
修改章节信息
修改章节状态
课时需要的操作:
新建课时信息
回显课时信息,进行修改,这里前端可以操作
修改课时信息
修改课时状态

在这里插入图片描述

课程管理模块表设计:
创建数据库及表:
sql语句地址:
链接:https://pan.baidu.com/s/1nBzKDzF5JJAfBGDaEi2zxg
提取码:alsk
注意:排序的操作,可以通过表来操作
表关系介绍:
ER图:

在这里插入图片描述

虽然他们都有对应联系(主从表),但是这个联系是为了确定位置的,但是影响开发效率
但是若我们不不添加这些联系,虽然测试时,不会出错,但可能会有漏网之鱼,即多余数据,对后续维护可能会造成影响
所以我们一般都会有对应索引(也可以加快查询速率,实际上是对表底层查询操作的变化)
数据实体描述 :
在对应sql中有对应描述,看如下图:

在这里插入图片描述

字段设置了comment,使得有描述,所以若要知道对应描述,可以去表里面看
课程管理模块的实现:
多条件课程列表查询 :
需求分析 :
根据课程名称及课程状态进行多条件查询:

在这里插入图片描述

在这里插入图片描述

在这里说明一下,一般的,大的项目都需要对应的接口文档,也就是提供对应规范
如请求地址:请求的地址信息
请求方式:如POST方式
接口描述:是哪个模块的接口,或者进行什么操作的
请求参数:需要传递的参数有哪些,以及对应参数说明
请求示例:请求的例子
响应结果示例:请求后,响应回来的参数例子
一般我们都是按照上面的操作来进行开发,这个一般需要自己公司里提供,这里就不提供了
对应实体类,参照表来编写,具体地址:
链接:https://pan.baidu.com/s/1VhDnglRa1yfiFpFnPNa6dg
提取码:alsk
其中说明一下:
ResponseResult 类的响应的形式:
public class ResponseResult {
 private Boolean success;
 private Integer state;
 private String message;
 private Object content;

public ResponseResult() {
}

public ResponseResult(Boolean success, Integer state, String message, Object 

content) {
    this.success = success;
    this.state = state;
    this.message = message;
    this.content = content;
}
 //getter/setter..

}

实体CourseVo类(View Object表现层对象:主要用于表现层来接收参数的),使得多个表的数据都可以放在这里进行统一操作
如添加数据时,对应传递的数据若包含两个及其两个以上的表字段变量,就需要对应一个新的类了,这里是CourseVo类
而使用这个类,存放后,可以使用对应方法,进行赋值:如BeanUtils.copyProperties()方法,需要jar包导入
因为最后操作的还是对应表的实体类,因为这个CourseVo类只是存放了对应需要的字段,而不是全部
接下来说明一下,为什么不直接使用这个类,而是需要对应实体类的存在
根据下面的添加课程及讲师信息的saveCourseOrTeacher方法为例子:
因为就算封装了全部,也没有对应多个表的联系
如外键和主键的对应变量
注意:数据库表不一定是设置了主键和外键(一般都会设置主键),只是有对应变量而已
而设置主键和外键,最主要的就是数据的对应关系操作,使得不会出现多余数据
因为一个类里面基本操作不了,实际上也是可以
如将这个类以本体作为变量,虽然由两个类,变成只有一个类了
但是发现会有多余字段,而且表逻辑性不好
使得效率(运行的效率,效率低,执行速度越慢,这里统称为效率)变低(同样是多个嵌套,但这里初始化变多了)
即虽然使得类变少(空间变少),但执行速度变慢了(少嵌套的情况下)
若不以本体,那么就少创建两个对象,那么多个嵌套也有优势了
若没有进行嵌套的话,那么效率就高点,因为只创建一个对象(赋值时),而他们需要三个对象
即少创建了两个对象,如使用上面的BeanUtils.copyProperties()方法就需要创建对应对象,加起来多创建了两个对象
我们发现,将代码全部放在一起,可以提高执行速度和内存空间(不以本体),当然也节省了硬盘空间,但这个可以忽略不计
内存空间的节省通常也会伴随着硬盘空间的节省,因为我们都是从硬盘的文件到内存的(而不是的通常不会,如数据库)
所以数据库的所占硬盘空间基本不会和内存空间联系,可以看成他们是有联系的
但是,若全部放在一起就不好维护,以及对应关系的确定,因为这样的效率与维护的成本来说太过于渺小
就如我们使用框架提高了开发速度,但不使用,则降低开发速度,也可以说使用is-else虽然比不上for循环判断
但实际上执行时,if-else要快,虽然我代码全部弄在一起,虽然很难维护,但快
所以在空间和内存时间(执行速度)的基础上,还要有维护
不能使得维护起来困难,所以一般在内存空间和时间(执行速度)上的小额度提升时,优先考虑维护性
小额度:就是非常小的提升,大额度提升那么就不考虑对应维护性了,这是相对对应代码的额度提升
若又可以提高维护性,又可以提高内存空间和时间(执行速度),那么我们也肯定是趋向与这一种的
而使用BeanUtils.copyProperties()方法可以然一个类的变量进行利用最大化,而不会产生多余字段,且分工明确
而不是将所有的信息放入一个类里面,这样的耦合度是非常高的(虽然方便)
所有说,在考虑到需要多个表变量时,用一个类来刚好获取
然后对相应的类进行赋值,这样每个变量都进行操作了,而没有多余的
这样以少的代码,实现同样的操作,且代码相对较少,比较好维护
而在开发中,我们也尽量不要将不同的类型变量放在一个类里面,就如一个狗狗接口里面不能有飞行方法等等
否则,当项目足够大时,再次看回来,会发现有很多多余代码,我们且很难再次进行维护
就如我们不会认为狗狗接口里面是会飞一样,所以一个大的接口,需要变成一个一个的小接口
上面的类也是如此,虽然一个类就可以搞定,但维护性不高
我们来个实例:假如一个类里面有10000个变量,我让你去这些变量中
找一个变量是属于用户表的,你一定是每个变量都会对应一下
那么假如有1000个表,不可能都去看一下吧,那么ok假如有注释,那么这些注释你也要一路看下去
万一没看到,略过了,那么需要重新看,这是非常痛苦的,若我们将表名与类名对象(见名知义)
就我们将这些变量从一开始就分工明确,如User类放user表的字段,很明显,我们维护时,直接找到这个类即可
在合并的类存在时(如CourseVo类),若这时你还想要这个类
那么当需要添加一个表字段时,你就需要添加一个实体,并在这个类里面添加对应字段
那么这时你会发现变得麻烦,更加的是,随着项目越来越大,每次创建这个对象,都要很久,影响业务(当然很少的变量下可以)
那么只需要初始化一次了,可是项目一般都是非常多的,所有基本不可能很少,所以不考虑
这就是我们为什么需要实体类的原因,而不是将所有变量放在一起,所以CourseVo类只是用来放多个表数据的,而不是全部变量
使得初始化时浪费时间少
正是由于这样,使得表与sql对应,那么在分开后,这个存放了总体的类一般都是存放数据的,所以可以用来当接收参数
而对于局部的存放全部变量类,一般也不会当成参数
主要的是为了防止其他方法里使用这个方法时,没有创建这个CourseVo类的原因(因为对应关系)
所以我们一般都会创建对应类来当参数
而不是直接用CourseVo类来当参数,虽然可以少创建对象
最后:最重要的原因是,每个表的字段是可能相同的,以及使用方法时,通过方法名和参数,就可以知道是干什么的了
所有通常不会放在一起
我们发现
无论是空间(空间在这里一般称为内存空间,因为硬盘空间可以忽略,因为程序还不足以影响硬盘空间)时间(执行速度)
都是进行互相影响的
所以我们需要算法来使得更少的代码来实现同样的效果,以后再说算法,实际上VO只是针对某些需求需要的,如果前端移动数据,那么可以放在一起,虽然一次查询很多,但是后续不用查询了,各有好处吧
注意:这个类主要存放的是多个表的字段变量,而一些像状态管理的,表自己就可以操作的,就不需要了
虽然也可以写上,但是不好维护
这个类需要自己根据需求来编写:
package com.lagou.domain;

/**
 *
 */

    public class CourseVO {
        /**
         * 课程名称
         */
        private String courseName;
        /**
         * 课程状态
         */
        private Integer status;

        public String getCourseName() {
            return courseName;
        }

        public void setCourseName(String courseName) {
            this.courseName = courseName;
        }

        public Integer getStatus() {
            return status;
        }

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

Dao层:CourseMapper:
package com.lagou.dao;

import com.lagou.domain.Course;

import java.util.List;

/**
 *
 */
public interface CourseMapper {
    /*
    多条件课程列表查询
    findCourseByCondition英文的大致介绍:查询Course表通过条件
    By:通过
    Condition:条件
     */

    public List<Course> findCourseByCondition(Course course);

}

对应映射配置文件(CourseMapper.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.lagou.dao.CourseMapper">
<!--多条件课程列表查询-->
    <select id="findCourseByCondition" parameterType="com.lagou.domain.CourseVo" 
            resultType="com.lagou.domain.Course">
        select * from course

        <where>
        <!--and相当于java里的&&,会被解析成&&的-->
        <!--前面说过,一般返回是null,""会被处理,而这里就由我们自己处理了(没有使用代码处理了)
注意:可能不同版本的mybatis对%的模糊查询有问题,所以建议使用concat拼接(具体可以百度),这里是没有问题的
-->
            <if test="courseName !=null and courseName!=''">
                and course_name like '%${courseName}%'
            </if>
            <if test="status !=null and status!=''">
                and status = #{status}
            </if>
            <if test="true">
                and is_Del != 1
                <!--
			这里写上and is_Del != 1,也就是说,保证是没有删除的
			所以查询所有也会用这个判断,也就是前端的无条件查询(这个除外)
			-->
            </if>
        </where>
    </select>

</mapper>

Service层:CourseService及其实现类 :
package com.lagou.service;

import com.lagou.domain.Course;
import com.lagou.domain.CourseVo;

import java.util.List;

/**
 *
 */
public interface CourseService {
        /*
    多条件课程列表查询

     */

    public List<Course> findCourseByCondition(CourseVo courseVo);

}

package com.lagou.service.impl;

import com.lagou.dao.CourseMapper;
import com.lagou.domain.Course;
import com.lagou.domain.CourseVo;
import com.lagou.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class CourseServiceImpl implements CourseService {

    @Autowired
    private CourseMapper courseMapper;
  /*
    多条件课程列表查询

     */
    @Override
    public List<Course> findCourseByCondition(CourseVo courseVo) {
        List<Course> courseByCondition = courseMapper.findCourseByCondition(courseVo);

        return courseByCondition;
    }
}

Web层:CourseController :
package com.lagou.controller;

import com.lagou.domain.Course;
import com.lagou.domain.CourseVo;
import com.lagou.domain.ResponseResult;
import com.lagou.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("/course")
public class CourseController {

    @Autowired
    private CourseService courseService;

    //多条件查询
    @RequestMapping("/findCourseByCondition")
    public ResponseResult findCourseByCondition(@RequestBody CourseVo courseVo){
        //之所以使用RequestBody,是因为传递过来的参数是json格式

        //调用service
        List<Course> courseByCondition = courseService.findCourseByCondition(courseVo);

        ResponseResult responseResult = new ResponseResult(true, 200, "响应成功", courseByCondition);

        return responseResult;

    }


}

添加日志文件log4j.properties:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

对应jar包:
   <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>
<!--差不多的日志(基本一样),即可以去掉一个-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
对应日志文件的介绍在63章博客里有
接下来就可以启动服务器,使用Postman测试接口了,测试(使用)方式56章博客里有
注意:对应sql表来说,手动设置排序的会优先于主键的排序,这个多用于排序字段
课程图片上传 :
在没有使用框架的项目中是没有进行图片的回显的(并不需要立即回显)
那么对应的得到图片信息(如在服务器的地址)会与web层进行一起操作,即顺便存到数据库
而这里由于需要立即回显,那么我们就将对应得到的图片信息(如在服务器的地址)与web层分开了
使得分开操作,如点击上传按钮,使得可以回显,然后存放到数据库里面,但是若多次上传图片
而不保存图片信息到数据库,那么对应的图片在服务器路径会多出很多,这是一个问题,后续看需求解决
上面的实际上也可以是其他的文件,反正是文件的上传(如音视频等等),因为是字节流的输入和输出
但要注意不要超过自己设置的配置文件的对应文件的最大大小,否则报错
需求分析:
需求:在新增课程页面需要进行图片上传,并回显图片信息:

在这里插入图片描述

添加部分springmvc.xml:
 <!--配置文件解析器-->
    <!-- 此处id为固定写法,不能随便取名-->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--
设定文件上传的最大值,比如说设置5242880,即最大值为5MB
5*1024*1024 = 5242880,从B开始,1B代表一个字节,这是5242880字节,即5*1024*1024B=5*1024kB=5MB
-->
        <property name="maxUploadSize" value="1048576"></property>
    </bean>
Web层:添加部分CourseController
    /*
    课程图片上传
     */
    @RequestMapping("/courseUpload")
    public ResponseResult fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest 
                                     request) throws IOException {

        //判断接收到的上传文件是否为空
        //file.isEmpty()判断对应的数据是否为空
        if(file.isEmpty()){
            throw new RuntimeException();
            //主动抛出异常,而不用等出现异常后执行对应异常信息(如try)
            //当然使用这个方法时也是需要对应处理异常的
        }

        //获取项目部署路径
        //D:\apache-tomcat-8.5.56\webapps\ssm_web\,不同文件系统,可能默认的\是/(linux)(但可能并不绝对),但反正是当前项目部署路径
        String realPath = request.getServletContext().getRealPath("/");
        //D:\apache-tomcat-8.5.56\webapps\
        String substring = realPath.substring(0, realPath.indexOf("ssm_web"));

        //获取原文件名
        //如lagou.jpg
        String originalFilename = file.getOriginalFilename();
        //让文件名基本不重复,一个用户基本不可能会有相同的名称
        //多个用户可能会有,但是用户之间肯定是不可互相访问的,所以这里就相当于设置了文件名不重复
        String newFileName = System.currentTimeMillis() + 
            originalFilename.substring(originalFilename.lastIndexOf("."));
        //得到后缀名,用时间戳当名称
        //如1651975001568.jpg

        //开始文件上传
        // D:\apache-tomcat-8.5.56\webapps\ upload\
        //注意:注释里面当出现\ u时,必须要有数,否则报错,所以上面我在\和u之间加了一个空格
        //\ u是转义字符,表示后面跟一个十六进制数,通过这个十六进制数来指定一个字符,若不是则报错
        String uploadPath = substring + "upload\\";
        //加上/也可以,但为了符号windows的目录化,就使用\\了,网页就是/
        File file1 = new File(uploadPath, newFileName);

        //如果目录不存在则创建目录(判断有没有父路径uploadPath,没有则创建)
        if(!file1.getParentFile().exists()){
            file1.getParentFile().mkdirs();
            System.out.println("创建目录" + file1);
        }

        //图片进行上传 通过里面的文件(实际上是原文件),来进行IO的输入和输出(输出到指定参数的文件)
        file.transferTo(file1);

        //将文件名和文件路径返回给前端,使得前端显示图片
        HashMap<String, String> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("fileName", newFileName);
        //文件名,如1651975001568.jpg
        objectObjectHashMap.put("filePath", "http://localhost:8080/upload/" + newFileName);
        //在服务器上的访问路径,也就是通过端口访问服务器资源的路径

        ResponseResult responseResult = new ResponseResult(true, 200, "图片上传成功", 
                                                           objectObjectHashMap);

        return responseResult; //会解析集合的,也就是将map进行解析


        //这里我说明一下:json实际上就是一种格式
        //怎么说呢,就是我们规定字符串以什么形式存在
        //如有[]包括,里面用大括号,所以可以说,字符串的一系列变化,就是json格式
        //那么json格式可以存在多个形式,只是使用json格式时
        //有很多工具,就是将对应信息解析成数据
        //对于类来说,是对应get解析,名称首字母大小写忽略,没有get方法,对应字段不会显示(不会出现键值对)
        //用特定格式的字符串存好
        //如数值,字符串,普通集合,map集合,类,json工具通过解析他们,进行得到对应字符串使得拼接起来
        //如普通集合list里面有数值1,字符串1,类(或者集合),那么结果是
        //先解析普通集合list,给出[]
        //数值1,给出1,那么结果是[1]
        //字符串1,给出"1"(java里面会转义),即结果[1,"1"],不同集合数之间会加上,
        //类,给出{"key":value},即结果是[1,"1",{"key":value}]
        //map集合,给出{"key":value},即结果是[1,"1",{"key":value},{"key":value}](一般是补充的,否则map基本和类是一样的)
        //可以发现,对应json就是通过解析,来操作字符串,使得返回的
        //注意:上面的key是由""包括的,也就是说,无论key是什么类型,都被""包括,这是json的解析方式
        //当然,对于map集合本身来说,是有对应覆盖的,如key相同,那么他只有对应一个相同的那个key
        //然后作为响应数据,请求数据我们一般之间编写这样的字符串(json)的
        
        //上面是对于数据变json,而json(一个字符串)变数据就相当于上面进行反方向的操作,也就是结果变对应类型
        //但是若是不符合的结构的就不会进行赋值,如接收参数的类,但是用了[]
        //注意:在结构同样的时候,那么就是对于set的操作了,忽略大小写,没有set就直接赋值
        //但是若没有对应传入参数,或者值是""且对应类型不是字符串(这里后面可能会解决你的疑惑)
        //那么就没有进行赋值而已(那么一般就是默认值),而多余的,就是没有进行操作,所以也不会报错
        //且响应的数据顺序是按照是否全部小写的
        //若全部小写则按照ascll码值来决定先后,不全部小写在全部小写后面,然后他们按照ascll码值来决定先后
        //而由于数据变json对应key必须""包括,那么json变数据对应的key也要被""包括(Postman可能默认加上""),否则不识别,即报错(当然,随着版本更替,可能不用"",但最好使用,因为规范是可以避免很多问题哦,也不怕版本更替哦,即规范基本不会出错,当然,这些会出错的地方了解即可,并不需要自己测试,因为没有意义,知识太多,死磕细节没有用的,反正基本是别人产出的,手动滑稽🤭)
        //这是json的解析方式,不可能不相同的,所以正向操作和逆向操作应当相同
        
        //注意:这里回顾一下,当使用ajax时,若设置了json发送格式,若不使用字符串,那么就会当成键值对
        //js的存放方式,一般就是{},和[]
        //这时若后台使用json解析,则会报错,但是若目标是字符串
        //也就是@RequestBody注解的目标是String类型的,即会将{}里的键值对变成字符串赋值
        //若有[],那么对应{}就是undefined=,多个则是undefined=&undefined= ...
        //这是目标是String的操作
        
        //最后:@RequestBody和@ResponseBody都是操作字符串,只是json是一个有格式的字符串
        //而由于这样所以一般他们只能操作请求体的数据,即一般都是post请求,且data的值一般都是字符串
        //若是get那么基本不可能是字符串了,所以也就基本操作不了
        //对于get来说,当url有中文时,浏览器或者框架对应url解析,是中文的不同操作
        //会使得报错的,因为URL的解析中文时是特殊的(基本框架都会判断,使得报错,解析的除外)
        //不同浏览器或者不同框架版本(有默认解析的就可以)可能会有所不同
    }


}

使用Postman测试接口
新建课程信息 :
需求分析:
填写好新增课程信息后,点击保存,将表单信息保存到数据库中

在这里插入图片描述

在这里插入图片描述

Dao层:添加部分CourseMapper :
	/*
    新增课程信息
     */
    public void saveCourse(Course course);
    
    /*
    新增讲师信息
     */
    public void saveTeacher(Teacher teacher);
添加部分CourseMapper.xml:
 <!--新增课程信息-->
    <insert id="saveCourse" parameterType="Course">
        INSERT INTO course(
        course_name,
        brief,
        preview_first_field,
        preview_second_field,
        course_img_url,
        course_list_img,
        sort_num,
        price,
        discounts,
        sales,
        discounts_tag,course_description_mark_down,
        create_time,
        update_time
        ) VALUES(#{courseName},#{brief},#{previewFirstField},#{previewSecondField},#
        {courseImgUrl},
        #{courseListImg},#{sortNum},#{price},#{discounts},#{sales},#{discountsTag},#
        {courseDescriptionMarkDown},
        #{createTime},#{updateTime});
        <!--
返回查询的值(id),赋值给传入的对应类变量,set和赋值操作,且忽略大小写,mybatis一般都是如此
其他的框架一般都是set,但也有赋值操作,如@RequestBody,而有些只会操作set,也就是说,不会赋值
但只操作set的,删除set通常都不会报错的,只是没有赋值而已
-->
        <selectKey resultType="int" order="AFTER" keyProperty="id">
            select last_insert_id()
        </selectKey>
    </insert>

    <!--新增讲师信息-->
    <insert id="saveTeacher" parameterType="Teacher">
        INSERT INTO teacher(course_id,
                            teacher_name,
                            position,
                            description,
                            create_time,
                            update_time)
        VALUES (#{courseId}, #{teacherName}, #{position}, #{description}, #{createTime}, #
        {updateTime});

        <!--注意:复制时#与{}之间最好不要有空格,要紧挨一起,否则执行对应代码时,可能会报错-->
    </insert>
注意:由于参数过多,前面的CourseVo的变量太少,不足以满足我们需求,所以我们需要添加变量:
package com.lagou.domain;

import java.util.Date;

/**
 *
 */
public class CourseVo {
    //------------------------------------课程信息,包括了课程名称和课程状态
    //主键
    private int id;

    //课程名称
    private String courseName;

    //课程一句话简介
    private String brief;

    //原价
    private double price;

    //原价标签
    private String priceTag;

    //优惠价
    private double discounts;

    //优惠价标签
    private String discountsTag;

    //课程内容markdown
    private String courseDescriptionMarkDown;

    //课程描述
    private String courseDescription;

    //课程分享图片url
    private String courseImgUrl;

    //是否新品
    private int isNew;

    //广告语
    private String isNewDes;

    //最后操作者
    private int lastOperatorId;

    //自动上架时间
    private Date autoOnlineTime;

    //创建时间
    private Date createTime;

    //更新时间
    private Date updateTime;

    //是否删除
    private int isDel;

    //总时长
    private int totalDuration;

    //课程列表展示图片
    private String courseListImg;

    //课程状态,0-草稿,1-上架
    private int status;

    //课程排序
    private int sortNum;

    //课程预览第一个字段
    private String previewFirstField;

    //课程预览第二个字段
    private String previewSecondField;

    //销量
    private int sales;

    //------------------------------------讲师信息

    //讲师姓名
    private String teacherName;

    //讲师职务
    private String position;

    //介绍
    private String description;

    public int getId() {
        return id;
    }

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

    public String getCourseName() {
        return courseName;
    }

    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }

    public String getBrief() {
        return brief;
    }

    public void setBrief(String brief) {
        this.brief = brief;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getPriceTag() {
        return priceTag;
    }

    public void setPriceTag(String priceTag) {
        this.priceTag = priceTag;
    }

    public double getDiscounts() {
        return discounts;
    }

    public void setDiscounts(double discounts) {
        this.discounts = discounts;
    }

    public String getDiscountsTag() {
        return discountsTag;
    }

    public void setDiscountsTag(String discountsTag) {
        this.discountsTag = discountsTag;
    }

    public String getCourseDescriptionMarkDown() {
        return courseDescriptionMarkDown;
    }

    public void setCourseDescriptionMarkDown(String courseDescriptionMarkDown) {
        this.courseDescriptionMarkDown = courseDescriptionMarkDown;
    }

    public String getCourseDescription() {
        return courseDescription;
    }

    public void setCourseDescription(String courseDescription) {
        this.courseDescription = courseDescription;
    }

    public String getCourseImgUrl() {
        return courseImgUrl;
    }

    public void setCourseImgUrl(String courseImgUrl) {
        this.courseImgUrl = courseImgUrl;
    }

    public int getIsNew() {
        return isNew;
    }

    public void setIsNew(int isNew) {
        this.isNew = isNew;
    }

    public String getIsNewDes() {
        return isNewDes;
    }

    public void setIsNewDes(String isNewDes) {
        this.isNewDes = isNewDes;
    }

    public int getLastOperatorId() {
        return lastOperatorId;
    }

    public void setLastOperatorId(int lastOperatorId) {
        this.lastOperatorId = lastOperatorId;
    }

    public Date getAutoOnlineTime() {
        return autoOnlineTime;
    }

    public void setAutoOnlineTime(Date autoOnlineTime) {
        this.autoOnlineTime = autoOnlineTime;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public int getIsDel() {
        return isDel;
    }

    public void setIsDel(int isDel) {
        this.isDel = isDel;
    }

    public int getTotalDuration() {
        return totalDuration;
    }

    public void setTotalDuration(int totalDuration) {
        this.totalDuration = totalDuration;
    }

    public String getCourseListImg() {
        return courseListImg;
    }

    public void setCourseListImg(String courseListImg) {
        this.courseListImg = courseListImg;
    }

    public int getStatus() {
        return status;
    }

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

    public int getSortNum() {
        return sortNum;
    }

    public void setSortNum(int sortNum) {
        this.sortNum = sortNum;
    }

    public String getPreviewFirstField() {
        return previewFirstField;
    }

    public void setPreviewFirstField(String previewFirstField) {
        this.previewFirstField = previewFirstField;
    }

    public String getPreviewSecondField() {
        return previewSecondField;
    }

    public void setPreviewSecondField(String previewSecondField) {
        this.previewSecondField = previewSecondField;
    }

    public int getSales() {
        return sales;
    }

    public void setSales(int sales) {
        this.sales = sales;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

//toString()方法没有用到,所以这里就不生成了
Service层:添加部分CourseService 及其实现类:
/*
    添加课程及讲师信息
     */
    public void saveCourseOrTeacher(CourseVo courseVo) throws InvocationTargetException, 
IllegalAccessException;

/*
    添加课程及讲师信息
     */
    @Override
    public void saveCourseOrTeacher(CourseVo courseVo) throws InvocationTargetException, 
IllegalAccessException {
        //封装课程信息
        Course course = new Course();

        //将对应参数的值赋值给course
        //只操作set赋值(首字母大小写忽略,即不看变量,判断set名称符合来进行赋值)
        //在这个项目中也有一个对应包的类名与这个是一样的,即org.springframework.beans.BeanUtils
        //但操作不同,这里用的是org.apache.commons.beanutils.BeanUtils
        //不要导错了,否则的话,没有进行赋值操作,虽然类名一样,但操作不一样
        //且由于类名相同,那么没有导入的类,不可再次导入了,否则报错
        //且没有导入的那个类使用时,是直接使用全路径名来操作的
        BeanUtils.copyProperties(course, courseVo);
        //注意:这个方法会检查Date的值是否是null,若是则报错,虽然我们设置Date时可以设置null
        //但他这个方法会判断的,即不准让Date出现null

        //补全课程信息,及创建时间(更新时间开始与创建时间一样)
        Date date = new Date();

        course.setCreateTime(date);
        course.setUpdateTime(date);

        //保存课程
        courseMapper.saveCourse(course);

        //获取新插入的id值
        int id = course.getId();

        //封装讲师信息
        Teacher teacher = new Teacher();

        BeanUtils.copyProperties(teacher, courseVo);

        //补全讲师信息
        teacher.setCreateTime(date);
        teacher.setUpdateTime(date);
        teacher.setIsDel(0);
        teacher.setCourseId(id);

        //保存讲师信息
        courseMapper.saveTeacher(teacher);



    }
Web层:添加部分CourseController:
/*
    新增课程信息及其讲师信息
    新增课程信息和修改课程信息要写在同一个方法中
    后面会进行修改扩展,主要是判断id的值
    因为添加和修改的区别就是id的传递
    因为不传递id,基本就是不赋值,那么基本就是默认值
    所以我们通常判断是否是对应默认值,来确定执行的是添加操作还是修改操作
    所以方法名是下面这样写的
     */
    @RequestMapping("saveOrUpdateCourse")
    public ResponseResult saveOrUpdateCourse(@RequestBody CourseVo courseVo) throws 
        InvocationTargetException, IllegalAccessException {

        //调用service
        courseService.saveCourseOrTeacher(courseVo);

        ResponseResult responseResult = new ResponseResult(true, 200, "响应成功", null);
        return responseResult;

    }
注意:由于BeanUtils.copyProperties(course, courseVo)方法不能使得null赋值给Date
所以我们删掉CourseVo对应类型的Date的变量及其对应的set和get方法
使用Postman测试接口(若没有删掉对应变量,则会报错,记得删掉后进行测试)
修改课程信息 :
点击保存按钮,将修改后的课程信息保存到数据库中
首先我们先得到对应数据来进行回显操作:
主要是sql的编写:
SELECT
  c.*,
  t.teacher_name,
  t.position,
  t.description
FROM
  course c
  LEFT JOIN teacher t
    ON c.id = t.id
WHERE c.id = 16

-- 16是我测试的数据,在java里面应该是占位符
-- 这里必须要查询对应课程信息,所以左查询的左边的课程
-- 根据查询的信息进行回显
Dao层:添加部分CourseMapper:
/*
    回显课程信息(根据id查询对应的课程信息及关联的讲师信息)
     */
    public CourseVo findCourseById(Integer id);
添加部分CourseMapper.xml:
 <!--回显课程信息 public CourseVo findCourseById(Integer id)-->

    <select id="findCourseById" parameterType="int" resultType="CourseVo">
        SELECT c.*,
               t.teacher_name,
               t.position,
               t.description
        FROM course c
                 LEFT JOIN teacher t
                           ON c.id = t.id
        WHERE c.id = #{id}
    </select>
Service层:添加部分CourseService及其实现类:
/*
   根据id查询课程信息(包括讲师信息)
    */
    public CourseVo findCourseById(Integer id);
//根据id查询课程信息(包括讲师信息)
    @Override
    public CourseVo findCourseById(Integer id) {
        CourseVo courseById = courseMapper.findCourseById(id);
        return courseById;
    }
//想要好的代码,那么变量一般少声明,即这里可以直接return courseMapper.findCourseById(id);,虽然不好维护
Web层:添加部分CourseController:
//根据id查询课程信息(包括讲师信息)
    @RequestMapping("/findCourseById")
    public ResponseResult findCourseById(Integer integer){
        CourseVo courseById = courseService.findCourseById(integer);
        ResponseResult responseResult = new ResponseResult(true,200,"根据id查询课程信息成
                                                           功",courseById);
        return responseResult;
    }
使用Postman测试接口(自己处理,后面会提示一下)
回显后,接下来我们进行课程的修改操作,在这里提醒一下:在表单中,对应输入框只要输入了数据,那么就不是空串,如空格
当然sql里的表,只要没有设置默认值,那么基本第一次添加时,没添加的地方默认是null
若设置了默认值,那么就是设置的默认值,如0等等
而由于后台操作数据时,若是根据名称来获得,对应名称不在,结果一般是null,不写,结果一般是""(空串)
而使用json解析时,对应key不在,一般不会解析赋值,即使用默认的
若是是"“,非String使用默认(包括整型默认值和null)和String使用”"(空串),整型默认值包括0,0.0或者其他
我们发现结果有整型默认值或者null或者""的,而由于整型默认值的存在
所以有时候网站上出现的默认0,0.0或者其他的情况,一般都是0,但上面的操作若在判断解决后
一般别人是通过不了开发者工具来使得数据库进行不合理的数据存放的,因为无非就是如下情况
前台有参数:后台显示值是空值或者有值,前台无参数:后台显示值是默认值(整型默认值,null)等等
至于整型默认值,我们可以不做要求,因为反正都是合理的(除非后台不合理,或者报错,那这是后台的问题)
而出现的null和"",所以下面的sql语句中都对这两种情况进行操作了
所以数据库里面一般不会使得出现null和空值(除非没有添加),而出现这些情况,我们一般是不会进行操作字段的,如修改等等
所以可以说明:在没有后台错误的情况,开发者工具一般不可能使得数据库数据出问题
就算没有设置判断,为null或者""也只是数据的不合理而已(判断基本是防止全部删除的)
需求分析:

在这里插入图片描述

Dao层:添加部分CourseMapper:
/*
    更新课程信息
     */
    public void updateCourse(Course course);
    /*
    更新讲师信息
     */
    public void updateTeacher(Teacher teacher);
添加部分CourseMapper.xml:
<!--更新课程信息-->
    <update id="updateCourse" parameterType="Course">
<!--
trim用来操作对应的sql代码的添加和删除,其中prefix表示添加这个sql后,再添加内部的sql
而suffixOverrides表示当内部的sql执行完毕后
将最后的逗号去掉(若最后一个数是逗号则去掉,若不是则当然不去掉,即不操作)

当trim里面没有加上对应的sql语句时,那么他也不会操作,即不会凭空的加上对应sql语句(这里是set)
-->
        update course
        <trim prefix="SET" suffixOverrides=",">
            <if test="courseName != null and courseName != ''">
                course_name = #{courseName},
            </if>
            <if test="brief != null and brief != ''">
                brief=#{brief},
            </if>
            <if test="previewFirstField != null and previewFirstField != ''">
                preview_first_field=#{previewFirstField},
            </if>
            <if test="previewSecondField != null and previewSecondField != ''">
                preview_second_field=#{previewSecondField},
            </if>
            <if test="courseImgUrl != null and courseImgUrl != ''">
                course_img_url=#{courseImgUrl},
            </if>
            <if test="courseListImg != null and courseListImg != ''">
                course_list_img=#{courseListImg},
            </if>
            <if test="sortNum != null and sortNum != ''">
                sort_num=#{sortNum},
            </if>
            <if test="price != null and price != ''">
                price=#{price},
            </if>
            <if test="discounts != null and discounts != ''">
                discounts=#{discounts},
            </if>
            <if test="sales != null and sales != '' or sales==0">
                sales=#{sales},
            </if>
            <if test="discountsTag != null and discountsTag != ''">
                discounts_tag=#{discountsTag},
            </if>
            <if test="courseDescriptionMarkDown != null and
                          courseDescriptionMarkDown != ''">
                course_description_mark_down=#{courseDescriptionMarkDown},
            </if>
            <if test="updateTime != null">
                update_time=#{updateTime},
            </if>
        </trim>
        <where>
            <if test="id!=null and id != '' ">id=#{id}</if>
        </where>

    </update>

    <!--更新讲师信息-->
    <update id="updateTeacher" parameterType="Teacher">
       update teacher
       <trim prefix="SET" suffixOverrides=",">
         <if test="teacherName != null and teacherName != ''">
           teacher_name = #{teacherName},
         </if>
         <if test="position != null and position != ''">
           position = #{position},
         </if>
         <if test="description != null and description != ''">
           description = #{description},
         </if>
         <if test="updateTime != null">
           update_time=#{updateTime}
           <!--这后面不加","是因为这是最后判断的,所以可以不加-->
             <!--因为动态sql基本就是从上到下的添加-->
         </if>
       </trim>
       <where>
         <if test="courseId != null and courseId != '' ">course_id = #{courseId}</if>
       </where>
    </update>
Service层:添加部分CourseService及其实现类:
  /*
    更新课程及讲师信息
     */
    public void updateCourseOrTeacher(CourseVo courseVo) throws InvocationTargetException, 
IllegalAccessException;

/*
    更新课程及讲师信息
     */
    @Override
    public void updateCourseOrTeacher(CourseVo courseVo) throws InvocationTargetException, 
IllegalAccessException {

        //封装课程信息
        Course course = new Course();

        BeanUtils.copyProperties(course, courseVo);

        //补全信息
        Date date = new Date();
        course.setUpdateTime(date);

        //更新课程信息
        courseMapper.updateCourse(course);

        //封装讲师信息
        Teacher teacher = new Teacher();
        BeanUtils.copyProperties(teacher, courseVo);

        //补全信息
        teacher.setCourseId(course.getId());
        teacher.setUpdateTime(date);

        //更新讲师信息
        courseMapper.updateTeacher(teacher);

        //那么到这里我们可以发现,一个CourseVo基本封装了课程及其讲师的变量
        //使得我们只要操作这个类进行赋值就基本可以操作对应类的值了
        //但由于有些是需要后台进行封装的,如课程里的讲师外键(因为前端基本只会传递一个主体的主键)
        //或者时间的更新等等,那么由这种可以被随时改变的,我们通常放在后端操作,因为若给前端操作的话
        //不止前端需要多个参数,后端也需要多个参数,那么本来一个方法可以解决的,使得前后端都进行参数的添加了
        //所以可以有本来的参数就可以得到的,一般都在后端获得,如teacher.setCourseId(course.getId())
        //或者创建或者更新时间的方法:teacher.setCreateTime(date),teacher.setUpdateTime(date)等等
    }
Web层:修改部分CourseController(扩展saveOrUpdateCourse方法):
  @RequestMapping("saveOrUpdateCourse")
    public ResponseResult saveOrUpdateCourse(@RequestBody CourseVo courseVo) throws 
    InvocationTargetException, IllegalAccessException {

        ResponseResult responseResult;
        if(courseVo.getId() != 0) {
            //新增课程
            //调用service
            courseService.saveCourseOrTeacher(courseVo);

            responseResult = new ResponseResult(true, 200, "新增成功", null);
        }else{
            //更新课程
            //调用service
            courseService.updateCourseOrTeacher(courseVo);

            responseResult = new ResponseResult(true, 200, "修改成功", null);


        }
        return responseResult;

    }
使用Postman测试接口
课程状态管理:
需求分析:
在课程列表展示页面中,可以通过点击 上架/下架按钮,修改课程状态:

在这里插入图片描述

Dao层:添加部分CourseMapper:
/*
    课程状态管理

     */
    public void updateCourseStatus(Course course);
添加部分CourseMapper.xml:
  <!--课程状态管理-->
    <update id="updateCourseStatus" parameterType="Course">
        update course
        set status=#{status},
            update_time=#{updateTime}
        where id = #{id}
    </update>
Service层:添加部分CourseService及其实现类:
 /*
    课程状态变更
     */
    public void updateCourseStatus(int id, int status);
/*
    课程状态变更
     */
    @Override
    public void updateCourseStatus(int id, int status) {

        //封装数据
        Course course = new Course();
        course.setId(id);
        course.setStatus(status);
        course.setUpdateTime(new Date());

        //调用mapper
        courseMapper.updateCourseStatus(course);

        //在sql语句需要多个参数时,一般都是用类来当参数,而不是分开的参数,否则当需要添加参数时
        //对应的地方都要添加,而使用类,在封装时会自动封装,这样基本只需要修改Service层就可以了
    }
Web层:添加部分CourseController:
/*
    课程状态管理
     */
    @RequestMapping("/updateCourseStatus")
	//一般方法名与这个请求一样,但有时只需要符合意思即可(见名知意)
    public ResponseResult updateCourseStatus(Integer id ,Integer status){

        //调用service,传递参数
        courseService.updateCourseStatus(id,status);

        //响应结果
        HashMap<String, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("status",status);

        ResponseResult responseResult = new ResponseResult(true, 200, "课程状态变更成功", 
                                                           objectObjectHashMap);
        return responseResult;
    }
使用Postman测试接口
课程内容展示:
需求分析:
需求:点击内容管理,展示课程对应的课程内容(课程内容包含了章节和课时,还有回显的课程信息)

在这里插入图片描述

在这里插入图片描述

现在我们编写查询章节和课时信息的代码:
Dao层:CourseContentMapper:
package com.lagou.dao;

import com.lagou.domain.CourseSection;

import java.util.List;

/**
 *
 */
public interface CourseContentMapper {

    /*
    根据课程id查询课程内容(后面通过这个来回显),章节信息以及其对应的课时信息
     */
    public List<CourseSection> findSectionAndLessonByCourseId(Integer courseId);
}

对应映射配置文件(CourseContentMapper.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.lagou.dao.CourseContentMapper">


    <resultMap id="SectionAndLessonResultMap" type="CourseSection">
        <id property="id" column="id"/>
        <result property="courseId" column="course_id"/>
        <result property="sectionName" column="section_name"/>
        <result property="description" column="description"/>
        <result property="createTime" column="create_time"/>
        <!--创建时间和修改时间可以不操作,因为基本不会用
若你想要完整性,那么可以写上去,满足你的完整性,或者满足一下强迫症,ヽ(✿゚▽゚)ノ-->
        <result property="updateTime" column="update_time"/>
        <result property="isDe" column="is_del"/>
        <result property="orderNum" column="order_num"/>
        <result property="status" column="status"/>
        <collection property="lessonList" ofType="CourseLesson">

            <id property="id" column="lessonId"/>
            <result property="courseId" column="course_id"/>
            <result property="sectionId" column="section_id"/>
            <result property="theme" column="theme"/>
            <result property="duration" column="duration"/>
            <result property="isFree" column="is_free"/>
            <result property="orderNum" column="order_numl"/>
            <result property="status" column="statusl"/>

        </collection>

    </resultMap>


    <!--根据课程id查询章节信息和课时信息-->
    <select id="findSectionAndLessonByCourseId" resultMap="SectionAndLessonResultMap">
        SELECT
        cs.*,
        <!--起别名,使得映射时,会使用正确的值,而不是左边的对应值
        如当没有设置这个时,id有两个,那么优先使用左边的
        -->
        cl.id lessonid,
        cl.course_id,
        cl.section_id,
        cl.theme,
        cl.duration,
        cl.create_time,
        cl.update_time,
        cl.is_del,
        cl.order_num order_numl,
        cl.status statusl
        FROM
        course_section cs
        LEFT JOIN course_lesson cl
        ON cs.id = cl.section_id
        WHERE cs.course_id = #{id}
        ORDER BY cs.order_num  
        <!--排序,使得根据排序来显示先后,但是,这里只对章节进行排序
没有对课程信息排序,因为这里的sql表帮我们对课时进行排序了(使用索引操作,注意是索引操作,满足索引才会出现排序)
一般也是根据order_num来进行排序的,索引的操作,一般是对应的字段越大,越在后面,满足上面的排序的情况下
-->
    </select>
</mapper>
Service层:CourseContentService及其实现类 :
package com.lagou.service;

import com.lagou.domain.CourseSection;

import java.util.List;

/**
 *
 */
public interface CourseContentService {
    /*
   根据课程id查询课程内容(后面通过这个来回显),章节信息以及其对应的课时信息
     */
    public List<CourseSection> findSectionAndLessonByCourseId(Integer courseId);
}

package com.lagou.service.impl;

import com.lagou.dao.CourseContentMapper;
import com.lagou.domain.CourseSection;
import com.lagou.service.CourseContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 */
@Service
public class CourseContentServiceImpl implements CourseContentService {

    @Autowired
    private CourseContentMapper courseContentMapper;

    //根据课程id查询课程内容(后面通过这个来回显),章节信息以及其对应的课时信息
    @Override
    public List<CourseSection> findSectionAndLessonByCourseId(Integer courseId) {
        List<CourseSection> sectionAndLessonByCourseId = 
            courseContentMapper.findSectionAndLessonByCourseId(courseId);
        return sectionAndLessonByCourseId;
    }
}

Web层:CourseContentController:
package com.lagou.controller;

import com.lagou.domain.CourseSection;
import com.lagou.domain.ResponseResult;
import com.lagou.service.CourseContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("/courseContent")
public class CourseContentController {


    @Autowired
    private CourseContentService courseContentService;

    //根据课程id查询课程内容
    //到这里发现,dao,service,controller的这些层,对应方法都是一样的
    //主要是为了方便区分,若使得方法多样,则不好维护(对看和找来说)
    @RequestMapping("/findSectionAndLesson") //这个配置有时只要符合意思即可,不需要与方法名一模一样
    public ResponseResult findSectionAndLessonByCourseId(Integer courseId){
        //调用service
        List<CourseSection> sectionAndLessonByCourseId = 
            courseContentService.findSectionAndLessonByCourseId(courseId);

        ResponseResult responseResult = new ResponseResult(true, 200, "章节及课时内容查询成功", 
                                                           sectionAndLessonByCourseId);
        return responseResult;

    }
}

使用Postman测试接口
上面只是查询对应课程内容(章节和课时信息),但前端通常有对应课程信息,即现在我们进行回显操作
Dao层:添加部分CourseContentMapper:
 /*
    回显章节对应的课程信息
     */
    public Course findCourseByCourseId(Integer courseId);
添加部分CourseContentMapper.xml:
  <!--回显课程信息-->
    <select id="findCourseByCourseId" parameterType="int" resultType="Course">
        select id, course_name
        from course
        where id = #{id}
    </select>
Service层:添加部分CourseContentService及其实现类 :
/*
   回显章节对应的课程信息
    */
    public Course findCourseByCourseId(Integer courseId);
    /*
 回显章节对应的课程信息
  */
    @Override
    public Course findCourseByCourseId(Integer courseId) {
        Course courseByCourseId = courseContentMapper.findCourseByCourseId(courseId);
        return courseByCourseId;
    }
Web层:添加部分CourseContentController:
   /*
 回显章节对应的课程信息
  */
    @RequestMapping("/findCourseByCourseId")
    public ResponseResult findCourseByCourseId(Integer courseId){
        //调用service
        Course courseByCourseId = courseContentService.findCourseByCourseId(courseId);

        ResponseResult responseResult = new ResponseResult(true, 200, "查询课程信息成功", 
                                                           courseByCourseId);
        return responseResult;
    }
使用Postman测试接口
新建章节信息
需求分析:
在课程内容展示页面中,可以通过点击添加阶段按钮,添加章节信息

在这里插入图片描述

Dao层:添加部分CourseContentMapper:
    /*
    新建章节信息
     */
    public void saveSection(CourseSection courseSection);
添加部分CourseContentMapper.xml:
  <!--新建章节信息-->
    <insert id="saveSection" parameterType="CourseSection">
    INSERT INTO course_section(
         course_id,
         section_name,
         description,
         order_num,
         STATUS, <!--这个字段可以不需要,当然写上也是没事的,因为有默认值-->
         create_time,
         update_time
       )VALUES(#{courseId},#{sectionName},#{description},#{orderNum},
       #{status},#{createTime},#{updateTime})
    </insert>
    <!--
有时候添加数据,未必一定要所有数据添加,也可以添加一部分,如前面的添加课程信息,并没有全部添加
这是根据需求来的,什么地方添加时,自然的回有对应sql,而不是直接一个地方全部添加,这样是非常不友好的
当我们需要在不同页面操作添加时,若都是全部添加,那么其中一个页面,也必须要保证只会操作当前页面的添加
那么就需要对对应的类进行封装,可是不同的页面,要使用远程封装是非常困难的,且执行sql也要更多时间了
所以不同的添加只会操作对应数据,而不会进行全部添加,这样既不会影响其他数据,也会提高效率(不是开发效率)
是开发效率的会指定
-->
Service层:添加部分CourseContentService及其实现类 :
/*
    新建章节信息
     */
    public void saveSection(CourseSection courseSection);
 /*
    新建章节信息
     */
    @Override
    public void saveSection(CourseSection courseSection) {
        Date date = new Date();
        courseSection.setCreateTime(date);
        courseSection.setUpdateTime(date);
        courseContentMapper.saveSection(courseSection);
    }
Web层:添加部分CourseContentController:
  /*
    新建章节信息
    与新增课程一样,添加和修改操作放在一起,通过判断id值
    来操作对应的sql执行,所以方法名是如下这样的
    而对应的代码修改,也是在后面进行修改扩展
    现在只操作新建章节信息
     */
    @RequestMapping("/saveOrUpdateSection")
    public ResponseResult saveOrUpdateSection(@RequestBody CourseSection courseSection){

        //调用service
        courseContentService.saveSection(courseSection);
        ResponseResult responseResult = new ResponseResult(true, 200,"新增章节成功", null);

        return responseResult;
    }
使用Postman测试接口
修改章节信息
需求分析:
点击确定按钮,将修改后的章节信息保存到数据库中
注意:这里就不需要回显了,具体原因如下:
我们在修改课程时,是需要回显的,因为我们并没有在同一个页面,也就是没有具体的数据
而这个章节,由于是在同一个页面,即我们在查询章节信息和课程信息中是有对应数据的
所以前端是可以帮我们进行对应的回显
那么你可能会有疑惑:为什么我们不通过后台得到数据进行回显呢,那是因为若我们需要后台得到数据回显
那么也就必须好编写对应代码,造成执行速度变低(前端的逻辑操作数据,通常比后端的操作数据快,特别是远程时,更加明显)
而我们在查询到章节信息和课时信息时,这些信息我们都放在前端的
所以我们可以通过前端来直接获得,这样节省很多代码以及提高执行速度(或者说执行效率)
最后:回显实际上基本都是根据id查询对应的信息的操作,然后将对应数据显示在页面上进行展示的,通常用于修改
所以一般的,在修改操作之前,都需要回显后,再进行修改操作

在这里插入图片描述

Dao层:添加部分CourseContentMapper:
/*
       更新章节信息
     */
    public void updateSection(CourseSection courseSection);
添加部分CourseContentMapper.xml:
 <!-- 更新章节信息-->
    <update id="updateSection" parameterType="CourseSection">
        UPDATE course_section
           <trim prefix="SET" suffixOverrides=",">
             <if test="sectionName != null and sectionName != ''">
               section_name = #{sectionName},
             </if>
             <if test="description != null and description != ''">
               description = #{description},
             </if>
             <if test="orderNum != null and orderNum != '' or orderNum == 0">
               order_num = #{orderNum},
             </if>
             <if test="updateTime != null">
               update_time=#{updateTime}
             </if>
           </trim>
           <where>
             <if test="id != null and id != '' ">id = #{id}</if>
           </where>
    </update>
Service层:添加部分CourseContentService及其实现类 :
 /*
    更新章节信息
     */
    public void updateSection(CourseSection courseSection);
 /*
    更新章节信息
     */
    @Override
    public void updateSection(CourseSection courseSection) {
        courseSection.setUpdateTime(new Date());
        courseContentMapper.updateSection(courseSection);
    }
Web层:修改部分CourseContentController(扩展saveOrUpdateSection方法):
  @RequestMapping("/saveOrUpdateSection")
    public ResponseResult saveOrUpdateSection(@RequestBody CourseSection courseSection){

        ResponseResult responseResult
        if(courseSection.getId() == null) {
            //调用service
            courseContentService.saveSection(courseSection);
            responseResult = new ResponseResult(true, 200, "新增章节成功", null);
        }else{
            //调用service
            courseContentService.updateSection(courseSection);
            responseResult = new ResponseResult(true, 200, "修改章节成功", null);
        }
        return responseResult;
    }
使用Postman测试接口
这里说明一下为什么新增操作和修改操作要放到一起:
第一:对应传递的参数基本一致,除了对应id
第二:修改操作的页面和新增操作的页面基本是同一个(在少数情况下,如果新增和修改的业务代码基本不会相同,那么建议分开)
只是对应传递的参数id的不同(一般是这个,传递空串),或者没有加上id
若你不放在一起,那么对应的Controller要多写一个,且前端也要多写一个请求页面
那么我们为什么不简便一点呢,所以一般都放在一起,正是因为只有id的不同,才好进行判断
若你将所有的操作放在一个Controller里,那么基本是很难判断出来的,不像这个只需要判断id
比如说回显操作,一般我们只需要传递id,若放在新增操作里,就要将id用json来操作,那么对应的类只有id有值
那么当你新增时,这时虽然我们可以判断id,而进行回显的操作,但是id的判断是肯定只有一次的
若有id一样,那么你肯定也会判断其他字段,这时代码就多起来了
也就出现了前面说的,我们需要维护性,虽然可以简便,但维护性不高
而保存和修改的相似性太高
主要是前端页面相似或者完全相同,导致使用新增和修改一起的,否则基本就要创建提交的按钮(因为不是同一个Controller)
且基本只有id的变化(其他的基本上有多个默认值,如回显)
也更加的省略了修改的操作
其他的省略是没必要的,如回显id,可能其他地方也会用到他的方法,这是主要的,所以基本不会放一起
而修改基本只会在这一个地方,这也是可以放一起的原因
那么我们通常就会优先于在保存里使用修改,而不是回显或者其他操作,这样也就提高空间和时间(执行速度)
而正是因为操作了修改的判断id,其他的判断是非常困难的,所以基本不会放在一起,使得不好维护
实际上我们可以进行比较:
使用回显放在一起:少一个Controller,前端页面多个提交按钮(来时得到id,同一个参数,包含id存放)
使用修改放一起:少一个Controller,原按钮提交(来时得到id,同一个参数,包含id存放)
这时最小的回显操作,也是没有优势的
所以说主要是前端页面相似或者完全相同,导致使用新增和修改一起的
否则基本就要创建提交的按钮(因为不是同一个Controller),而使用修改和添加一起时的回显在前端只是修改名称
修改章节状态
需求分析:
需求:点击修改章节状态按钮,将当前章节信息的状态进行修改

在这里插入图片描述

Dao层:添加部分CourseContentMapper:
 /*
    修改章节状态
     */
    public void updateSectionStatus(CourseSection courseSection);
添加部分CourseContentMapper.xml:
<!--修改章节状态-->
    <update id="updateSectionStatus" parameterType="CourseSection">
        UPDATE course_section set
                                  status = #{status},
                                  update_time = #{updateTime}
        WHERE id = #{id}

    </update>
    <!--
    上面对应的大小WHERE(或者其他的大写)实际上是sql可视化界面测试的代码时
    自动大写的,而直接写在这里的是对应小写,当然大小写是忽略的
    -->
Service层:添加部分CourseContentService及其实现类 :
  /*
   修改章节状态
    */
    public void updateSectionStatus(int id,int status);
 /*
   修改章节状态
    */
    @Override
    public void updateSectionStatus(int id,int status) {

        //封装数据
        CourseSection courseSection = new CourseSection();
        courseSection.setId(id);
        courseSection.setStatus(status);
        courseSection.setUpdateTime(new Date());

        //调用mapper
        courseContentMapper.updateSectionStatus(courseSection);
    }
Web层:添加部分CourseContentController:
 /*
    修改章节状态
     */
    //很少的参数,一般就不需要创建对象了,节省一点创建对象所要的空间
    @RequestMapping("/updateSectionStatus")
    public ResponseResult updateSectionStatus(int id, int status){
        //调用service
        courseContentService.updateSectionStatus(id, status);

        Map<String, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("status", status);
        ResponseResult responseResult = new ResponseResult(true, 200, "修改章节状态成功", 
                                                           objectObjectHashMap);
        return responseResult;
    }
使用Postman测试接口
新建课时信息
通过上面的代码编写,若可以的话,可以不看后面的代码,自己进行一次新建课时信息的编写
需求分析:
需求:点击添加课时按钮,将弹出页面填写的课时信息保存到数据库中

在这里插入图片描述

Dao层:添加部分CourseContentMapper:
/*
    添加课时信息
     */
    public void saveLesson(CourseLesson courseLesson);
添加部分CourseContentMapper.xml:
 <!--添加课时信息-->
    <insert id="saveLesson" parameterType="CourseLesson">
        INSERT INTO course_lesson (
            course_id,
            section_id,
            theme,
            duration,
            is_free,
            order_num,
            create_time,
            update_time
        )VALUES(#{courseId},#{sectionId},#{theme},#{duration},#{isFree},
                #{orderNum},#{createTime},#{updateTime});

    </insert>
Service层:添加部分CourseContentService及其实现类 :
 /*
   添加课时信息
    */
    public void saveLesson(CourseLesson courseLesson);
   /*
   添加课时信息
    */
    @Override
    public void saveLesson(CourseLesson courseLesson) {
        Date date = new Date();
        courseLesson.setCreateTime(date);
        courseLesson.setUpdateTime(date);
        courseContentMapper.saveLesson(courseLesson);
    }
Web层:添加部分CourseContentController:
 /*
   添加或修改课时信息
   也是添加课时信息和对应修改课时信息放一起
   在后面进行修改扩展
   现在只操作新建课时信息
    */
    @RequestMapping("/saveOrUpdateLesson")
    public ResponseResult saveOrUpdateLesson(@RequestBody CourseLesson courseLesson){

        courseContentService.saveLesson(courseLesson);

        ResponseResult responseResult = new ResponseResult(true, 200, "添加课时成功", null);
        return responseResult;
    }
使用使用Postman测试接口
修改课时信息
需求分析:
点击确定按钮,将修改后的课时信息保存到数据库中(回显是前端操作的)
Dao层:添加部分CourseContentMapper:
/*
    修改课时信息
     */
    public void updateLesson(CourseLesson courseLesson);
添加部分CourseContentMapper.xml:
 <!--修改课时信息-->
    <update id="updateLesson" parameterType="CourseLesson">
        UPDATE course_lesson
        <trim prefix="SET" suffixOverrides=",">
            <if test="theme != null and theme != ''">
                theme = #{theme},
            </if>
            <if test="duration != null and duration != ''">
                duration = #{duration},
            </if>
            <if test="isFree != null and isFree != ''">
                is_free = #{isFree},
            </if>
            <if test="orderNum != null and orderNum != '' or orderNum == 0">
                order_num = #{orderNum},
            </if>
            <if test="updateTime != null">
                update_time=#{updateTime}
            </if>
        </trim>
        <where>
            <if test="id != null and id != '' ">id = #{id}</if>
        </where>
    </update>
Service层:添加部分CourseContentService及其实现类 :
/*
    修改课时信息
     */
    public void updateLesson(CourseLesson courseLesson);
 /*
   修改课时信息
    */
    @Override
    public void updateLesson(CourseLesson courseLesson) {
        courseLesson.setUpdateTime(new Date());
        courseContentMapper.updateLesson(courseLesson);
    }
Web层:修改部分CourseContentController(扩展saveOrUpdateLesson方法):
  @RequestMapping("/saveOrUpdateLesson")
    public ResponseResult saveOrUpdateLesson(@RequestBody CourseLesson courseLesson){
        ResponseResult responseResult;
        if(courseLesson.getId() == null) {
            courseContentService.saveLesson(courseLesson);

           responseResult = new ResponseResult(true, 200, "添加课时成功", null);
        }else{
            courseContentService.updateLesson(courseLesson);
           responseResult = new ResponseResult(true, 200, "修改课时成功", null);
        }
        return responseResult;
    }
使用使用Postman测试接口
到这里可以知道,前端传递的参数只是影响对应参数变量,最终的还是要看sql怎么编写的
如你传递100个参数,而sql没有使用对应的占位符操作或者替换操作,就是单纯的sql语句,如select * from 表名
那么这100个参数也就没有起作用了
修改课时状态:
需求分析:
需求:点击修改课时状态按钮,将当前课时信息的状态进行修改
Dao层:添加部分CourseContentMapper:
 /*
   修改课时状态
    */
    public void updateLessonStatus(CourseLesson courseLesson);
添加部分CourseContentMapper.xml:
 <!--修改课时状态-->
    <update id="updateLessonStatus" parameterType="CourseLesson">
        UPDATE course_lesson set
                                  status = #{status},
                                  update_time = #{updateTime}
        WHERE id = #{id}

    </update>
Service层:添加部分CourseContentService及其实现类 :
  /*
  修改课时状态
   */
    public void updateLessonStatus(CourseLesson courseLesson);
    //这里就进行一次不操作固定参数了,也就是int id,int status
//但最好不要这样(当然对应前台也会进行对应操作的)
    /*
  修改课时状态
   */
    @Override
    public void updateLessonStatus(CourseLesson courseLesson) {
        courseLesson.setUpdateTime(new Date());
        courseContentMapper.updateLessonStatus(courseLesson);
    }
Web层:添加部分CourseContentController:

      /*
  修改课时状态
   */
    @RequestMapping("/updateLessonStatus")
    public ResponseResult updateLessonStatus(@RequestBody CourseLesson courseLesson){
        courseContentService.updateLessonStatus(courseLesson);
        ResponseResult responseResult = new ResponseResult(true, 200, "修改课时状态成功", null);
        return responseResult;
    }
使用使用Postman测试接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值