Spring
定义:
Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。
Spring想要加将项目中所有的对象都纳入Spring容器中进行管理(Spring提供了一些容器,用于存储项目中的对象)
两大核心:
IoC和AOP
优点:
- IOC和DI的支持
Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
- AOP 编程的支持
Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
- 声明式事务的支持
支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的JDBC代码,都可以不用自己写了
- 快捷测试的支持
Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。
- 快速集成功能
方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。
- 复杂API模板封装
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。
模块
Spring 框架是分模块存在,除了最核心的Spring Core Container
是必要模块之外,其他模块都是可选
,大约有 20 多个模块。
Spring模块划分
最主要的七大模块:
-
Spring Core:Spring 核心,它是框架最基础的部分,提供 IOC 和依赖注入 DI 特性。
-
Spring Context:Spring 上下文容器,它是 BeanFactory 功能加强的一个子接口。
-
Spring Web:它提供 Web 应用开发的支持。
-
Spring MVC:它针对 Web 应用中 MVC 思想的实现。(运行流程11步)
-
Spring DAO:提供对 JDBC 抽象层,简化了 JDBC 编码,同时,编码更具有健壮性。
-
Spring ORM:它支持用于流行的 ORM 框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO 的整合等。
-
Spring AOP:即面向切面编程,它提供了与 AOP 联盟兼容的编程实现。
Spring常用注解
Spring有很多模块,甚至广义的SpringBoot、SpringCloud也算是Spring的一部分,我们来分模块,按功能来看一下一些常用的注解:
Spring常用注解
@SpringBootApplication:
参数一:当类启动类的类对象,扫描这个类 EasyMavenApplication.class所在的包及其子包
SpringApplication.run(EasyMavenApplication.class, args);
- Web:
@Controller:组合注解(组合了@Component注解),应用在MVC层(控制层)。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@RequestMapping:用于映射Web请求,包括访问路径和参数。如果是Restful风格接口,还可以根据请求类型使用不同的注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@ResponseBody:支持将返回值放在response内,而不是一个页面,通常用户返回json数据。
@RequestBody:允许request的参数在request体中,而不是在直接连接在地址后面。
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
- 容器:
@Component:表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@Service:组合注解(组合了@Component注解),应用在service层(业务逻辑层)。
@Repository:组合注解(组合了@Component注解),应用在dao层(数据访问层)。
@Autowired:Spring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入)。
@Qualifier:该注解通常跟 @Autowired 一起使用,当想对注入的过程做更多的控制,@Qualifier 可帮助配置,比如两个以上相同类型的 Bean 时 Spring 无法抉择,用到此注解
@Configuration:声明当前类是一个配置类(相当于一个Spring配置的xml文件)
@Value:可用在字段,构造器参数跟方法参数,指定一个默认值,支持 #{} 跟 ${} 两个方式。一般将 SpringbBoot 中的 application.properties 配置的属性值赋值给变量。
@Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Scope:定义我们采用什么模式去创建Bean(方法上,得有@Bean) 其设置类型包括:Singleton 、Prototype、Request 、 Session、GlobalSession。
- AOP:
@Aspect:声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After :在方法执行之后执行(方法上)。
@Before:在方法执行之前执行(方法上)。
@Around:在方法执行之前与之后执行(方法上)。
@PointCut:声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)。
- 事务:
@Transactional:在要开启事务的方法上使用@Transactional注解,即可声明式开启事务。
设计模式
Spring中用到的设计模式
工厂模式 : Spring 容器本质是一个大工厂,使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理模式 : Spring AOP 功能功能就是通过代理模式来实现的,分为动态代理和静态代理。
单例模式 : Spring 中的 Bean 默认都是单例的,这样有利于容器对Bean的管理。
模板模式 : Spring 中 JdbcTemplate、RestTemplate 等以 Template结尾的对数据库、网络等等进行操作的模板类,就使用到了模板模式。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知 (Advice) 使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller。
策略模式:Spring中有一个Resource接口,它的不同实现类,会根据不同的策略去访问资源。
IOC和AOP
**IOC:**控制翻转
Spring的IOC意思为控制反转,它不是什么技术,而是一种设计思想。它是将你设计好的对象交给Spring容器来控制,而不是由我来管理对象,给它赋值等等
引入一个概念:
- 耦合度是指系统中不同部分之间的依赖程度或联系紧密程度。在软件开发中,耦合度是指模块或组件之间的相互依赖程度。耦合度越高,模块之间的依赖关系越紧密,修改一个模块可能会对其他模块产生影响,系统的可维护性和可扩展性会降低。反之,耦合度越低,模块之间的依赖关系越松散,修改一个模块不会对其他模块产生影响,系统的可维护性和可扩展性会提高。
Spring想要加将项目中所有的对象都纳入Spring容器中进行管理(Spring提供了一些容器,用于存储项目中的对象)
IoC纳入容器管理的主要就是三层架构:action,service,dao
按照一般情况的话,我们需要从容器中取某一个对象时,容器以集合为例:list.get(x),这样的话,代码之间的耦合度非常高;
耦合度高:比如我这个集合,list.get(1)的对象已经被删除了,那么我们获得的对象就不是我们想要的,因为下一个对象会补上来,关系太紧密了,
- 反转的体现:
传统的面向对象思想对于构造对象最简单的方法无非就是在对象内部通过New对象进行创建,是程序主动去创建依赖对象,由我们主动控制去**直接获取依赖对象,**这就是正转
但IOC不同,IOC是专门有一个容器来创建、初始化这些对象,这些对象被称为Bean。谁控制谁? 是IOC容器控制住了对象
,控制了什么? 控制了bean的生命周期。
而反转就是由Spring容器来创建及注入依赖对象,为何是翻转?因为有容器自动的帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,反转点就是依赖对象的获取
- DI(Dependence Injection)
IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。在生产bean的过程中,需要解决bean之间的依赖问题,才引入了依赖注入(DI)这种技术。也就是说依赖注入是beanFactory生产bean时为了解决bean之间的依赖的一种技术而已。
将对象(service)加入到容器中
@Service//将EasyService加入到容器管理
public class EasyService {
public String method() {
return "EasyService method";
}
}
//依赖注入并运行依赖对象的方法
@Controller
@RequestMapping("easyc")
public class EasyController {
//EasyController依赖EasyService
@Autowired //依赖注入的表现
EasyService easyS;
//Action
@RequestMapping("hello")
@ResponseBody
public String helloworld() {
return easyS.method();
}
注解:
@Controller
//1.标注这个类是一个处理器类(只有有改注解修饰,你才可以输入网址访问类中的地址路径)
//2.会将这个类的对象纳入到Spring管理器
public class EasyAController {
@RequestMapping("easyB")//声明映射地址
@ResponseBody//将该方法的返回值直接写入到response对象中
//如果不写ResponseBody,会将这个方法的返回值当做一个地址进行转发处理
public String method() {
return "EasyB method";
}
@RequestMapping("easyA")
public String easya() {
// return "easyB";//由于没有注解:ResponseBody,会将结果作为转发路径
return "redirect:easyB";//重定向easyB
return "forward:easyB";
}
Springmvc接收前台参数
- 使用参数值的方式,直接接收(前台的参数名与后台的参数名要统一)
@Controller
public class EasyAController {
@RequestMapping("easyC")
@ResponseBody
public String easyc(String name,String sex) {//参数需要与前端发送过来的值名相同
return "接收到前台参数name:"+name+" sex:"+sex;
}
}
- 访问结果:
- 使用Map的形式来接收参数,非常灵活,但是不严谨(不推荐)
@Controller
public class EasyAController {
@RequestMapping("easyD")
@ResponseBody
public String easyd(@RequestParam Map parms) {
return "接收到前台参数"+parms;
}
}
- 访问结果:
- 使用封装对象的方式接受参数(推荐)
@Controller
public class EasyAController {
@RequestMapping("easyE")
@ResponseBody
public String easye(Staff staff,HttpSession session) {
System.out.println(session.getId());
return "接收到前台参数"+staff;
}
}
- 访问结果:
SpringMVC
-
SpringMVC 框架
-
SpringMVC 是一个基于 Java 的实现了 MVC 设计模式的**请求驱动类型的轻量级 Web 框架,**通过把 Model,View,Controller 分离,将 Web 层进行职责解耦,把复杂的 Web 应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 简而言之,SpringMVC 就是将我们原来开发在 Servlet 中的代码拆分了,一部分由 SpringMVC 完成,一部分由我们自己完成。
-
SpringMVC 主要组件
-
前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了 DispatcherServlet 就减少了其它组件之间的耦合度。
处理器映射器 HandlerMapping:根据请求的 URL 来查找 Handler。
处理器适配器 HandlerAdapter:负责执行 Handler。
处理器 Handler:处理业务逻辑的 Java 类(我们自己写的 Controller 类)。
视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将 ModelAndView 解析成真正的视图(view) 。
视图 View:View 是一个接口, 它的实现类支持不同的视图类型,如 jsp,freemarker, pdf 等。
- 用户发送请求到前端控制器(DispatcherServlet)。
- 前端控制器 ( DispatcherServlet ) 收到请求调用处理器映射器 (HandlerMapping),去查找处理器(Handler)。
- 处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
- 前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。
- 处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller)。
- 自定义的处理器类(Controller)将得到的参数进行处理并返回结果给处理器适配器(HandlerAdapter)。
- 处理器适配器 ( HandlerAdapter )将得到的结果返回给前端控制器 (DispatcherServlet)。
- 前端控制器(DispatcherServlet )将 ModelAndView 传给视图解析器 (ViewReslover)。
- 视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)。
- 前端控制器(DispatcherServlet)调用物理视图进行渲染并返回。
前端控制器(DispatcherServlet)将渲染后的结果返回。
拦截器(Interceptor)
在执行处理器之前进行运行的,对请求进行拦截,处理检查操作,
返回true,放行,会正确处理handler,
返回false,就不会再执行handler,直接回应给浏览器(请求方).
定义拦截器:
定义一个拦截器类实现HandlerInterceptor,在类中可以实现三个方法,依情况而实现,然后再定义一个配置类,将拦截器对象注册到springboot中去.
- 拦截器类:
public class EasyInterceptor implements HandlerInterceptor{
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("1-----------------prehandler");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("2------------------posthandler");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("3-----------------aftercompletion");
}
}
- 配置类:
配置器类需要实现webMvcConfigurer接口,实现addInterceptors方法,使用InterceptorRegistry 对象registry注册器将拦截器添加进来
@Configuration //告知Springboot该类我配置类
public class EasyConfig implements WebMvcConfigurer{
public void addInterceptors(InterceptorRegistry registry) {
//registry:注册器,将我们定义的拦截器对象添加进去
registry.addInterceptor(new EasyInterceptor())
.addPathPatterns("/staff","/easyjsp");
//对路径为staff和easyjsp进行拦截
}
}
拦截器中的三个方法:
- boolean preHandler:在获取handler处理器之前运行
- void postHandler:在执行完handler之后运行
- void afterCompletion:在整个mvc流程都结束了,才会执行
补充:
handler就是controller中一个个的方法,每个方法上有注解:@RequestMapping(“xx”)路径
SpringMVC处理异常(服务降级)
当程序出现异常,处理方式可以是try-catch,然后访问的界面会出现一堆看不懂的异常,给用户造成不便,如下:
造成该现象不难看出是因为0作为除数:
@RequestMapping("easyce")
@ResponseBody
public String easye() {
int a=12/0;//异常
return "easyc";
}
那么我们的SprngMVC可以给界面一个简单的数据回应,这也叫做服务降级;
定义异常处理器:
在当前controller中定义一个方法来编写返回给界面的信息,然后在该方法上添加注解:@ExceptionHandler(异常类型.class)和@ResponseBody,将信息写在界面上,而不是跳转界面,如果跳转就不写@ResponseBody注解;
@Controller
public class Test {
@RequestMapping("hhh")
@ResponseBody
public String method() {
int a=0/2;
return "你好啊,异常";
}
//添加异常处理器:
@ExceptionHandler(Exception.class)
@ResponseBody
public String msg() {
return "出错啦,我给你服务降级提示";
}
}
访问界面:
上面所定义的异常处理器只在当前controller生效,只是局部的,不能对其他controller生效,如果想要变成全局的话,需要在当前类的controller注解下添加一个注解:ControllerAdvice;
@Controller
@ControllerAdvice //该类的异常处理器就作为所有的处理器的异常回应(全局性),不添加就只是局部的,只是当前easyce的异常处理
public class EasyEController {
@RequestMapping("easyce")
@ResponseBody
public String easye() {
int a=12/0;//异常
return "easyc";
}
视图解析器:
作用:
在 Spring MVC 中,控制器(Controller)的主要职责是处理用户请求,并产生模型数据(Model),同时指定该请求的逻辑视图名称(Logical View Name)。视图解析器的作用是**将逻辑视图名称解析成实际的视图对象,以便最终在浏览器中呈现给用户。**例如,当用户请求一个 URL 时,控制器可以返回一个逻辑视图名称为 “home”,而视图解析器会将其解析成一个 JSP 页面,最终在浏览器中渲染出来。
使用
当前我们存在一个jsp文件,其目录结构为:
我们想要访问该文件,可以直接访问:
@Controller
public class EasyEController {
@RequestMapping("demopage")
//浏览器输入localhost:8080/demopage
public String demo() {
return "/WEB-INF/page/demo.jsp";
}
}
我们会发现路径冗长,程序员的有点就是懒,所以我们可以配置Springmvc的视图解析器,那么我们就不用写这么多路径,直接写文件名即可访问:
在.yml文件书写:
#配置springmvc的视图解析器
spring:
mvc:
view:
prefix: /WEB-INF/page/
suffix: .jsp
那么我们就可以写成:return “demo”;即可访问文件
制定扫描包
我们有一个启动类,用注解:@SpringbootApplication标注该类是springboot的一个应用,当我们启动springboot时,该类对象会扫描该类所在的包及其子包.
如果我们想要扫描其他包(非该类的所在包及其他包),我们可以使用注解:@ComponentScan(basePackage = {“包名”,“包名”…})
package com.easy;
//标注我是驱动springboot的一个应用
@SpringBootApplication
//扫描其他包的注解
//@ComponentScan(basePackages = {"com","com.demoo"})
public class EasyMavenApplication {
//springBoot项目的启动类
public static void main(String[] args) {
//参数一:当类启动类的类对象,扫描这个类 EasyMavenApplication.class所在的包及其子包
SpringApplication.run(EasyMavenApplication.class, args);
// http://localhost:8080/easyc/hello
}
}
Springbean的作用域
五大作用域:
类别 | 说明 |
---|---|
singleton | 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器调用Bean时,都会返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个Http Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境 |
gloablSession | 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
演示前两种:
- Bean:
package com.easy.bean;
@Component//和Service一样,由于他不是三层模式中的,所有就用该注解,将当前类添加到IoC容器中.
@Scope("singleton")//定义该bean的类别
public class EasyDemo {
}
- 控制器:
@Controller
public class EasyFController {
@Autowired//依赖注入
EasyDemo demo;
@RequestMapping("easyF")
@ResponseBody
public String easyf() {
System.out.println(demo);//打印bean对象地址
return "easyf";
}
}
@Controller
public class EasyFFController {
@Autowired//依赖注入
EasyDemo demo;
@RequestMapping("easyFF")
@ResponseBody
public String easyf() {
System.out.println(demo);
return "easyff";
}
}
两个控制器的内容一样,bean也一样,此时我们bean的类别是单例,那我此时依次访问easyF和easyFF,控制台应该是打印的一个对象地址
控制台打印:
修改bean中的类别,改为prototype:
动态代理&静态代理
代理模式:在不修改原代码的情况下,可以对原有的代码进行增强
静态代理
- 接口:
public interface Handler {
void action();
}
- 代理类
package com.easy;
/**
* 代理类
*/
public class Proxy implements Handler{
Handler handler;
@Override
public void action() {
System.out.println("---------1");//增强业务的体现(不改变委托人的业务且增加新的行为)
//调用真实的业务逻辑
handler.action();//委托人的业务
System.out.println("----------2");
}
}
- 主业务类
package com.easy;
/**
* 主业务类
*
* 想要在原代码上新增新的业务逻辑
*/
public class Easy implements Handler{
@Override
public void action() {
System.out.println("------------主要业务代码,不可改变");
}
public static Handler getInstance() {
Handler easy=new Easy();
Proxy proxy=new Proxy();
//将被代理对象设置到代理对象中
proxy.handler=easy;
return proxy;
}
public static void main(String[] args) {
Handler proxy=getInstance();
proxy.action();
}
}
AOP:
定义
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,使用AOP进行编程,可以降低代码的侵入性,提高程序的可重用性,同时提高了开发的效率.**简单来说,aop是一种是一种思想和和规范,通过选择不同方法(可以在不同类)在不同时机(方法执行前、执行后、返回前后、抛出异常后…),对选择的方法统一添加处理逻辑。**它只实现了对方法的增强,没有实现对属性的增强
作用:
它可以在不改变原有代码的情况下,动态地将额外的代码(称之为“切面”)横向地“切入”到原有代码流程中的特定位置,从而达到增强原有代码的目的。(动态代理的作用)
核心知识点
切面类:定义一个类来组织方法增强的逻辑(内部由切点和通知组成)
切点(PointCut):定义要增强的方法(确定增强的范围)
Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述)来匹配 Join Point,给满足规则的 Join Point 添加 Advice
通知:定义方法增强的实现逻辑(定义何时增强以及如何增强)
定义了切面是什么,何时使用,其描述了切面要完成的工作,还解决何时执行这个工作的问题。
应用:
控制器:
package com.easy.controller;
@Controller
public class EasyHController {
@RequestMapping("easyh")
@ResponseBody
public String easyh() {
return "easyh";
}
}
切点:
@Component
@Aspect
public class EasyCut {
//定义切点
//定义一个切点,就会在下面路径中的方法切入
@Pointcut("execution(public String com.easy.controller.EasyHController.easyh(..))")
public void cutA() {
}
//通知,代表要将下面的方法加入到cutA()这个切点(路径中的方法)中去
@Before(value = "cutA()")//前置通知
public void EasyBeforeA() {
System.out.println("-----------EasyBeforeA");
}
}
切点会找到注解:@pointcut后面跟的路径的方法,然后根据通知将通知修饰的方法加入到切点对应的方法中去.
通知:
- Before advice(前置通知):连接点前面执行,不能终止后续流程,除非抛异常
- After returning advice(后置通知):连接点正常返回时执行,有异常不执行
- Around advice(环绕通知):围绕连接点前后执行,也能捕获异常处理
- After advice(最终通知):连接点退出时执行,无论是正常退出还是异常退出
- After throwing advice(异常通知):连接点方法抛出异常时执行
动态SQL
查询:
查询有两种方式,一种是拼接字符串,另一种是预编译(有返回值,有参数)
用到的SQL标签:
where,if,bind,choose-when-otherwise
拼接字符串:
- mapper:
在mapper同一作用域下,id是不可重复的
id值必须等于对应接口中的对应方法名
resultType是指每一行数据要封装成的类型; Mybatis内置类型:int String map
parameterType是指向sql语句中传入参数的类型,一般用全类名表示类型
‘%${xx}%’
resultType:返回值类型
parameterType:参数类型
<mapper name="com.easy.dao.IStaffDao">
<select id="searchList" resultType="com.easy.bean.Staff" parameterType="com.easy.bean.Staff">
select * from t_staff
<where>
<if test="name!=null">
<!--使用${name}以字符串拼接的形式将参数中的name值放在指定位置-->
name like '%${name}%'
</if>
</where>
</select>
</mapper>
- 接口类:
select中id的值与接口的方法对应,该接口还需要用注解:@Mapper修饰,
表示将该类放进spring容器进行管理(在控制类使用DI),也表示该接口和mapper文件那边进行映射呼应
@Mapper
public interface IStaffDao {
List<Staff> searchList(Staff staff);
}
- 控制类
@Controller
public class EasyHController {
@RequestMapping("searchlist")
@ResponseBody
public List<Staff> searchList(Staff staff){
System.out.println(staff);
return staffD.searchList(staff);
}
}
在浏览器输入地址:localhost:8080/searcherlist后,看控制台打印的SQL语句:
预编译:
就将mapper中修改即可:
bind标签:对原来的代码或参数进行再拼接
将%name%这样的模糊匹配拼接后作为一个nametext的值
然后#{nametext}:表示占位符?
<if test="name!=null">
<!-- name like '%${name}%'-->
<bind name="nametext" value="'%'+name+'%'"></bind>
name like #{nametext}
</if>
再来看看SQL语句:
将?换为parameters:那么SQL语句就是:
select * from t_staff WHERE name like %王%(String);
choose-when-otherwise
这就相当于if-else if-else;
- mapper:
- 在上面的查询中继续写
<choose>
<when test="name eq '在职'">
and age>1
</when>
<when test="name eq '休假'">
and age>2
</when>
<when test="name eq '调休'">
and age>3
</when>
<otherwise>
and age>0
</otherwise>
</choose>
我的路径只进行了name模糊查询,没有所以没有name=以上eq后面的值,所以执行了otherwise语句,在原来的sql语句后面增加了age>0
我将name=休假写在地址中,没有模糊匹配到数据,然后满足以上的休假,所以会在SQL语句后面添加age>3;
插入:
向表中直接插入指定字段:
- mapper:
这里会碰到遍历**:foreach**
separator:循环一遍后添加一个分隔符:
就像:insert into xx表 values (),(),(); ()之间的,就是这里separator定义
collection:就是foreach循环的容器,这里遍历的容器名为items,该容器在接口类中使用注解:@Param定义
<insert id="addstaff">
insert into t_staff(name,age,tel,height) values
<foreach separator="," collection="items">
('zhangsan',11,'15730629595',1.88)
</foreach>
</insert>
- 接口类:
使用注解:@Param将list集合对象传给mapper中的去遍历,该集合中有几个对象,那么mapper中的sql语句就执行几次,那么这个对象里面是从controller传过来的
@Mapper
public interface IStaffDao {
int addstaff(@Param("items")List list);
}
控制类:
该方法返回的是一个int型,返回的是几,就会执行几遍sql语句
@Controller
.....
@RequestMapping("addstaff")
@ResponseBody
public int addstaff(){
List list=new ArrayList();
Staff staff=new Staff();
list.add(staff);
list.add(staff);
list.add(staff);
return staffD.addstaff(list);
}
....
那么当我访问addstaff时,就会往数据库添加三条数据:
从元素中取值:
上面插入的数据是固定的,我们可以取对象的值然后加入到表中
- mapper:
- item:"temp"将每一个元素作为temp,然后取出每个temp的值添加到数据库中
- 控制类:
@RequestMapping("addstaff")
@ResponseBody
public int addstaff(){
List list=new ArrayList();
Staff staff=new Staff();
list.add(staff);
list.add(staff);
list.add(staff);
return staffD.addstaff(list);
}
trim标签:
可以当做where来使用,添加前缀后缀:
<insert id="addstaff">
insert into t_staff(name) values
<foreach separator="," collection="items" item="temp">
<trim prefix="(" suffix=")">
#{temp.name}
</trim>
</foreach>
</insert>
修改:
查询用where,修改就用set,一样的道理进行字段判断
<update id="edit">
update t_staff
<set>
<if test="name!=null">
name=#{name},
</if>
<if test="tel!=null">
tel=#{tel},
</if>
</set>
</update>
获取自增主键
向数据库中添加对象,我们不传入主键,让主键自增,然后将对象传入到数据库中
- mapper:
- 表中id为主键,我们添加除了id的其他列,(#{})是占位符?,
<insert id="add">
insert into t_staff (name,age,height,tel) values
(#{name},#{age},#{height},#{tel})
</insert>
- 接口类:
返回值为int,int为几,就执行几遍sql语句,参数必须为封装类型的对象
@Mapper
public interface IStaffDao {
int add(Staff staff);
}
- 控制类:
add(中的参数是获取前台的参数,jiusSpringMVC接收前台参数的三种方式,这里最好使用封装对象来接收前台的参数(就是地址后面?后的数据))
@Controller
...
@RequestMapping("add")
@ResponseBody
public int add(Staff staff) {
System.out.println(staff);
int result=staffD.add(staff);
System.out.println(staff);
return result;
}
...
此时的话,我们没有让id这列自增,只是向其他列添加数据了
输入地址;
看控制台:
两次打印的对象id都为空,第一次打印的是从前台获取的数据,第二次打印的是将数据加入到数据的对象数据,没有变化,id没有自增
想要自增,需要在mapper类中进行属性的添加:
useGeneratedKeys=“true”:表示开启主键自增
keyColumn=“id” :表示数据库中的哪个列是自增的
keyProperty=“id”:表示,自增长的主键,要放到我们封装对象中的哪个属性中去
<insert id="add" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into t_staff (name,age,height,tel) values
(#{name},#{age},#{height},#{tel})
</insert>
结果如下:
一对一的处理
association标签,result标签,resultMap标签
两张表:
Goods:
type:
一对一就是Goods表中type_id的值与type中的id的值相同则对应type中的一行数据,这是goods中的一行对应着type中的一行
不难发现,a表的type_id和b表的id是存在联系的,我们可以根据a表的type_id的值获取到b表的数据;这就是一对一的处理
首先我们需要定义两个javabean,
goods表:
多写了一个属性,该属于可以理解为要展示另一张表的所有内容
public class Goods {
private Integer id;
private String name;
private Integer type_id;
private Type type;}
type表:
public class Type {
private Integer id;
private String name;}
- 控制层:
@Controller
...
@Autowired
IGoodsDao goodsDao;
@RequestMapping("goodslist")
@ResponseBody
public List<Goods> list(){
return goodsDao.goodslist();
}
- 接口层:
@Mapper
public interface IGoodsDao {
List<Goods> goodslist();}
- mapper层:
select * from goods
此时我们就查询出goods表中的所有数据,但是此时是没有type表的数据的:
下面我们就开始进行一对一的表关联:
第一种方法:
我们只需要再mapper层书写,其他不用变:
resultMap:id是一种标识,我用select中的sql语句时,就需要用goodsMap这种规范去进行封装,而不是像原来直接写个Goods的全类名进行封装;
type类中最终还是封装成Goods的全类名类型
Property:查询的数据要放到哪个属性中去,这里查询的数据就要放到Goods中的type属性中去
column:要用数据库中哪一列去查询,这里就是用type_id,因为type_id和type表中的id存在关联
**select:**怎么去查询我要的数据(书写SQL语句,将结果作为我Goods对象中type的值)
select中的resultMap就是指定resultMap作为我执行SQl语句的封装规则,具体封装规则得看resultMap中怎么规定
<!--
不进行一对一就是这样的,我们结果集类型封装成Goods了,需要改变,我们要按照指定的规范进行封装
<select id="goodslist" resultType="com.easy.bean.Goods" >
-->
< resultMap type="com.easy.bean.Goods" id="goodsMap" >
<association property="type" column="type_id" select="goodstype"></association>
</resultMap>
<select id="goodslist" resultMap="goodsMap" >
select * from goods
</select>
//书写resultMap中的select语句:结果作为Goods类中type的值
<select id="goodstype" resultType="com.easy.bean.Type">
select * from type where id=#{type_id}
//type_id是Goods中的值
</select>
流程:通过 select * from type where id=#{type_id}查询出指定的分类信息(Goods中的type),将 这个select语句的id放到association中的select中去,然后封装到property的值中去(Goods中的type值),那么type值就是整个type表的数据了;
这样type属性就有值了,那么我们会发现type_id又为空了,因为我们把type_id拿去查询了,所以我们要添加上:
这样就完成了一对一的操作;
表连接处理一对一:
Mapper:
在association中,就是处理一对一了,
javatype声明了一个type为com.easy.bean.Type类,property表示javabean中的属性,column表示的是数据库中的列名,所以总之就是将数据的值赋给javabean中的属性,具体是哪个javabean就看type类型
select语句还是不能定义为全类名的包,用resultMap去
<resultMap type="com.easy.bean.Goods" id="goodsJoinMap">
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="type_id" property="type_id"></result>
<association property="type" javaType="com.easy.bean.Type">
<result column="typeid" property="id"></result>
<result column="typename" property="name"></result>
</association>
</resultMap>
<select id="goodsJoinList" resultMap="goodsJoinMap">
select a.*,b.id as typeid,b.name as typename from goods as a left join type as b on a.type_id=b.id
</select>
一对多的处理
方式一:SQL查询
collection,result,resultMap
两张表:
Goods:
type:
还在这两张表,一对多则就是站在type表中,type表中的id值对应着goods表中的type_id为id的所有行的数据,比如type中的id为1,对应在goods表中,就对应着type_id为1的行,很显然就是三行,这就是一对多的理解。
既然是站在type表中,现在书写javabean,type表中应该有个属性来表示goods表中的多个数据,所以应该是集合加Goods的泛型
type:
private Integer id;
private String name;
List<Goods> goodslist;
Goods:
private Integer id;
private String name;
private Integer type_id;
private Type type;
然后又是熟悉的三件套:controller,dao,mapper.xml
- 接口类(dao):
@Mapper
public interface ITypeDao {
List<Type> typeList();
}
- 控制类:
@Controller
public class TypeController {
//依赖注入
@Autowired
ITypeDao typeDao;
@RequestMapping("typelist")
@ResponseBody
public List<Type> typelist(){
return typeDao.typeList();
}
}
- mapper层
解读:
namespace属性表示接口类的全类名,接口层中有个注解**@mapper,namespace**与其形成映射建立链接
代码书写顺序:123
1:id为接口层中的方法, resultMap:不再是resultType,对应着resultMap标签中id的值;
2.type:类型,是1sql语句的javabean的类型全类名。 id和1中的resultmap相呼应
result标签标识单独将数据库中的一列数据column加到javabean中的属性中;
collection:是一对多的时候使用,一对一的时候是使用association标签;
property是表示javabean中的属性;column表示数据库中的字段,selest表示另一个sql语句的引用
3**.id**表示上面select中的另一个sql引用的值,result表示sql语句查询的表的对应的javabean的全类名;
<mapper namespace="com.easy.dao.ITypeDao">
<!--2-->
<resultMap type="com.easy.bean.Type" id="typeMap">
<!-- colleation中的column为id,将id拿到id=goodstype中去查询了,所有显示不了id,我们需要使用result标签加上id的值-->
<<result column="id" property="id"></result>
<collection property="goodslist" column="id" select="goodstype"></collection>
</resultMap>
<!--1-->
<select id="typeList" resultMap="typeMap">
select * from type
</select>
<!--3-->
<select id="goodstype" resultType="com.easy.bean.Goods">
select * from goods where type_id=#{id};
</select>
</mapper>
方式二:表链接:
我们只需要更改mapper层中的代码:
- mapper:
<!-- 对应接口的全名-->
<mapper namespace="com.easy.dao.ITypeDao">
<resultMap type="com.easy.bean.Type" id="typeMap">
<!--id标签声明的列,一样的值就为一列-->
<id column="typeid" property="id"></id>
<result column="typename" property="name"></result>
<collection property="goodslist" javaType="java.util.List" ofType="com.easy.bean.Goods">
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="type_id" property="type_id"></result>
</collection>
</resultMap>
<select id="typeList" resultMap="typeMap">
select a.*,b.id as typeid,b.`name` as typename from goods as a right join type as b on a.type_id=b.id;
</select>
</mapper>
懒加载
Mybatis的缓存机制
缓存:
查询一次SQL语句之后,将查询的结果存储起来,当进行相同的查询时,直接回应上一次查询的结果,就不需要在执行一次SQL语句
**mybatis默认开启一级缓存,一级缓存是SQLSession级别。
一级缓存(只对查询有用):
浏览器发送请求到controller,然后controller去调用连接池对象,连接池对象去执行一条条的Sql语句,执行Sql语句就是一个Sql会话,要使用一级缓存就必须要确保 同一个过程(一次压栈),使用同一个连接对象,执行**相同的SQL语句,**就可以使用一级缓存,例如在事务中。
缓存失效:
- 缓存超时;
- 执行了增删改任意一个操作;
怎样不使用一级缓存:
可以看使用缓存的条件,就有同一个过程,同一个链接对象和同一个SQL语句,我们从SQL语句入手,可使用随机数,拼接SQL语句,确保每次执行的SQL语句都是不相同的;
二级缓存:
二级缓存默认不开启,是Mapper级别,在Mapper.xml文件中添加**标签**,就对这个mapper开启了二级缓存,可以在整个文件全局开启这个二级缓存,也可以只对某一个标签中useCatch=true对单个标签开启二级缓存,并且还需要该对象的实体类是可序列化的。
yml文件使用
Restful请求风格
把每一个数据的请求都当做一个路径来处理,每一个资源对应着一个地址,就请求方式不一样(在ajax中的method中可以使用)
- POST-新增用户
- DELETE:删除用户
- PUT:修改用户
- Get-获取用户
那么对应的请求方式SpringMVC对应着四种请求的映射地址(注解):
- @PostMapping
- @DeleteMapping
- @PutMapping
- @GetMapping
在处理表单请求时,因为method只有post和get两种方式,在Spring框架中规定,用post请求来模拟delete和put请求 ,然后我们需要在表单中声明一个隐藏域:_method:delete/put,让post去执行_method的请求方式 :
input的输入框就是隐藏域;
没有对动作的区分,只有对Goods的处理;
获取(get)
注解:@RestController:用来接收所有的Rest请求
Rest请求就是专门来做前后端分离的,在Rest请求中没有跳转路径这一说,只会把我们的数据返回到前端去,相当于所有的Resuest,Mapping上面都加上的@ResqonseBidy注解
@GetMapping:请求方式,(“goods/{id}”:表示请求路径是goods,/{id}表示二级路径带了个参数id值
@PathVariable注解表示来接受地址栏输入的参数,也就是上面的id值,我们将id值作为参数用来查询
- 控制类:
@RestController:标识Testt为一个控制器类,并且该类的所有方法默认使用@ResponseBody标注
@RestController
//这个类所有的请求都默认加上了@RequestBody注解
public class Testt {
@Autowired
IGoodsDao goodsDao;
@GetMapping("goods/{id}")
public Goods getGoods(@PathVariable int id) {
System.out.println(id);
//查询数据库
return goodsDao.getGoodsByID(id);
}
- Dao层
@Param(“id”):标识参数int型的id标识为id,mapper层获取该参数可通过名字id获取,只有一个参数可以不用,多个参数需要标识每一个参数
@Mapper
public interface IGoodsDao {
Goods getGoodsByID(@Param("id")int id);}
- Mapper:
id=#{id},#{}相当于占位符?,id表示接口中的方法的参数值,将参数值作为sql语句查询条件
<!-- 对应接口的全名-->
<mapper namespace="com.easy.dao.IGoodsDao">
<select id="getGoodsByID" resultType="com.easy.bean.Goods">
select * from goods where id =#{id}
</select>
</mapper>
那么我们在浏览器输入地址,获取数据库id为的数据:
SQL语句:
?就等于Parameters的值-----1;
添加(Post)
- 控制类:
相当于一个Servlet,ajax的url会发送数据(goods对象)来这里处理(将goods对象添加到数据库中[这里就是mapper中实现]);
@RequestBody:不加该注解,表示接受表单传过来的数据,用来接收ajax传过来的数据,此时那边传过来的数据类型是json数据,想要接收json类型的数据就需要该注解。
@RestController
//这个类所有的请求都默认加上了@RequestBody注解
public class Testt {
@Autowired
IGoodsDao goodsDao;
@PostMapping("goods")
//@RequestBody表示接收ajax那边的信息,我们这边接收的消息需要用该注解
public Goods saveGoods(@RequestBody Goods goods) {
System.out.println(goods);
goodsDao.saveGoods(goods);
return goods;
}
- DAO层:
@Param标识一个对象,那么mapper获取参数时,必须要使用goods.属性(name),没有该注解的话,那么直接使用.(属性name )
@Mapper
public interface IGoodsDao {
int saveGoods(@Param("goods")Goods goods);}
- Mapper
id:为dao层中的方法名;
useGeneratedKeys:开启主键自增
keyColumn:主键列名(数据库中的)
keyProperty:javabean中的属性(对应数据库中的主键)
#{goods.name}:获取dao方法中带的参数,由于当时使用了注解@Param注解,所以需要注解值.属性获取值
<mapper namespace="com.easy.dao.IGoodsDao">
<insert id="saveGoods" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into goods (name,type_id) values(#{goods.name},#{goods.type_id})
</insert>
</mapper>
- jsp:
点击按钮,发送ajax请求,将自定义的goods数据以post请求(添加)方式发送给goods控制层去;
跨域的restful请求,并不能用表单去发送数据。而是要发送json数据,并且要将json数据放到请求体中去发送;
contentType:指定我们传输的数据类型
‘application/json’:我们传递的数据是json数据
data:表示传输的数据
JSON.stringify(goods):将传送的数据(这里是goods对象)转为json数据
...
<button type="button" id="save">保存货品</button>
<!-- 导入js -->
<script type="text/javascript" src="jquery-3.7.1.min.js"></script>
<script type="text/javascript">
// 发送请求保存goods信息
$("#save").click(function(){
var goods={name:"方便面",type_id:1}
$.ajax({
url:"goods",
method:'post',
contentType: 'application/json',
data:JSON.stringify(goods),
success:function(result){
console.log(result);
}
});
});
</script>
运行流程:输入地址进入jsp界面,点击添加按钮,然后将goods对象发送到url地址中去,来到controller,controller接收ajax的数据,然后调用接口层dao中的方法,dao中的方法又与mapper中的select存在映射,所以最终将ajax传过来的对象作为了mapper中insert中的参数添加到数据库中;
修改(put):
- jsp:
将id为10的商品名称和类型编号修改为火腿肠和1
...
<button type="button" id="save">保存货品</button>
<!-- 导入js -->
<script type="text/javascript" src="jquery-3.7.1.min.js"></script>
<script type="text/javascript">
$("#edit").click(function(){
var goods={name:'火腿肠',type_id:1,id:10};
$.ajax({
url:"goods",
method:'put',
contentType: 'application/json',
data:JSON.stringify(goods),
success:function(result){
console.log(result);
}
});
});
</script>
- 控制类
@RestController
//这个类所有的请求都默认加上了@RequestBody注解
public class Testt {
@Autowired
IGoodsDao goodsDao;
@PutMapping("goods")
public Goods editGoods(@RequestBody Goods goods) {
System.out.println(goods);
goodsDao.editGoods(goods);
return goods;
}
}
- Dao
@Mapper
public interface IGoodsDao {
int editGoods(Goods goods);}
- mapper
dao层中的方法参数没有使用注解@Param.所以不需要xx.属性,直接属性即可
<update id="editGoods">
update goods set name=#{name},type_id=#{type_id} where id=#{id}
</update>
效果:
删除(delete)
- jsp
删除id为10的数据
$("#del").click(function(){
$.ajax({
url:"goods/10",
method:'delete',
success:function(result){
console.log(result);
}
});
});
- controller:
@DeleteMapping("goods/{id}")
public Goods delGoods(@PathVariable int id) {
//回写数据,查看删除的是谁
Goods goods =goodsDao.getGoodsByID(id);
goodsDao.deleteGoods(id);
return goods;
}
- Dao:
int deleteGoods(int id);
- mapper:
<delete id="deleteGoods">
delete from goods where id=#{id}
</delete>
@Autowired和@Resource的区别:
区别:
1.@Autowired是Spring的注解,@Resource是j2ee的注解
2.@Autowired默认使用byType的方式查找指定的bean,就是看类型EasyAService,如果找不到单独对应的bean就会报错**@Autowired可以结合**@Qualifier注解通过ByName的方式查找指定bean,如果找不到就报错。
3.@Resource默认使用byName的方式查找指定的bean,如果找不到,就使用byType的方式查找,以byName方式查找如果还找不到或者找到多个就会报错
Spring事务处理
Spring对事物的处理大多数情况是在Service层情况下处理的,Service是一个业务,一个业务就是一个事务,在一个事务上需要调用好几次dao的方法,这几个dao的方法就当做一个事件来处理
现在我要往数据库两张表添加数据,圈出来的部分就是联系
首先已知的思想路线,在controlle中定义访问路径,紧跟的方法中调用Service中的方法,事务是在Service中实现的,并且service中调用接口(dao)中的多个抽象方法合为一个事务,然后dao又和mapper有映射关系,mapper中则是实实在在的sql语句。
- Controller
依赖注入easyB(Service),调用service中的方法
@RestController
public class EasyIController {
@Resource
EasyBService easyB;
@RequestMapping("addinfo")
public String addInfo() {
easyB.addinfo();
return "success";
}
}
- Service
该类是书写事务的类,就是会多次调用接口中的抽象方法,看做一个整体,此时我们先别开启事务,看看效果;
依赖注入,引入dao接口,然后在类方法(addinfo)中调用dao中的方法
@Service
public class EasyBService {
@Autowired
IGoodsDao goodsDao;
public void addinfo() {
//添加type表的数据
Type type=new Type();
type.setName("学习用品");
//1.新增分类表的数据
//使用dao添加type对象
goodsDao.addType(type);
// int a=12/0;
//添加goods表的数据
Goods goods=new Goods();
goods.setName("英语课本");
goods.setType_id(type.getId());
//2.新增商品表的数据
//使用dao添加goods对象
goodsDao.addGoods(goods);
}
- dao层
该类中的方法在Service中同时被使用
//定义添加数据的方法
int addType(Type type);
int addGoods(Goods goods);
- Mapper
写实实在在的Sql语句,与dao中对应
<insert id="addType" useGeneratedKeys="true" keyProperty="id">
insert into type(name) values (#{name})
</insert>
<insert id="addGoods">
insert into goods (name,type_id) values (#{name},#{type_id})
</insert>
此时我们只是在Service中的addinfo中同时调用了dao中的两个方法,但是没有将该方法作为一个事务,所以结果应该为数据库中的两张表都会添加成功(都没问题)
接下来我们添加事务来看看:
事务:多条SQL语句组成的集合,要么都成功要么都执行失败
那么再次提醒:事务是在Service中使用,所以我们只需要在Service中修改即可:
- Service:
Spring使用事务只需要注解:@Transactional即可
让所有SQL语句都执行同一个connection,MYSQL中每一个表都可以指定存储引擎,该表的存储引擎如果不支持事务,@Transactional也会失效(支不支持事务由存储引擎决定)
@Transactional
public void addinfo() {
Type type=new Type();
type.setName("学习用品");
goodsDao.addType(type);
Goods goods=new Goods();
goods.setName("英语课本");
int a=10/0;//11111111111111111111这里有异常
goods.setType_id(type.getId());
goodsDao.addGoods(goods);
}
不难发现,该事务中有错误,那么整个事务都不会执行,即使type对象执行中没问题,最后结果是表中都没添加进去数据;
事务回滚:
@Transactional也并不是说我所有异常都需要回滚,因为有时候会存在在一个方法中,我数据库的操作已经实现完了,是后面的代码出现问题,那这个时候我就不需要回滚,那此时我们就可以在Transcational后面指定哪种异常我们就回滚:
事务失效
在Service内部调用事务方法,被调用的事务方法可能会失效
//调用事务的方法
public void addinfo2() {
this.addinfo();
}
@Transactional()
public void addinfo() {
Type type=new Type();
type.setName("学习用品");
.....
spring是通过动态代理的方式实现对事物的处理
想要让事务生效必须调用动态代理的处理方法
但是动态代理执行的还是原生类对象的方法 当我们执行addInfo2(addinfo沒有@transactional,那就不是动态代理的方法而是原生态的方法)的时候,this那就是原生类的对象
在这里调用addinfo方法就是用原生类对象调用自身的方法,并不是动态代理的方法所以事务会失效
Spring事务的传播行为
前提:两个事务或多个事务之间
-
REQUIRED
(Spring默认的事务传播类型 required:需要、依赖、依靠):如果当前没有事务,则自己新建一个事务,如果当前存在事务则加入这个事务
当A调用B的时候:如果A中没有事务,B中有事务,那么B会新建一个事务;如果A中也有事务、B中也有事务,那么B会加入到A中去,变成一个事务,这时,要么都成功,要么都失败。
当AB都是事务(@Transactional注解修饰),A中调用B的方法,B中有错误,那么A会受影响,这就是传播行为,那么数据库AB的数据都没加入进去;
当A(调用者 )是不是事务,B(调用者)是事务,然后A中调用了B,
如果A当中有事务,那么B事务会加入进来,然后合为一个事务进行处理 ;
如果A不是事务,那么B事务会单独开启一个事务,自己去处理;
- SUPPORT
(supports:支持;拥护):当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
如果A中有事务,则B方法的事务加入A事务中,成为一个事务(一起成功,一起失败),如果A中没有事务,那么B就以非事务方式运行(执行完直接提交)
上面是B事务的代码,A中会调用B,B中的传播行为变为SUPPORT,B主要看调用B的方法(A),看A是否存在事务,如果A有事务则B加入到A中,合为一个事务,如果A没有事务。那B就以非事务运行