Spring

面向企业开发基础

一 Maven

1 概要

  • 导入依赖
  • 项目结构
  • 项目后期 —> 测试 —> 打包 —> 部署 —> 多模块开发

2 作用

  • 解决依赖问题

  • 规范项目结构

  • 解决项目后期的打包部署等一些列工作

3 什么是Maven

Maven是使用java编写的开源项目管理工具 POM.xml Project Object Model

为什么需要Maven?

1.项目管理问题:项目中jar包资源越来越多,jar包的管理越来越沉重

2.繁琐:腰围每个项目手动导入所需的jar,需要搜集全部jar

3.复杂:项目中的jar如果需要版本升级,就需要再重新搜集jar

4.冗余:相同的jar在不同的项目中保存了多份

Maven解决了什么问题?

1.添加第三方jar包

2.jar包之间的依赖关系

3.处理jar包之间的冲突

4.获取第三方jar包

5.将项目拆分成多个工程模块

6.实现项目的分布式部署

4 其他构建技术

  • Ant 构建 – build.xml
  • Maven构建 – pom.xml
  • Gradle构建 – build.gradle

5 使用

  • 下载maven

  • 配置maven

    • localRepository
    <?xml version="1.0" encoding="UTF-8"?>
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
      <!-- 1 修改成你的路径 --> 
      <localRepository>D:\tools\repo</localRepository>
      <pluginGroups>
      </pluginGroups>
      <proxies>
      </proxies>
      <servers>
      <!-- 2  maven 私服 -->
      </servers>
      <!-- 3  maven 镜像 -->
      <mirrors>
        <mirror>
            <id>nexus-aliyun</id>
            <mirrorOf>central</mirrorOf>
            <name>Nexus aliyun</name>
            <url>https://maven.aliyun.com/repository/central</url> 
        </mirror>
      </mirrors>
      <profiles>
      </profiles>
    </settings>
    
  • 用户配置

    将配置号的settings.xml文件复制到用户下面的.m2目录下

二 Spring

IoC

IoC:Inversion of Control,控制反转,把创建对象的控制权交给第三方容器

如何将创建对象的控制权交个容器

1.导入依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.10</version>
    </dependency>
  </dependencies>

2.spring-context.xml

<bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">

3.从容器中获取创建的类,源码是使用xmlDOM操作通过id获取

public class iocTestMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
        UserDaoImp userDao = classPathXmlApplicationContext.getBean("userDao", UserDaoImp.class);
    }
}
//默认查找classpath路径下的文件
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml");
//多文件,也可传递数组
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml","spring/spring-ioc2.xml",.....);

//默认为项目工作路径 即项目的根目录 
FileSystemXmlApplicationContext applicationContext=
                new FileSystemXmlApplicationContext("/src/main/resources/spring/spring-ioc.xml");

//也可以读取classpath下的文件
FileSystemXmlApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:spring/spring-ioc.xml");

//使用前缀file 表示的是文件的绝对路径 
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("file:D:/app.spring.xml"); 
//多文件与ClassPathXmlApplicationContext相同

相关源码

if (beanProperty.element("map") != null) {   
                    Map<String, Object> propertiesMap = new HashMap<String, Object>();   
                    Element propertiesListMap = (Element) beanProperty   
                            .elements().get(0);   
                    Iterator<?> propertiesIterator = propertiesListMap   
                            .elements().iterator();   
                    while (propertiesIterator.hasNext()) {   
                        Element vet = (Element) propertiesIterator.next();   
                        if (vet.getName().equals("entry")) {   
                            String key = vet.attributeValue("key");   
                            Iterator<?> valuesIterator = vet.elements()   
                                    .iterator();   
                            while (valuesIterator.hasNext()) {   
                                Element value = (Element) valuesIterator.next();   
                                if (value.getName().equals("value")) {   
                                    propertiesMap.put(key, value.getText());   
                                }   
                                if (value.getName().equals("ref")) {   
                                    propertiesMap.put(key, new String[] { value   
                                            .attributeValue("bean") });   
                                }   
                            }   
                        }   
                    }   
                    bean.getProperties().put(name, propertiesMap);   
                }

public static Object newInstance(String className) {
		Class<?> cls = null;
		Object obj = null;
		try {
			cls = Class.forName(className);
			obj = cls.newInstance();
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
		return obj;

}

DI

DI:Dependency Injection,依赖注入

如何注入?

1.<!--set方法注入
        使用的标签:property
        标签出现的位置:bean标签的内部
        标签中的属性
            name:指定注入时所调用的set方法名称
            ===================
            value:用于提供基本类型和String类型的数据
            ref:用于指定其他的bean类数据,在spring的IOC核心容器中出现过的bean对象
        优势:
            创建对象时没有明确的限制,可以直接使用构造函数
        弊端:
            如果某个成员必须有值,有可能set方法没有执行赋值
        -->
2.<!--构造函数注入:
        使用的标签:constructor-arg
        标签出现的位置:bean标签的内部
        标签中的属性
            type:指定要注入的数据的数据类型,该数据类型也是构造函数中的某个参数的类型
            index:指定要定索引位置,索引的位置从0开始
            name:指定参数名称
            =========以上用于指定给构造函数中的哪个参数赋值==========
            value:用于提供基本类型和String类型的数据
            ref:用于指定其他的bean类数据,在spring的IOC核心容器中出现过的bean对象
        优势:
            在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
        弊端:
            创建对象时,如果用不到这些数据,也必须提供
    -->
<bean id="accountService" class="com.qf.dao.service.impl.AccountServiceImpl"
          scope="prototype">
        <constructor-arg name="name" value="lisi"></constructor-arg>
        <constructor-arg name="age" value="23"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

1.spring-context.xml IoC容器

<bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">

</bean>
<bean id="userService" class="com.qf.dao.service.impl.UserServiceImp">
        <!-- 注入userDaoImp对象,需要set方法--><!--set注入关键词-->
	<property name="userDaoImp" ref="userDao"></property>
</bean>

2.userService声明中多出了一个property的标签,这个标签指向了我们刚才创建的userDao对象,它的作用是把userDao对象传递给userService实现类中的userDaoImp属性,该属性必须拥有set方法才能注入成功,我们把这种往类userService对象中注入其他对象(userDaoImp)的操作称为依赖注入,其中的id或者name必须与userService实现类中变量名称相同,到此我们就完成对需要创建的对象声明。

通过源码得知,对于非集合类型的属性,底层代码大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值,所以说为什么要在类里面设置set方法。

public class UserServiceImp implements UserService {
    //userDaoImp为注入的对象
    private UserDaoImp userDaoImp;

    //注入userDaoImp对象,需要set方法
    public void setUserDaoImp(UserDaoImp userDaoImp) {
        this.userDaoImp = userDaoImp;
    }

    @Override
    public Integer login() {
        return userDaoImp.insert();
    }
}

注册对象

1.xml

2.注解

3.Java Config

1.xml

2.导入依赖

spring-context
<?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:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-4.0.xsd
">

    <bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">

</bean>
    <bean id="userService" class="com.qf.dao.service.impl.UserServiceImp">
        <!-- 注入userDaoImp对象,需要set方法-->
<property name="userDaoImp" ref="userDao"></property>
    </bean>

    <!--作用域 prototype 每次从容器中获取的对象都是新的对象  singleton 单例对象 默认值 每次都是相同的对象-->
    <bean id="test" class="com.qf.dao.pojo.TestScope" scope="prototype"></bean>
    <!--<bean id="test" class="com.qf.dao.pojo.TestScope" scope="singleton"></bean>
    结果:test1 = com.qf.dao.pojo.TestScope@5ec0a365
         test2 = com.qf.dao.pojo.TestScope@5ec0a365
    <bean id="test" class="com.qf.dao.pojo.TestScope" scope="prototype"></bean>
    结果: test1 = com.qf.dao.pojo.TestScope@4fe3c938
          test2 = com.qf.dao.pojo.TestScope@5383967b
    -->


    <bean id="life" class="com.qf.dao.pojo.TestLife" init-method="init" destroy-method="destroy"></bean>

    <bean id="pojo" class="com.qf.dao.pojo.TestPojo">
        <property name="list" >
            <list>
                <value>2</value>
                <value>22</value>
                <value>5</value>
            </list>
        </property>
        <property name="map">
            <map>
                <entry key="name" value="张三"></entry>
            </map>
        </property>
        <property name="name" value="小明"></property>
        <property name="age" value="12"></property>
    </bean>

    <bean id="pojo1" class="com.qf.dao.pojo.TestPojo"
          p:age="12"
          p:name="李四"
          p:list-ref="list"
          p:map-ref="map"
    />
    <util:list id="list">
        <value>12</value>
        <value>23</value>
    </util:list>
    <util:map id="map">
        <entry key="name" value="王五"></entry>
    </util:map>
</beans>
public class iocTestMain2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
        TestPojo pojo = classPathXmlApplicationContext.getBean("pojo", TestPojo.class);
        System.out.println("pojo = " + pojo);
    }
}
//结果:
//初始化
//pojo = TestPojo{list=[2, 22, 5], map={name=张三}, name='小明', age=12}
public class iocTestMain2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
        TestLife life = classPathXmlApplicationContext.getBean("life", TestLife.class);
        classPathXmlApplicationContext.close();
    }
}
//结果:
//初始化
//销毁
public class TestLife {
    public TestLife() {
    }

    public void init(){
        System.out.println("初始化");
    }

    public void destroy(){
        System.out.println("销毁");
    }
}

属性

init-method
<!--init-method="init" 初始化连接-->
<bean id="life" class="com.smart.pojo.TestLife"
          init-method="init"
/>
destroy-method
<!--destroy-method="destroy" 容器关闭则执行-->
<bean id="life" class="com.smart.pojo.TestLife" destroy-method="destroy"/>
id

声明对象的在容器中的键

class

在容器需要创建对象的类

scope

对象的作用域

  1. singleton 单例对象 默认值
  2. prototype 每次从容器中获取对象都是新创建对象
  3. request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境
  4. sesison 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境
  5. globalSession 为每个全局的HTTP会话创建一个实例

初始化赋值

  • 通过constructor-arg
  • 通过property子标签

简写方式

  • p 表示 property的缩写
xmlns:p="http://www.springframework.org/schema/p"
  • util 声明 list map array
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
">

2.注解

spring-ioc-注解

  • 必须开启注解支持,在xml文件中使用 context:component-scan
<!--base-package指定了路径,然后就扫描遍历该路径下面所有的.class文件,通过反射可以拿到一个个对象,怎么知道要把哪个对象注入容器呢,关键就在哪个类上面有注解就把哪个类注入容器,如下面的UserServiceImp类上面有@service注解,就把UserServiceImp注入容器-->
xmlns:context="http://www.springframework.org/schema/context"
<context:component-scan base-package="根包名"/>
<!--例如-->
<context:component-scan base-package="com.qf.dao.controller"/>

使用原则

1.能使用注解尽量使用注解

2.第三方框架 xml方式

核心注解

注册到容器

  1. @Component 通用注解,当一个类需要注入的容器中,没有特定的语义可以使用该注解
  2. @Service 在业务层使用
  3. @Repository 使用在数据层
  4. @Controller 在控制层
public class iocTestMain2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context1.xml");
        UserService userServiceImp = classPathXmlApplicationContext.getBean("userServiceImp", UserService.class);
        System.out.println("userServiceImp = " + userServiceImp);
    }
}

//如果括号里面内容为空,默认名字为类名的首字母小写,即userServiceImp,特殊情况为如果类的名字开头几个字母都为大写,则原封不动的命名,如ABUserServiceImp
@Service()
public class UserServiceImp implements UserService {
    private UserDaoImp userDaoImp;

    public void setUserDaoImp(UserDaoImp userDaoImp) {
        this.userDaoImp = userDaoImp;
    }

    @Override
    public Integer login() {
        return userDaoImp.insert();
    }
}

依赖注入

  • @Autowired
  • @Resource
  1. 构造方法注入
  2. setter方法注入
  3. 属性注入
    3.1 Autowired spring 自带的注解
  • Autowired 通过类型去查找依赖对象,如果类型不存在或者存在多个类型直接报错

3.2 Resource jsr250定义的

  • 在不使用任何属性的情况下 先根据名字查找对象(属性的名字) 如果通过属性名找不到依赖对象直接通过类型查找,如果类型不存在或者存在多个类型直接报错

  • 如果指定了name属性的值 只会通过name属性来查找对象,如果找不到直接抛出异常

  • 如果指定了type属性的值 只会通过类型去查找对象,如果查找类型不存在或者存在多个 直接抛出异常
    3.3 Inject jsr330定义的

@Service()
public class UserServiceImp implements UserService {
    @Autowired
    @Resource
    private UserDaoImp userDaoImp;

    public void setUserDaoImp(UserDaoImp userDaoImp) {
        this.userDaoImp = userDaoImp;
    }

    @Override
    public Integer login() {
        return userDaoImp.insert();
    }
}

其他注解

@Qualifier

配合Autowired使用,通过名字来查找依赖对象

@Primary

如果容器中存在多个实现类,可以使用@Primary注解来提高优先级

Java Config

/** 
 * 在配置类上使用注解 @Configuration
 * 在类中定义方法
 * 1 必须是public  声明
 * 2 必须有返回值   
 * 3 必须在方法上面使用@Bean 注解
 */
@Configuration
public class DruidConfig {
    @Bean
    public DataSource dataSource() throws Exception {
        HashMap<String, String> map = new HashMap<>();
        map.put("username", "root");
        map.put("password", "root");
        map.put("url", "");
        return DruidDataSourceFactory.createDataSource(map);
    }
}

三 spring-MVC

Spring的web模型 - 视图 - 控制器(MVC)

  • 前后端分离

    1. 浏览器 —>发送请求—> 请求前端xxx.html界面 —>浏览器解析–>js 图片 ajax–>请求服务(ajax)–>请求服务接口
    1. 后台 --> 控制层 —>业务层 —>数据层 -->数据库
  • 响应数据 --> json

    1. 前端—> 接收解析json -> DOM操作—>展示数据
  • 前后端不分离

    1. 浏览器—> 请求后台路径 (http://localhost:8080/anno/body) —> 控制层 —>业务层---->数据层—>数据库

      浏览器发送请求->DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)->业务层->数据层->数据库

  • 渲染 xxx.jsp + 数据 – > xxx.html

Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型。

前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类)(“普通Java对象”(Plain Old Java Object)的定义,简称POJO)

在Spring的Web MVC框架提供了模型 - 视图 - 控制器架构以及可用于开发灵活,松散耦合的Web应用程序准备的组件。 MVC模式会导致分离的应用程序(输入逻辑,业务逻辑和UI逻辑)的不同方面,同时提供这些元素之间的松耦合。

模型(Model )封装了应用程序的数据和一般他们会组成的POJO。

视图(View)是负责呈现模型数据和一般它生成的HTML输出,客户端的浏览器能够解释。

控制器(Controller )负责处理用户的请求,并建立适当的模型,并把它传递给视图渲染。

DispatcherServlet

Spring的web模型 - 视图 - 控制器(MVC)框架是围绕着处理所有的HTTP请求和响应的DispatcherServlet的设计。 Spring的Web MVC框架的DispatcherServlet的请求处理流程说明如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btHo3ryM-1653495489176)(E:\培训\第二阶段\总结\pictrue\mvc.webp)]

下面是对应于传入的HTTP请求到DispatcherServlet的事件序列:

1.接收HTTP请求后,DispatcherServlet 咨询 HandlerMapping 来调用相应的控制器。DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)

2.该控制器接受请求并调用基于使用GET或POST方法相应的服务方法。服务方法将基于定义的业务逻辑设置模型数据,并返回视图名到DispatcherServlet。

3.DispatcherServlet将需要帮助的ViewResolver从拾取到该请求所定义的视图。

4.一旦视图被敲定,DispatcherServlet会传递模型数据是在浏览器上最终呈现的视图。

所有上述部件,即HandlerMapping,控制器和视图解析器WebApplicationContext 部分是纯的 ApplicationContext 必要的 Web应用程序的一些额外的功能扩展。

作用

Spring 进行web开发

对Servlet进行封装简化

如何使用

1 导入依赖
<!--导入spring-mvc框架-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.9</version>
</dependency>
2 配置文件

spring-context.xml

  • context:component-scan
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启spring对注解的支持 -->
    <context:component-scan base-package="com.smart.mvc"/>
</beans>

spring-mvc.xml

  • context:component-scan
  • mvc:annotation-driven
<?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:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd 		   http://www.springframework.org/schema/context 		    			   https://www.springframework.org/schema/context/spring-context.xs	    	   http://www.springframework.org/schema/mvc 	
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- Spring MVC的注解扫描功能,允许做出像 @Controller和使用@RequestMapping注解等使用-->
    <context:component-scan base-package="com.smart.mvc.controller"/>
    <!-- 开启webmvc注解支持-->
    <mvc:annotation-driven/>

</beans>

web.xml

  • Spring其他的配置文件初始化
  • 注册DispatchServlet
    • 初始化配置文件
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<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">

    <display-name>Archetype Created Web Application</display-name>
    
    <!--listener用来 把配置文件交给spring加载-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!--servlet启动之后加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring-context.xml</param-value>
    </context-param>
    
 <!--给servlet命名-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        
         <!--servlet启动之前加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    
    <!--给servlet作映射,拦截浏览器请求的所有路径-->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

控制层

@Controller
public class UserController {
    @RequestMapping("/test")
    @ResponseBody
    public String test() {
        return "test";
    }
}                 

@RequestMapping

作用

路径映射

可以使用在类上面,也可以在方法上使用

用在方法上可以限制请求方式

源代码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
	String name() default "";
	@AliasFor("path")
	String[] value() default {};//映射的路径
	@AliasFor("value")
	String[] path() default {};//映射的路径
	RequestMethod[] method() default {};//浏览器的请求方式
	
    String[] params() default {};//表示请求的参数中必须包含指定的键值,如param={"id=1"},表示请求中必须有id=1才会接收
	String[] headers() default {};//表示请求头必须包含指定的键值,如header={"token=123"},表示请求中必须有token=123才会接收
	String[] consumes() default {};//处理指定http的提交内容,编码格式(Content-Type)
	String[] produces() default {};//请求头中Accept类型中必须包含指定的值,如produces={"application/json"},请求头中Accept类型中必须包含application/json才会接收
}

请求方式:

  1. get
  2. post
  3. put
  4. delete
  5. head

了解:

  1. patch
  2. trace
  3. options

属性详解

属性说明
value/path(重点)表示映射的路径
method(重点)限制客户端的请求方式
consumes(了解)处置指定的提交内容(Content-Type) text/html image/* application/json
params(了解)表示请求的参数中必须包含指定的键值
headers(掌握)表示请求头必须包含指定的键值
produces(了解)请求头中Accept类型中必须必须包含指定的值

补充

路径映射支持通配符 ANT风格

  1. ? 配置路径中的单个字母
  2. * 匹配一级路径
  3. ** 匹配多级路径

例子

/user/*   匹配一级路径 例如:user/list  /user/info  成功  一级路径以上就会失败:user/info/detail  失败

/user/**  匹配多级路径:ser/list  /user/info    user/info/detail   成功  

扩展注解

请求方式的简化

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

二 请求参数处理

  1. 普通参数
  2. 参数注解

普通参数

QO:query object 查询对象

PO:entity 持久化对象

  1. 基本类型的参数(掌握)
  2. 对象(掌握)
  3. 复杂对象
  4. 集合类型(不支持集合直接作为请求参数,需要配合注解使用)
  5. map
  6. 数组
  7. json数据(掌握)
  8. 内置的对象类型

参数注解

必须掌握
  1. @ReqeustParam
  2. @ReqeustBody
  3. @PathVariable

其他注解

  1. @ReqeustHeader
  2. @CookieValue
  3. @SessionAttribute
  4. @ModelAttribute

@ReqeustParam

//@RequestParam:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

@Controller
@RequestMapping("hello")
public class HelloController2 {
/**
 * 接收普通请求参数
 * http://localhost:8080/hello/show16?name=linuxsir
 * url参数中的name必须要和@RequestParam("name")一致
 * @return
 */
@RequestMapping("show16")
public ModelAndView test16(@RequestParam("name")String name){
    ModelAndView mv = new ModelAndView();
    mv.setViewName("hello2");
    mv.addObject("msg", "接收普通的请求参数:" + name);
    return mv;
}
 
/**
 * 接收普通请求参数
 * http://localhost:8080/hello/show17
 * url中没有name参数不会报错、有就显示出来
 * @return
 */
@RequestMapping("show17")
public ModelAndView test17(@RequestParam(value="name",required=false)String name){
    ModelAndView mv = new ModelAndView();
    mv.setViewName("hello2");
    mv.addObject("msg", "接收普通请求参数:" + name);
    return mv;
}
 
/**
 * 接收普通请求参数
 * http://localhost:8080/hello/show18?name=998 显示为998
 * http://localhost:8080/hello/show18?name 显示为hello
 * @return
 */
@RequestMapping("show18")
public ModelAndView test18(@RequestParam(value="name",required=true,defaultValue="hello")String name){
    ModelAndView mv = new ModelAndView();
    mv.setViewName("hello2");
    mv.addObject("msg", "接收普通请求参数:" + name);
    return mv;
}

批量删除

@GetMapping("/del")
public String del(@RequestParam List<Integer> ids) {
    ids.forEach(System.out::println);
    return "list作为参数";
}

作用

  1. 给参数起别名
  2. 默认情况下是必要参数
  3. 给参数设置默认值,当设置了默认值,必要参数就会设置成false

源码

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
	@AliasFor("name")
	String value() default "";//给参数起别名
	@AliasFor("value")
	String name() default "";
	boolean required() default true;//是否是必要参数,默认是必要参数
	String defaultValue() default ValueConstants.DEFAULT_NONE;//给参数设置默认值
}

属性说明

属性说明
defaultValue给参数设置默认值
value给参数起别名
required是否是必要参数,默认是必要参数

@ReqeustBody

作用

获取请求体中的数据,只能用POST请求和PUT请求

应用场景 通常情况下接收客户端的json数据自动转化对象

@ResponseBody 通常情况下服务端发送的对象自动转化成json数据

源码

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
	boolean required() default true;
}

例子

@Data
public class UserReqeustParams {
    @JSONField(name = "user_name")
    private String username;
    private int page;
    private int size;
    @JSONField
    private String password;
    private String phone;
    @JSONField(deserialize = false)
    private List<String> addressList;
    @JSONField(format = "yyyy-MM-dd")
    private Date createData;
    @JSONField(serialize = false)
    private boolean status;
}
   @PostMapping("/body")
    @ResponseBody
    public String body(@RequestBody UserReqeustParams user) {
        System.out.println(user.toString());
        return "接受客服端的json数据";
    }

参数

{
  "user_name": "admin",
  "page": 1,
  "size": 10,
  "password": "123456",
  "phone": "123456",
  "addressList": [1,2,3,4],
  "createData": "2021-09-24 15:39:25",
  "status": true
}

集成fastjson

导入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>
配置
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <!--解决IE浏览器将json数据当做文件来下载-->
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
            <property name="fastJsonConfig">
                <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
                    <!--指定日期的全局通用格式-->
                    <property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" />
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface JSONField {
	// 设置别名
    String name() default "";
    //设置日期格式
    String format() default "";
    //  是否序列化,序列化就是对象转json格式
    boolean serialize() default true;
    // 是否反序列化,反序列化就是json转对象
    boolean deserialize() default true;
}
@Data
public class UserReqeustParams {
    //fastjson传过来的名字为user_name,就需要设置别名
    @JSONField(name = "user_name")
    private String username;
    
    private int page;
    private int size;
    
    @JSONField
    private String password;
    private String phone;
    
    @JSONField(deserialize = false)
    private List<String> addressList;
    
    //设置日期格式
    @JSONField(format = "yyyy-MM-dd")
    private Date createData;
    
    @JSONField(serialize = false)
    private boolean status;
}

@PathVariable

作用

从请求路径中获取参数信息

注意:

RESTFul风格

  • 只能声明一些简单的 参数 例如 id page size 等 经常使用在get delete 请求

源代码

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
	@AliasFor("name")
	String value() default "";
	@AliasFor("value")
	String name() default "";
	boolean required() default true;
}

属性说明

属性说明
value/name给参数起别名
required是否是必要参数, 默认必要参数
例子
	// product/1
    @GetMapping("/product/{pid}")
    @ResponseBody
    public String body(@PathVariable("pid") Long id) {
        return "接受客服端的json数据";
    }
	
	
    //  list/2/20
    @GetMapping("/list/{page}/{size}")
    @ResponseBody
    public String list(@PathVariable int page, @PathVariable int size) {
        return "接受客服端的json数据";
    }

三 响应内容

概要

  1. 前后分离
  2. 前后端不分离
  • 前后端分离

    1. 浏览器—发送请求— 请求前端xxx.html界面 —>浏览器解析–js 图片 ajax-- 请求服务(ajax)–请求服务接口
    1. 后台 --> 控制层 —>业务层 —>数据层 -->数据库
  • 响应数据 --> json

    1. 前端—> 接收解析json -> DOM操作—>展示数据
  • 前后端不分离

    1. 浏览器-> 请求后台路径 (http://localhost:8080/anno/body) —> 控制层 —>业务层---->数据层—>数据库

      浏览器发送请求->DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)->业务层->数据层->数据库

  • 渲染 xxx.jsp + 数据 – > xxx.html

分离

  1. 核心注解 @RestController = @Controller + @ResponseBody
  2. 在类上面使用 @RestController
  3. 方法的返回值可以是任意对象 不需要进行json转化

注意:

  • 依赖于第三方json框架 fastjson jackson gson

不分离

  1. 可以返回字符串 也可以是ModelAndView对象
  2. 转化跟重定向
    1. 转发 forward:站内路径
    2. 重定向 redirect:“站内路径|站外路径”

注意:

  • 配置视图解析器

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/views/"
          p:suffix=".jsp"
          />
    <!-- 配置静态资源不拦截-->
    <mvc:default-servlet-handler />
    
    

Mybatis

一 概要

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

二 传统JDBC开发的缺点

  1. 频繁的建立连接释放资源 可以复用连接对象,提供性能 (连接池)

  2. SQL语句跟Java代码耦合在一起

    • 需求一旦发生改变,维护起来比较困难
    • 可读性比较差

    解决方案

    • 将SQL语句跟Java代码分离
  3. JDBC参数问题 存在一定的局限性

    1. 传递参数的顺序
    2. 条件判断

    解决方案

    1. 对参数进行封装,不需要关注参数的传递顺序
    2. 动态SQL
  4. 结果集处理

    1. 从rs对象获取值,在封装成对象

    解决方案

    1. 对结果集的处理进行统一的封装 (将数据的结果集转化成对象)

备注

ORM 是 (Object Relation Mapping)

用户面向对象的方式操作数据库

  • 类名 —> 表名
  • 属性 —> 列名
  • 类型 ---- 类型
  • 对象 ----> 记录

当列名跟属性,表名跟类命名不一致的情况下该怎么办?

  • 注解
  • ResultMap

三 基础使用

1 导入依赖

<dependencies>
    	
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    	
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
    
</dependencies>

2 核心配置文件

<?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>
    <!--    开发环境  生产环境  测试环境 -->
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="url" value="jdbc:mysql://114.215.196.38:3306/db_mybatis"/>
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

3 定义mapper接口

/**
 * 1> 注意接口中的所有方法的命名全部采用SQL的关键句开头
 */
public interface UserMapper {
    /**
     * 查询所有的用户信息
     *
     * @return
     */
    List<User> selectList();
	/**
	 *  保存用户
	 */
    int insert(User user);
}

4 定义mapper.xml文件

  • insert标签

  • 参数处理

    • 在xml文件中parameterType属性
    • 在接口中使用注解 @Param
  • 在mapper,xml文件中如何使用参数

    #{变量}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mybatis.mapper.UserMapper">
    <insert id="insert" parameterType="com.smart.mybatis.entity.User">
        INSERT INTO t_user(username, password, phone)
        VALUES (#{user.username}, #{user.password}, #{user.phone})
    </insert>
</mapper>

5 在核心配置文件中注册

<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

6 测试代码

public class TestMain {
    /**
     * @param args
     */
    public static void main(String[] args) {
        InputStream resourceAsStream = TestMain.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession session = ssf.openSession(true);
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("admin");
        user.setPassword("123456");
        user.setPhone("123");
        mapper.insert(user);
    }
}

SSM整合

SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)

Spring IoC 配置

Spring mvc 配置

Spring-mybatis 配置

一 技术栈

  1. spring
  2. springmvc
  3. mybatis
  4. druid
  5. jackson/fastjson

二 SS整合

1 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>

2 配置文件

spring-context.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.smart.ssm"/>
</beans>

spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.smart.ssm.controller"/>
    <mvc:annotation-driven/>
</beans>

三 SM整合

1 导入相关依赖

<!--  1 驱动   2 数据库连接池 Druid   3 Mybatis -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

2 配置文件

所有的配置文件最终都会放到同一个容器里面,就像很多独立的章节,最终都会变成一篇论文,可以相互连接的

  • 注册SQLSessionFactory对象
    • 扫描所有的mapper.xml文件
    • 配置数据库连接池
  • 注册扫描所有的接口
    • MapperScannerConfigurer
  • 连接池的配置

UserMapper.xml

所有数据层的接口实现类用xxMapper.xml文件来写

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.ssm.mapper.UserMapper">

    <insert id="insert" parameterType="User">
        insert into t_user(username,password) values (#{user.username},#{user.password})
    </insert>
</mapper>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
</configuration>

spring-mybatis.xml

注册对象有三种方式,xml、注解、java config,由于mybatis是第三方框架,使用注解难以注册,所以使用xml方式,mybatis就是对传统的JDBC进行封装2,所以就需要数据库连接池、加载mapper.xml文件里面的sql语句,结果集等等

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <!-- 1 (必要属性配置) 注册所有的mapper.xml文件-->
        <property name="mapperLocations" value="classpath*:/mapper/**/*.xml"/>
       
        <!-- 2 (必要配置) 数据库连接池 -->
        <property name="dataSource" ref="dataSource"/>
        
        <!-- 3  配置 类别名 
如果在mapper.xml中有parameterType这个属性,则配置了下面这个属性就不需要在parameterType这个属性内容加前缀com.smart.ssm.entity
<insert id="insert" parameterType="User">
    </insert>-->
        
        <property name="typeAliasesPackage" value="com.smart.ssm.entity"/>
        
        
        
        <!--  4   配置加载核心配置文件-->
        
        <!--        <property name="configLocation" value="classpath:mybatis-config.xml"/> 该方式需要独立创建一个mybatis-config.xml文件 文件内容为
	<settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>,和下面的一起两种方式,开发中这一种方式比较多-->
        
        <!-- 用来修改mybatis框架里面默认的一些配置-->
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="lazyLoadingEnabled" value="true"/>
            </bean>
        </property>
    </bean>
    
    <!--没用mybatis框架之前的做法:Dao接口   Dao实现类  @Repository   -->
    <!-- 使用框架之后:把所有的接口注册到容器里-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <!--把上面的bean注册到容器中,id为sqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        
        <!--注册 mapper接口-->
        <property name="basePackage" value="com.smart.ssm.mapper"/>
    </bean>
</beans>

spring-durid.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="maxActive" value="20"/>
        <property name="url" value="${jdbc_url}"/>
        <property name="username" value="${jdbc_user}"/>
        <property name="password" value="${jdbc_password}"/>
        <property name="filters" value="stat"/>
        <property name="initialSize" value="1"/>
        <property name="maxWait" value="6000"/>
        <property name="minIdle" value="1"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxOpenPreparedStatements" value="20"/>
        <property name="asyncInit" value="true"/>
    </bean>
</beans>

常见错误

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.mybatis.spring.mapper.MapperScannerConfigurer#0' defined in file [D:\work\IdeaProjects\2105\ssm-example\target\ssm-exmaple\WEB-INF\classes\spring-mybatis.xml]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/dao/support/DaoSupport

解决:导入spring-orm依赖

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>5.3.9</version>
</dependency>

四 web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<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">
    <display-name>Archetype Created Web Application</display-name>
    
    <!--listener用来 把配置文件交给spring加载-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
     <!--servlet启动之后加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-*.xml</param-value>
    </context-param>
    
    <!--给servlet命名-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--servlet启动之前加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    
      <!--给servlet作映射,拦截浏览器请求的所有路径-->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

XML映射器

一 概要

  1. mapper.xml文件(标签)

    1. 增删改

    2. 多表查询(join)

    3. 结果集标签

  2. 动态SQL

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

核心顶级标签

  • insert – 映射插入语句。

  • update – 映射更新语句。

  • delete – 映射删除语句。

  • select – 映射查询语句。

  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。

其他

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • sql – 可被其它语句引用的可重用语句块。

二 查询(单表)

通过select标签来实现

常用属性

属性说明
id对应接口的名称
parameterType推荐使用@Param来传递参数 (开发中能不用就不用)
resultType返回查询时候指定的类型(开发中建议不要使用)
resultMap配合resultMap标签使用 跟resultType属性只能选其一

例子

1 mapper接口
public interface UserMapper {
    /**
     * 返回单个对象
     */
    User selectById(@Param("uid") Long uid);
    /**
     * 分页查询
     */
    List<User> selectList(@Param("limit") int limit, @Param("offset") int offset);
}

2 mapper映射
<!--其中 selectById为接口中对应的方法,resultMap="BaseResultMap"为查询后映射到结果集的id,要告诉它映射到哪一个结果集-->    
<select id="selectById" resultMap="BaseResultMap">
        SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
        FROM t_user u
        WHERE u.uid = #{uid}
    </select>
    <!--    -->
    <select id="selectList" resultMap="BaseResultMap">
        SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
        FROM t_user u
        WHERE status = 1
        LIMIT #{limit} , #{offset}
    </select>
3 resultMap
<!--id="BaseResultMap"为查询后mapper映射的结果集id,告诉它是映射到这个结果集,type="User"为映射结果集的类,即结果集中结果集的对象类型-->
<resultMap id="BaseResultMap" type="User" extends="ResultMap1">
    <!--        主键的映射-->
    <result property="status" column="status"/>
    <result property="createDate" column="create_date"/>
</resultMap>

<resultMap id="ResultMap1" type="User">
    <!--        主键的映射-->
    <id property="uid" column="uid"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="phone" column="phone"/>
</resultMap>

4 单元测试
@SpringJUnitConfig(
        locations =
                {"classpath:spring-context.xml",
                        "classpath:spring-mvc.xml",
                        "classpath:spring-mybatis.xml",
                        "classpath:spring-druid.xml"})
public class UserMapperTest {
    @Resource
    UserMapper userMapper;

    @Test
    void selectById() {
        User user = userMapper.selectById(1L);
        System.out.println(user.toString());
    }

    @Test
    void selectList() {
        List<User> users = userMapper.selectList(0, 10);
        users.forEach(user -> System.out.println(user.getUsername()));
    }
}

三 resultMap

概要

定义结果集映射

  1. 属性跟列名映射

属性

属性说明
id唯一标识
typeEntity类型 映射结果集的类型
extends继承其他的resultMap

子标签

标签名说明
id定义主键
result定义普通的字段与属性的对应关系

四 增删改

@Builder

@Builder,有@Builder要添加@NoArgsConstructor和@AllArgsConstructor,否则有可能映射不到构造方法

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    /**
     *
     */
    private Long uid;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 手机号
     */
    private String phone;
}
public class UserMapperTest {
    @Resource
    UserMapper userMapper;

    @Test
    void insert() {
        User user = User.builder()
                .username("test11111")
                .password("123456")
                .phone("123132132")
                .build();
        userMapper.insert(user);
        System.out.println(user.getUid());

    }
}
BeanUtils.copyProperties

使用BeanUtils.copyProperties就不需要其他注解了

@Data
public class Access {
    /**
     * 门禁id
     */
    private Long accessId;

    /**
     * 门禁编码
     */
    private String accessNum;

    /**
     * 门禁名称
     */
    private String accessName;

    /**
     * 门禁类型
     */
    private String accessType;

    /**
     * 门禁位置
     */
    private String accessSite;

    /**
     * 门禁方向
     */
    private String direction;

    /**
     * 创建时间
     */
    private Date createDate;

    /**
     * 描述
     */
    private String description;

    /**
     * 是否删除 1正常 0删除
     */
    private Boolean status;
}

使用BeanUtils.copyProperties(param,access)赋值

@Service
public class AccessServiceImpl implements AccessService {
    @Resource
    private AccessMapper accessMapper;

    /**
     *
     * @param param
     *@return java.lang.Boolean
     */
    @Override
    public Boolean insert(AccessRequestParam param) {
        try {
            Access access = new Access();
            BeanUtils.copyProperties(param,access);
            Integer insert = accessMapper.insert(access);
            return insert>0;
        } catch (BeansException e) {
            throw new ServiceException(ResponseCode.SYS_ERROR);
        }
    }
    
    @Override
    public Boolean update(AccessRequestParam param) {
        try {
            Access access = new Access();
            BeanUtils.copyProperties(param,access);
            Integer update = accessMapper.update(access);
            return update>0;
        } catch (BeansException e) {
            throw new ServiceException(ResponseCode.SYS_ERROR);
        }
    }
}

insert

作用

用户往数据中插入数据

属性
属性说明
id对应接口方法的名字,如id=“insert”
useGeneratedKeys是否获取数据库自增主键,取值为true或false,如useGeneratedKeys=“true”
keyProperty赋值主键值的字段,如keyProperty=“user.uid”
例子

mapper接口

public interface UserMapper {
    int insert(@Param("user") User user);
}

mapper.xml

<insert id="insert">
    INSERT INTO t_user (username, password, phone)
    VALUES (#{user.username}, #{user.password}, #{user.phone})
</insert>
备注:
  • 获取保存数据的主键的值的时候

例子

mapper接口

 int insert(@Param("user") User user);

mapper.xml

   <insert id="insert" keyProperty="user.uid" useGeneratedKeys="true">
        INSERT INTO t_user (username, password, phone)
        VALUES (#{user.username}, #{user.password}, #{user.phone})
    </insert>

单元测试

@Test
void insert() {
    User user = User.builder()
        .username("test11111")
        .password("123456")
        .phone("123132132")
        .build();
    userMapper.insert(user);
    System.out.println(user.getUid());
}

update/delete

作用

更新和删除数据

属性
属性说明
id对应接口方法的名字

mapper接口

/**
*
* @param uid
* @return
*/
int delete(@Param("id") Long uid);

/**
*     数据库核心
*/
int update(@Param("user") User user);

mapper.xml

<update id="update">
    UPDATE t_user u
    SET u.username=#{user.username},
    u.password=#{user.password},
    u.phone= #{user.phone},
    u.status  = #{user.status}
    WHERE u.uid = #{user.uid}
</update>

<delete id="delete">
    UPDATE t_user u
    SET u.status = 0
    WHERE u.uid = #{id}
</delete>

测试代码

@Test
void delete() {
    int delete = userMapper.delete(13L);
    if (delete > 0) {
        System.out.println("删除成功");
    } else {
        System.out.println("删除失败");
    }
}

五 单元测试junit5

导入依赖

<!--   单元测试核心依赖 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.9</version>
    <scope>test</scope>
</dependency>
<!--单元测试时测试控制层需要这个依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

测试数据层

public interface UserMapper {
    /**
     * 返回单个对象
     */
    User selectById(@Param("uid") Long uid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="User" extends="ResultMap1">
        <!--        主键的映射-->
        <result property="status" column="status"/>
        <result property="createDate" column="create_date"/>
    </resultMap>
	
    <resultMap id="ResultMap1" type="User">
        <!--        主键的映射-->
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="phone" column="phone"/>
    </resultMap>


    <select id="selectById" resultMap="BaseResultMap">
        SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
        FROM t_user u
        WHERE u.uid = #{uid}
    </select>

</mapper>

单元测试

ctrl + shift + t 创建单元测试类

@SpringJUnitConfig(
        locations =
                {"classpath:spring-context.xml",
                        "classpath:spring-mvc.xml",
                        "classpath:spring-mybatis.xml",
                        "classpath:spring-druid.xml"})
public class UserMapperTest {
    @Resource
    UserMapper userMapper;
	
    @Test
    void selectById() {
        User user = userMapper.selectById(1L);
        System.out.println(user.toString());
    }
	
}

错误

Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from class path resource [spring-druid.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 49; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'context:property-placeholder' 的声明。
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:402)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:338)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:257)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadBeanDefinitions(AbstractGenericContextLoader.java:265)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:122)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
	... 69 more
Caused by: org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 49; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'context:property-placeholder' 的声明。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:453)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3231)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1912)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:761)
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:351)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2784)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771)
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
	at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
	at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:77)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:432)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
	... 82 more

关联查询

一 概要

前面我们讲的都是单表查询,但企业开发中我们可能会碰到一些复杂的业务,这个时候可能需要多表查询

映射关系:

  1. 一对一
  2. 一对多(如果没有特殊说明,建议一对一采用一对多的方式设计) 多对一
  3. 多对多 角色跟权限(两个一对多)

二 关系模型(外键约束)

  1. 一对一

    1. 主表
    2. 从表

    垂直分表

    • 常用字段放主表
    • 不常用字段放从表

    外键字段

    • 把主表的主键作为从表的外键 外键字段使用唯一约束
    用户表   
    用户详情表  uid 
    
  2. 一对多

    外键不使用唯一约束

    selelct  * from  t   where  外键字段=值
    
  3. 多对多

    • 两个一对多
    • 必须有第三张表
    • 性能问题

三 一对一

association标签

SQL 关系模型

主表

CREATE TABLE t_user
(
    uid         bigint AUTO_INCREMENT       PRIMARY KEY,
    username    varchar(128)                       NOT NULL COMMENT '用户名',
    password    varchar(128)                       NOT NULL COMMENT '密码',
    phone       varchar(11)                        NOT NULL COMMENT '手机号',
    create_date datetime DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间',
    status      int(1)   DEFAULT 1                 NOT NULL COMMENT '1 表示正常  0 表示删除',
    CONSTRAINT phone   UNIQUE (phone),
    CONSTRAINT username   UNIQUE (username)
);

从表

  • 外键字段 uid
CREATE TABLE t_user_detail
(
    user_detail_id bigint AUTO_INCREMENT PRIMARY KEY,
    email          varchar(255) UNIQUE NOT NULL COMMENT '用户邮箱',
    status         int(1) DEFAULT 1    NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
    /*一对一使用UNIQUE外键唯一约束*/
    uid            bigint UNIQUE       NOT NULL COMMENT '用户ID',
    /*外键约束 constraint 约束  foreign key 外键 references  映射*/
    CONSTRAINT fk_user_uid FOREIGN KEY (uid) REFERENCES t_user (uid)
)

对象模型

@Data – 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@NoArgsConstructor – 这个注解可以生成无参构造方法
@AllArgsConstructor – 这个注解可以生成全参构造函数,且默认不生成无参构造函数

@RequiredArgsConstructor – 在类上使用,这个注解可以生成带参或者不带参的构造方法

用户对象

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    /**
     * 关系模型 自己在User中加
     */
    private UserDetail userDetail;
}

用户详情

@Data
public class UserDetail {
    /**
     * 用户ID
     */
    private Long uid;
}

mapper接口

User selectByUsername(@Param("username") String username);

mapper.xml

resultMap

    <!-- 通过用户名查询用户的所有信息 -->
    <resultMap id="UserDetailResultMap" type="User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="phone" column="phone"/>
        <result property="status" column="status"/>
        <result property="createDate" column="create_date"/>
        <!--     表示一对一的关系 这里的userDetail是User里面的userDetail属性 -->
        <association property="userDetail" resultMap="DetailBaseResultMap"/>
    </resultMap>

    <!-- 通过用户名查询用户的所有信息 -->
    <resultMap id="DetailBaseResultMap" type="UserDetail">
        <id property="userDetailId" column="user_detail_id"/>
        <result property="email" column="email"/>
        <result property="status" column="status"/>
        <result property="uid" column="uid"/>
    </resultMap>

<!--        普通多表 < 子查询  < 外连接    左外连接  右外连接  全连接 -->
<select id="selectByUsername" resultMap="UserDetailResultMap">
    SELECT u.uid,
    u.username,
    u.password,
    u.phone,
    u.create_date,
    u.status,
    ud.user_detail_id,
    ud.email,
    ud.status,
    ud.uid
    FROM t_user u
    LEFT JOIN t_user_detail ud ON u.uid = ud.uid
    WHERE u.username = #{username}
</select>

测试代码

 @Test
 void selectUserByName() {
     User user = userMapper.selectByUsername("霞霞1号");
     System.out.println(user.getUserDetail().getEmail());
}

四 一对多

1 概要

一方持有多方的引用

如:

用户有多个订单 ,一个订单有多个商品

一级菜单包含多个二级菜单

2 SQL(关系模型)

用户表

CREATE TABLE t_user
(
    uid         bigint AUTO_INCREMENT       PRIMARY KEY,
    username    varchar(128)                       NOT NULL COMMENT '用户名',
    password    varchar(128)                       NOT NULL COMMENT '密码',
    phone       varchar(11)                        NOT NULL COMMENT '手机号',
    create_date datetime DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间',
    status      int(1)   DEFAULT 1                 NOT NULL COMMENT '1 表示正常  0 表示删除',
    CONSTRAINT phone   UNIQUE (phone),
    CONSTRAINT username   UNIQUE (username)
);

订单表

CREATE TABLE t_order
(
    order_id bigint AUTO_INCREMENT
        PRIMARY KEY,
    order_no bigint           NOT NULL COMMENT '订单号',
    msg      varchar(255)     NULL COMMENT '附加信息',
    status   int(1) DEFAULT 1 NULL,
    uid      bigint           NOT NULL COMMENT '用户ID',
    CONSTRAINT order_no
        UNIQUE (order_no)
);

3 对象模型

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    /**
     * 用户的订单信息 自己在User里加
     */
    private List<Order> orders;
}

@Data
public class Order {
    /**
     *
     */
    private Long orderId;

    /**
     * 订单号
     */
    private Long orderNo;

    /**
     * 附加信息
     */
    private String msg;

    /**
     *
     */
    private Integer status;

    /**
     * 用户ID
     */
    private Long uid;
}


4 mapper接口

User selectOrderById(@Param("uid") Long uid);

5 mapper.xml

    <!--    -->
    <select id="selectOrderById" resultMap="UserOrderResultMap">
        SELECT u.uid,
               u.username,
               u.password,
               u.phone,
               u.create_date,
               u.status,
               o.order_id,
               o.order_no,
               o.msg,
               o.status os,
               o.uid
        FROM t_user u
                     LEFT JOIN t_order o ON u.uid = o.uid
        WHERE u.uid = #{uid}
    </select>

6 ResultMap

    <!-- 通过用户id查询用户的所有订单 -->
    <resultMap id="UserOrderResultMap" type="User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="phone" column="phone"/>
        <result property="status" column="status"/>
        <result property="createDate" column="create_date"/>
        
        <!--这里的orders是User里面的orders属性-->
        <collection property="orders" resultMap="OrderResultMap"/>
    </resultMap>

    <resultMap id="OrderResultMap" type="Order">
        <id property="orderId" column="order_id"/>
        <result property="uid" column="uid"/>
        <result property="orderNo" column="order_no"/>
        <result property="msg" column="msg"/>
        <result property="status" column="os"/>
    </resultMap>

7 补充

如果多的一方的数据量比较大会产生性能问题

可以在collection标签中使用selelct属性来实现分页查询

用户相关

UserMapper 接口

//两个接口都通过uid来查询
User selectOrderById(@Param("uid") Long uid);

UserMapper.xml

<select id="selectOrderById" resultMap="UserDetailResultMap">
    SELECT *
    FROM t_user
    WHERE uid = #{uid}
</select>    

ResultMap

  • column 指定外键字段
  • select = 查询多方的接口
<resultMap id="UserDetailResultMap" type="User">
    <id property="uid" column="uid"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="phone" column="phone"/>
    <result property="status" column="status"/>
    <result property="createDate" column="create_date"/>
    
    <!--     一对多   多的一个放数据量比较大的话建立使用这种方式  com.smart.mapper.OrderMapper.selectOrders是指com.smart路径下面的mapper文件里OrderMapper接口的selectOrder方法-->
    <collection property="orders"
                column="uid"
                select="com.smart.mapper.OrderMapper.selectOrders"/>
</resultMap>
订单相关

OrderMapper 接口

//两个接口都通过uid来查询,只是OrderMapper接口通过selectOrders方法,通过uid查询到的用户的所有订单封装到List<Order>传给User对象
public interface OrderMapper {
    List<Order> selectOrders(@Param("uid") Long uid);
}

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.OrderMapper">
    <select id="selectOrders" resultMap="BaseResultMap">
        SELECT *
        FROM t_order
        WHERE uid = #{uid}
        LIMIT 0,10
    </select>
</mapper>

ResultMap

<resultMap id="BaseResultMap" type="Order">
    <id property="orderId" column="order_id"/>
    <result property="uid" column="uid"/>
    <result property="orderNo" column="order_no"/>
    <result property="msg" column="msg"/>
    <result property="status" column="status" />
</resultMap>

五 多对多

两个一对多

  • 角色 权限

    • 部门经理 炒鸡管理员
    • 增 删 改 查

关系模型(SQL)

  • 角色表
  • 权限表
  • 中间表

角色表

CREATE TABLE t_role
(
    role_id     int AUTO_INCREMENT,
    name        varchar(128)       NOT NULL COMMENT '角色的名字',
    role_dec    varchar(255) COMMENT '角色说明',
    create_date DATETIME DEFAULT NOW() COMMENT '创建时间',
    status      int      DEFAULT 1 NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
    CONSTRAINT pk_role_id PRIMARY KEY (role_id),
    CONSTRAINT uk_role_name UNIQUE (name)
);

CREATE TABLE t_permission
(
    per_id      int AUTO_INCREMENT PRIMARY KEY,
    name        varchar(128)       NOT NULL COMMENT '权限名称',
    per_dec     varchar(255) COMMENT '权限说明',
    create_date DATETIME DEFAULT NOW() COMMENT '创建时间',
    status      int      DEFAULT 1 NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
    CONSTRAINT uk_permission_name UNIQUE (name)
);
# 角色权限中间表
CREATE TABLE role_permission_relation
(
    id      int AUTO_INCREMENT PRIMARY KEY,
    per_id  int  NOT NULL COMMENT '权限ID',
    role_id int  NOT NULL COMMENT '角色ID'
)

对象模型

@Data
public class Role {
	//...
    private List<Permission> permissions;
}
@Data
public class Permission {
    private List<Role> roles;
}

mapper接口

public interface RoleMapper {
    /**
     * 查询所有的角色权限信息
     */
    List<Role> selectList();
}



public interface PermissionMapper {
}

mapper 映射

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.RoleMapper">

    <resultMap id="RolePermissionResultMap" type="Role">
        <id property="roleId" column="role_id"/>
        <result property="name" column="role_name"/>
        <result property="roleDec" column="role_dec"/>
        <result property="status" column="role_status"/>
        <result property="createDate" column="role_create_date"/>
        <collection property="permissions"
                    resultMap="com.smart.mapper.PermissionMapper.BaseResultMap"/>
    </resultMap>
    
    <!--    关联查询的时候  多表查询的字段不能有重复的   -->
    <select id="selectList" resultMap="RolePermissionResultMap">
        SELECT r.role_id,
               r.name        role_name,
               r.role_dec,
               r.create_date role_create_date,
               r.status      role_status,
               p.per_id,
               p.name,
               p.per_dec,
               p.create_date,
               p.status
        FROM t_role r
                     LEFT JOIN role_permission_relation rpr ON r.role_id = rpr.role_id
                     LEFT JOIN t_permission p ON p.per_id = rpr.per_id
        WHERE r.status = 1
          AND p.status = 1
    </select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.PermissionMapper">
    <resultMap id="BaseResultMap" type="Permission">
        <id property="perId" column="per_id"/>
        <result property="name" column="name"/>
        <result property="perDec" column="per_dec"/>
    </resultMap>
</mapper>

六 注意

  • 多表查询的时候如果查询的表的字段出现名字相同的时候,需要使用别名来解决数据错误的情况

动态SQL

1 if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;

如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

2 choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

3 trim、where、set

前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么,用where标签解决下面问题

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

<select id="selectList" resultMap="BaseResult">
        select b.bid, b.name, b.image, b.banner_model, b.banner_type, b.banner_type_id, b.banner_rank, b.status from t_banner b
        <where>
            <if test="name!=null and name!=''">
                b.name like concat('%',#{name},'%')
            </if>
            <if test="model!=null and model!=''">
              and b.banner_model   like concat('%',#{model},'%')
            </if>
            <if test="type!=null and type!=''">
                and b.banner_type   like concat('%',#{type},'%')
            </if>
            <if test="bstatus==1 or bstatus==0">
                and b.status=#{bstatus}
            </if>
        </where>
    </select>

比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

4 foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

事务

一 Spring 事务

用户定义一系列SQL操作的序列,这些做操作要么一起执行成功要么一起执行失败

  • 包含多条SQL语句
  • 多条SQL语句中包含,增删改的操作
  • 如果多条SQL语句都是查询的操作,可以不需要开启事务

特性

  • 原子性(Atomic)

  • 一致性(Consistent)

  • 隔离性(Isolated)

    • 读取未提交 未提交读(readuncommited)

    • 读取已提交(针对更新操作) 提交读(readcommit)

    • 可重复读(增加删除 多条记录) 重复读(repeatableread)

    • 串行化(serializable)

  • 持久性(durable)

二 概要

Spring对事务也进行了支持 底层AOP实现

分类:

  1. 编程事务 (不推荐)
  2. 声明事务 (注解+配置)

三 基础使用

导入相关依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.3.9</version>
</dependency>
<!--单元测试时测试控制层需要这个依赖-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.1</version>
</dependency>
<!--时间工具类-->
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.7.9</version>
</dependency>

配置

<?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:p="http://www.springframework.org/schema/p"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--   注册事务管理类  -->
  <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource"
  />
  <!--    开启注解事务的支持   -->
  <tx:annotation-driven/>
</beans>

业务层

@Transactional
@Service
public class OrderServiceImpl implements OrderService {
  // 开启事务    @Transactional
  @Transactional
  @Override
  public String createOrder() {
      return null;
  }
}

四 核心注解

作用

@Transactional 开启 Spring 事务支持

源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	Propagation propagation() default Propagation.REQUIRED;
	Isolation isolation() default Isolation.DEFAULT;//设置隔离级别
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	String timeoutString() default "";
	boolean readOnly() default false;
	Class<? extends Throwable>[] rollbackFor() default {};
}
属性说明
Isolation设置隔离(在开发中不要去设置隔离级别) mysql隔离 REPEATABLE_READ oracle READ_COMMITTED
propagation事务传播机制
timeout事务的超时时间 默认 -1 推荐 3 单位是秒
readOnly表示可读性事务(建议不要设置)
rollbackFor默认 RuntimeException 级别

propagation

事务的传播机制

@Transactional(propagation=Propagation.REQUIRED)

可选值

可选值说明
REQUIRED如果当前方法有事务就加入事务,没有就开启事务
SUPPORTS如果入口方法有事务,就加入事务,如果入口方法没事务则不开启事务
MANDATORY入口方法必须有事务,如果没有事务直接抛异常
PROPAGATION_REQUIRES_NEW不管入口方法有没有事务都单独开启事务
NOT_SUPPORTED不开启事务
NEVER必须在一个没有事务的方法中执行,否则直接抛异常
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作

五 事务失效场景

  • 在业务层的实现类上使用(类上加注解)
  1. 方法必须是public声明
  • AOP --> 反射 —> 获取所有公开的方法;
  1. 异常必须是RuntimException异常或者子类

  2. springmvc 重复扫描也会导致事务失效

  3. 数据必须支持事务 innoDB引擎才支持事务 MyISAM引擎不支持事务

  4. 异常被你处理

六 如何使用

注册送积分

注册送积分

  1. 查询用户是否存在
  2. 保存用户信息
  3. 添加积分
用户表
create table member(
  member_id bigint auto_increment primary key ,
    /*unique 用户名是唯一的*/
  username varchar(255) unique not null comment '用户名',
  password varchar(128) not null comment '密码',
    /*unique 手机号是唯一的 11位*/
  phone varchar(11) unique not null comment '手机号',
    /*unique 邮箱也是唯一的*/
  email varchar(255) unique not null comment '邮箱',
  create_date datetime default now() not null comment '注册时间',
    /*on update now() 更新就修改为更新当前时间*/
  update_date datetime default now() on update now() not null comment '最后更新时间',
  status tinyint(1) default 1 not null comment '是否删除 1 正常 0删除',
  head varchar(255) default '/static/image/default.png' not null comment '用户头像'
);
积分表
create table t_integral
(
    integral_id    int auto_increment primary key comment '主键',
    type           int                    not null comment '积分类型 1 表示新用户注册送积分 2 签到送积分',
    score          int                    not null comment '积分',
    create_date    datetime default now() not null comment '创建时间',
    operation_type int                    not null comment '1表示添加积分 2表示消费积分',
    expire_data    datetime               not null comment '积分过期时间',
    member_id      bigint                 not null comment '用户id',
    status         tinyint  default 1     not null comment '1 正常 2 过期 3冻结'
);
MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.tx.mapper.MemberMapper">
    <resultMap id="BaseResultMap" type="Member">
        <id property="memberId" column="member_id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="phone" column="phone"/>
        <result property="email" column="email"/>
        <result property="status" column="status"/>
    </resultMap>

    <select id="selectMember" resultMap="BaseResultMap">
        SELECT member_id,
               username,
               password,
               phone,
               email,
               status
        FROM member
        WHERE username = #{username}
           OR phone = #{phone}
           OR email = #{email}
    </select>

    <!--返回主键-->
    <insert id="insert" keyProperty="member.memberId" useGeneratedKeys="true">
        INSERT INTO member (username, password, phone, email)
        VALUES (#{member.username}, #{member.password}, #{member.phone}, #{member.email})
    </insert>
</mapper>
MemberMapper
public interface MemberMapper {

    /**
     * 影响的行数  int
     * Entity
     * List<Entity>
     *
     * @return
     */
    List<Member> selectMember(@Param("username") String username, @Param("phone") String phone, @Param("email") String email);

    int insert(@Param("member") Member member);

}
MemberServiceImpl
@Service
public class MemberServiceImpl implements MemberService {
    @Resource
    private IntegralService integralService;
    
    @Resource
    private MemberMapper memberMapper;

    /**
     * 判断会员是否存在
     * 添加信息到会员
     * 添加100积分
     *
     * @param params
     * @return 请求参数类转化成持久化对象  BeanUtils.copyProperties
     */
    @Transactional(rollbackFor = ServiceException.class)
    @Override
    public boolean register(MemberRequestParams params) {
        List<Member> list = memberMapper.selectMember(params.getUsername(), params.getPhone(), params.getEmail());
        if (list == null || list.size() == 0) {
            Member member = new Member();
            // source  有值对象    target  目标对象
            BeanUtils.copyProperties(params, member);
            member.setPassword(DigestUtils.md5DigestAsHex(member.getPassword().getBytes()));
            int count = memberMapper.insert(member);
            if (count > 0) {
                //避免生产环境中一些未知因素
                int num = integralService.addIntegral(member.getMemberId(), 100, 1, 1, DateUtil.nextMonth());
                return true;
            } else {
                throw new ServiceException(404, "用户注册失败");
            }
        } else {
            throw new ServiceException(40010, "账户已存在");
        }
    }
}
status为动态的动态sql

status不是动态的就直接where status=1,只显示没有删除的数据,即status为1的数据

<select id="selectList" resultMap="BaseResultMap">
        select a.apply_id, a.username, a.key_type, a.phone, a.id_card, a.key_password from rp_key_apply a
        where status=1
            <if test="name!=null and name!=''">
                and a.username like concat('%',#{name},'%')
            </if>
            <if test="type!=null and type!=''">
                and a.key_type like concat('%',#{type},'%')
            </if>
            <if test="phone!=null and phone!=''">
                and a.phone like concat('%',#{phone},'%')
            </if>
    </select>

如果status是多个状态,不只是删除和正常的状态,即status也是动态的,就用where标签解决

<select id="selectList" resultMap="BaseResult">
        select b.bid, b.name, b.image, b.banner_model, b.banner_type, b.banner_type_id, b.banner_rank, b.status from t_banner b
        <where>
            <if test="name!=null and name!=''">
                b.name like concat('%',#{name},'%')
            </if>
            <if test="model!=null and model!=''">
              and b.banner_model   like concat('%',#{model},'%')
            </if>
            <if test="type!=null and type!=''">
                and b.banner_type   like concat('%',#{type},'%')
            </if>
            <if test="status==1 or status==0">
                and b.status=#{status}
            </if>
        </where>
    </select>
购物车动态sql
CartMapper.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.smart.carts.mapper.CartMapper">
    <resultMap id="BaseResultMap" type="Carts">
        <!--@mbg.generated-->
        <!--@Table t_carts-->
        <id column="cart_id" jdbcType="BIGINT" property="cartId"/>
        <result column="product_id" jdbcType="BIGINT" property="productId"/>
        <result column="member_id" jdbcType="BIGINT" property="memberId"/>
        <result column="quantity" jdbcType="INTEGER" property="quantity"/>
        <result column="product_name" jdbcType="VARCHAR" property="productName"/>
        <result column="product_price" jdbcType="DECIMAL" property="productPrice"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
        <result column="create_date" jdbcType="TIMESTAMP" property="createDate"/>
    </resultMap>

    <select id="selectCartsByMemberId" resultMap="BaseResultMap">
        SELECT c.cart_id,
               c.product_id,
               c.member_id,
               c.quantity,
               c.product_name,
               c.product_price
        FROM t_carts c
        WHERE member_id = #{memberId}
          AND product_id = #{productId}
          AND status = 1
    </select>

    <insert id="insert" keyProperty="cartId" useGeneratedKeys="true">
        INSERT INTO t_carts
        <trim prefix=" (" suffix=")" suffixOverrides=",">
            <if test="carts.memberId != null">
                member_id,
            </if>
            <if test="carts.productId != null">
                product_id,
            </if>
            <if test="carts.productName != null">
                product_name,
            </if>
            <if test="carts.quantity != null">
                quantity,
            </if>
            <if test="carts.productPrice != null">
                product_price,
            </if>
        </trim>
        <trim prefix="values(" suffix=")" suffixOverrides=",">
            <if test="carts.memberId != null">
                #{carts.memberId},
            </if>
            <if test="carts.productId != null">
                #{carts.productId},
            </if>
            <if test="carts.productName != null">
                #{carts.productName},
            </if>
            <if test="carts.quantity != null">
                #{carts.quantity},
            </if>
            <if test="carts.productPrice != null">
                #{carts.productPrice},
            </if>
        </trim>
    </insert>

    <update id="updateById">
        UPDATE t_carts
        <trim prefix="SET" suffixOverrides=",">
            <if test="carts.productName != null">
                product_name= #{carts.productName},
            </if>
            <if test="carts.productPrice != null">
                product_price= #{carts.productPrice},
            </if>
            <if test="carts.quantity != null">
                quantity= #{carts.quantity},
            </if>
            <if test="carts.status != null">
                status=#{carts.status},
            </if>
            <if test="carts.memberId != null">
                member_id=#{carts.memberId},
            </if>
            <if test="carts.productId != null">
                product_id =#{carts.productId},
            </if>
        </trim>
        WHERE cart_id = #{carts.cartId}
    </update>

    <!--批量删除-->
    <update id="batch">
        <!--        更新 status -->
        UPDATE t_carts
        SET status=0
                WHERE member_id = #{memberId}
                  AND status = 1
                  AND cart_id IN
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </update>
</mapper>
CartMapper
public interface CartMapper {
    /**
     * 判断用户购物车中是否存在该条记录
     */
    Carts selectCartsByMemberId(@Param("memberId") Long memberId, @Param("productId") Long productId);

    /**
     * 编程的思想
     *
     * @param carts
     * @return
     */
    int insert(@Param("carts") Carts carts);

    /**
     * 必须有主键
     *
     * @param
     * @return
     */
    int updateById(@Param("carts") Carts carts);

    批量删除
    int batch(@Param("ids") List<Long> ids, @Param("memberId") Long memberId);
}
CartService
public interface CartService {
    Boolean add(CartsRequestParams cartsRequestParams);

    int batchDel(List<Long> ids, Long memberId);
}
CartServiceImpl
@Service
public class CartServiceImpl implements CartService {
    @Resource
    CartMapper cartMapper;

    /**
     * 10  布娃娃    10
     *
     * @param cartsRequestParams
     * @return qo对象
     * 转化成
     * po对象  entity
     */
    @Override
    public Boolean add(CartsRequestParams cartsRequestParams) {
        int count;
        try {
            /**
             * 判断用户是否已添加到购物车   用户ID
             */
            Carts carts = cartMapper.selectCartsByMemberId(cartsRequestParams.getMemberId(), cartsRequestParams.getProductId());
            if (carts == null) {
                //添加
                carts = new Carts();
//            Spring BeanUtils
                BeanUtils.copyProperties(cartsRequestParams, carts);
                count = cartMapper.insert(carts);
            } else {
                //  更新的操作   数量的计算  >1
                int quantity = cartsRequestParams.getQuantity() > 1 ? cartsRequestParams.getQuantity() : 1;
                carts.setQuantity(carts.getQuantity() + quantity);
                count = cartMapper.updateById(carts);
            }
        } catch (Exception ex) {
            throw new ServiceException(ResponseCode.SYS_ERROR);
        }
        return count > 0;
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public int batchDel(List<Long> ids, Long memberId) {
        int batch = cartMapper.batch(ids, memberId);
        if (batch == ids.size()) {
            return batch;
        } else {
            throw new ServiceException(ResponseCode.ERROR);
        }

    }
}
CartController
/**
 * servlet  + jsp
 * 关联查询操作  事务
 * 数据安全  锁
 * 事务
 * 购物车功能
 * 1. 分页显示购物信息  分页查询
 * 2  删除购物车信息    更新
 * 2  删除多条购物车信息  批量更新
 * 4  修改购物车数量     修改
 * 5  添加商品到购物车    存在在修改数量  不存在添加记录
 */
@RestController
@RequestMapping("/carts")
public class CartController {
    /**
     * 业务逻辑
     */
    @Resource
    private CartService cartService;

    /**
     * 添加购物车
     */
    @PostMapping("/add")
    public ResponseResult<Boolean> add(@RequestBody CartsRequestParams cartsRequestParams) {
        Boolean isAdd = cartService.add(cartsRequestParams);
        return ResponseResult.success(isAdd);
    }

    /**
     * 保存删除 购物车ID
     * 用户查看购物车信息
     */
    @PostMapping("/del/batch/")
    public ResponseResult<Integer> batchDel(@RequestParam List<Long> ids, @RequestParam Long memberId) {
        return ResponseResult.success(cartService.batchDel(ids, memberId));
    }
}

全局异常处理

异常处理范例

/**
 * 1. 如何使用自定义异常
 * 2. 全局异常如何处理
 * 3. 状态码封装
 */
@Service
public class MemberServiceImpl implements MemberService {
    @Override
    public String register(String username, String password) {
        // 第一步  判断用户名或者手机号
        if (false) {
            // 注册逻辑代码
        } else {
            throw new ServiceException(ResponseCode.ACCOUNT_IS_EXIST);
        }
        return null;
    }
}

枚举类响应状态码

/**
 * 响应状态码
 */
@Getter
public enum ResponseCode {
    /*
     * ===============通用状态码 ===============
     */
    SUCCESS(200, "success"),
    ERROR(404, "error"),
    SYS_ERROR(40400, "sys error"),
    /**
     * 系统相关的
     */

    /**
     * 业务异常
     * <p>
     * 订单状态
     * <p>
     * 用户状态码
     */
//    用户状态码
    ACCOUNT_IS_EXIST(40100, "username  or  phone  is exist");

    private int status;
    private String msg;

    ResponseCode(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }
}

统一结果集

/**
 * 
 * 状态码跟提示信息进行封装
 */
@Data
@AllArgsConstructor//提供全参构造方法
@NoArgsConstructor//提供无参构造方法
@Builder
public class ResponseResult<T> {
    private int status;
    private String msg;
    private T data;


    /**
     * 通用成功返回的数据
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> success(T data) {
        return success(data, ResponseCode.SUCCESS);
    }

    //泛型 静态方法前需要加<T>
    public static <T> ResponseResult<T> success(T data, ResponseCode responseCode) {
        return new ResponseResultBuilder<T>()
                .msg(responseCode.getMsg())
                .status(responseCode.getStatus())
                .data(data)
                .build();
    }


    public static <T> ResponseResult<T> error() {
        return error(ResponseCode.ERROR);
    }

    public static <T> ResponseResult<T> error(ResponseCode responseCode) {
        return error(responseCode.getStatus(), responseCode.getMsg());
    }

    public static <T> ResponseResult<T> error(int status, String msg) {
        return new ResponseResultBuilder<T>()
                .msg(msg)
                .status(status)
                .build();
    }

}

无语义异常处理

@Getter
public class BaseException extends RuntimeException {
    private ResponseCode responseCode;

    /**
     * 维护的角度
     *
     * @param responseCode
     */
    public BaseException(ResponseCode responseCode) {
        super(responseCode.getMsg());
        this.responseCode = responseCode;
    }
}
控制层异常处理,继承BaseException无语义的异常处理
public class ControllerException extends BaseException {
    public ControllerException(ResponseCode responseCode) {
        super(responseCode);
    }
}
业务层异常处理,继承BaseException无语义的异常处理
public class ServiceException extends BaseException {
    public ServiceException(ResponseCode responseCode) {
        super(responseCode);
    }
}
全局异常处理类
/**
 * 先定义个全局异常处理类
 * 在类上使用 @RestControllerAdvice
 * 
 */
@RestControllerAdvice
@Slf4j
public class GlobalHandlerException {

    /**
     * 接收所有的异常
     * @return
     */
    @ExceptionHandler(Exception.class)
    public static ResponseResult<Object> handlerException(Exception ex) {
        //打印异常信息,不然不会报错,也不知道是什么错误
        log.error(ex.getMessage());
        //如果是业务层的异常
        if (ex instanceof ServiceException) {
            ServiceException serviceException = (ServiceException) ex;
            return ResponseResult.error(serviceException.getResponseCode());
        }
        return ResponseResult.error();
    }

    //只接收业务层的异常ServiceException.class,当然也可以定义一个只接收控制层或者其他层的异常
    @ExceptionHandler(ServiceException.class)
    public static ResponseResult<Object> handlerException(ServiceException ex) {
        log.error(ex.getMessage());
        return ResponseResult.error(ex.getResponseCode());
    }

}

单表一对多

两级菜单

entity
@Data
public class Category {
    private Integer cateId;

    /**
     * 分类名称
     */
    private String name;

    /**
     * 小图片
     */
    private String icon;

    /**
     * 跳转路径
     */
    private String url;

    /**
     * 创建时间
     */
    private Date createDate;

    /**
     * 1 表示 一级菜单   2 表示二级菜单
     */
    private Integer level;

    /**
     * 父类ID  0表示父类菜单
     */
    private Integer parentId;

    /**
     * 是否删除  1---->正常  0---->表示删除
     */
    private Boolean status;

    //自己加
    private List<Category> subList;
}
CategoryMapper
public interface CategoryMapper {
    List<Category> selectList();
}
CategoryMapper.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.smart.carts.mapper.CategoryMapper">
    
    <resultMap id="BaseResultMap" type="com.smart.carts.entity.Category">
        <id column="cate_id" jdbcType="INTEGER" property="cateId"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
        <result column="icon" jdbcType="VARCHAR" property="icon"/>
        <result column="url" jdbcType="VARCHAR" property="url"/>
        <result column="create_date" jdbcType="TIMESTAMP" property="createDate"/>
        <result column="level" jdbcType="INTEGER" property="level"/>
        <result column="parent_id" jdbcType="INTEGER" property="parentId"/>
        <result column="status" jdbcType="BOOLEAN" property="status"/>
    </resultMap>
    
    <!-- 坑 多表查询属性不能一样 -->
    <resultMap id="CategoryListResultMap" type="com.smart.carts.entity.Category" extends="BaseResultMap">
        <collection property="subList" resultMap="SubResultMap"/>
    </resultMap>

    
    <resultMap id="SubResultMap" type="com.smart.carts.entity.Category">
        <id column="sub_cate_id" jdbcType="INTEGER" property="cateId"/>
        <result column="sub_name" jdbcType="VARCHAR" property="name"/>
        <result column="sub_icon" jdbcType="VARCHAR" property="icon"/>
        <result column="sub_url" jdbcType="VARCHAR" property="url"/>
        <result column="sub_create_date" jdbcType="TIMESTAMP" property="createDate"/>
        <result column="sub_level" jdbcType="INTEGER" property="level"/>
        <result column="sub_parent_id" jdbcType="INTEGER" property="parentId"/>
        <result column="sub_status" jdbcType="BOOLEAN" property="status"/>
    </resultMap>

    <select id="selectList" resultMap="CategoryListResultMap">
        SELECT c.cate_id,
               c.name,
               c.icon,
               c.url,
               c.create_date,
               c.level,
               c.parent_id,
               c.status,
               sub.cate_id     sub_cate_id,
               sub.name        sub_name,
               sub.icon        sub_icon,
               sub.url         sub_url,
               sub.create_date sub_create_date,
               sub.level       sub_level,
               sub.parent_id   sub_parent_id,
               sub.status      sub_status

        FROM pms_category c
                     LEFT JOIN pms_category sub ON c.cate_id = sub.parent_id
        WHERE c.parent_id = 0;
    </select>
</mapper>

多级菜单

entity
@Data
public class Address {
    /**
     * 地区id
     */
    private Long addressId;

    /**
     *
     */
    private String name;

    /**
     * 创建时间
     */
    private Date createDate;

    /**
     * 菜单级别 1表示一级菜单 2表示二级菜单
     */
    private Integer level;

    /**
     * 父类id
     */
    private Integer parentId;

    /**
     * 是否删除 1表示正常 0表示删除
     */
    private Boolean status;

    /**
     *二级菜单
     */
    private List<Address> secondList;

    /**
     *三级菜单
     */
    private List<Address> thirdList;

    /**
     *四级菜单
     */
    private List<Address> fourthList;
}
AddressMapper
public interface AddressMapper {
    List<Address> selectList();
}
AddressMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.AddressMapper">
    <resultMap id="BaseResultMap" type="Address">
        <id property="addressId" column="address_id"/>
        <result property="name" column="name"/>
        <result property="status" column="status"/>
        <collection property="secondList" resultMap="SecondBaseResultMap"/>
    </resultMap>

    <resultMap id="SecondBaseResultMap" type="Address">
        <id property="addressId" column="second_id"/>
        <result property="name" column="second_name"/>
        <result property="status" column="second_status"/>
        <collection property="thirdList" resultMap="ThirdBaseResultMap"/>
    </resultMap>

    <resultMap id="ThirdBaseResultMap" type="Address">
        <id property="addressId" column="third_id"/>
        <result property="name" column="third_name"/>
        <result property="status" column="third_status"/>
        <collection property="fourthList" resultMap="fourthBaseResultMap"/>
    </resultMap>

    <resultMap id="fourthBaseResultMap" type="Address">
        <id property="addressId" column="fourth_id"/>
        <result property="name" column="fourth_name"/>
        <result property="status" column="fourth_status"/>
    </resultMap>

    <select id="selectList" resultMap="BaseResultMap">
        SELECT a.address_id,
               a.`name`,
               a.`status`,
               a2.address_id second_id,
               a2.`name`     second_name,
               a2.`status`   second_status,
               a3.address_id third_id,
               a3.`name`     third_name,
               a3.status     third_status,
               a4.address_id fourth_id,
               a4.`name`     fourth_name,
               a4.status     fourth_status
        FROM address a
                     LEFT JOIN address a2 ON a.address_id = a2.parent_id
                     LEFT JOIN address a3 ON a2.`address_id` = a3.`parent_id`
                     LEFT JOIN address a4 ON a3.`address_id` = a4.`parent_id`
        WHERE a.`parent_id` = 0;
    </select>
</mapper>

result column=“sub_create_date” jdbcType=“TIMESTAMP” property=“createDate”/>



<select id="selectList" resultMap="CategoryListResultMap">
    SELECT c.cate_id,
           c.name,
           c.icon,
           c.url,
           c.create_date,
           c.level,
           c.parent_id,
           c.status,
           sub.cate_id     sub_cate_id,
           sub.name        sub_name,
           sub.icon        sub_icon,
           sub.url         sub_url,
           sub.create_date sub_create_date,
           sub.level       sub_level,
           sub.parent_id   sub_parent_id,
           sub.status      sub_status

    FROM pms_category c
                 LEFT JOIN pms_category sub ON c.cate_id = sub.parent_id
    WHERE c.parent_id = 0;
</select>
```

多级菜单

entity
@Data
public class Address {
    /**
     * 地区id
     */
    private Long addressId;

    /**
     *
     */
    private String name;

    /**
     * 创建时间
     */
    private Date createDate;

    /**
     * 菜单级别 1表示一级菜单 2表示二级菜单
     */
    private Integer level;

    /**
     * 父类id
     */
    private Integer parentId;

    /**
     * 是否删除 1表示正常 0表示删除
     */
    private Boolean status;

    /**
     *二级菜单
     */
    private List<Address> secondList;

    /**
     *三级菜单
     */
    private List<Address> thirdList;

    /**
     *四级菜单
     */
    private List<Address> fourthList;
}
AddressMapper
public interface AddressMapper {
    List<Address> selectList();
}
AddressMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.AddressMapper">
    <resultMap id="BaseResultMap" type="Address">
        <id property="addressId" column="address_id"/>
        <result property="name" column="name"/>
        <result property="status" column="status"/>
        <collection property="secondList" resultMap="SecondBaseResultMap"/>
    </resultMap>

    <resultMap id="SecondBaseResultMap" type="Address">
        <id property="addressId" column="second_id"/>
        <result property="name" column="second_name"/>
        <result property="status" column="second_status"/>
        <collection property="thirdList" resultMap="ThirdBaseResultMap"/>
    </resultMap>

    <resultMap id="ThirdBaseResultMap" type="Address">
        <id property="addressId" column="third_id"/>
        <result property="name" column="third_name"/>
        <result property="status" column="third_status"/>
        <collection property="fourthList" resultMap="fourthBaseResultMap"/>
    </resultMap>

    <resultMap id="fourthBaseResultMap" type="Address">
        <id property="addressId" column="fourth_id"/>
        <result property="name" column="fourth_name"/>
        <result property="status" column="fourth_status"/>
    </resultMap>

    <select id="selectList" resultMap="BaseResultMap">
        SELECT a.address_id,
               a.`name`,
               a.`status`,
               a2.address_id second_id,
               a2.`name`     second_name,
               a2.`status`   second_status,
               a3.address_id third_id,
               a3.`name`     third_name,
               a3.status     third_status,
               a4.address_id fourth_id,
               a4.`name`     fourth_name,
               a4.status     fourth_status
        FROM address a
                     LEFT JOIN address a2 ON a.address_id = a2.parent_id
                     LEFT JOIN address a3 ON a2.`address_id` = a3.`parent_id`
                     LEFT JOIN address a4 ON a3.`address_id` = a4.`parent_id`
        WHERE a.`parent_id` = 0;
    </select>
</mapper>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值