【面试】框架
框架
- 1、介绍一下Spring 的 IOC
- 2、将一个类声明为 Bean 的注解有哪些
- 3、Bean 的作用域有哪些
- 4、Spring 框架中的 Bean 是线程安全的吗
- 5、Spring 容器对象的懒加载
- 6、Spring 容器中的 bean 生命周期
- 7、谈谈自己对于 Spring DI 的了解
- 8、注入 Bean 的注解有哪些
- 9、Spring Boot 如何让你的 Bean 在其他 Bean 之前加载
- 10、Spring 中出现同名 Bean 怎么办
- 11、什么是 Spring 的循环依赖问题
- 12、介绍一下 Spring 的 AOP
- 13、Spring 通知有哪些类型
- 14、多个切面的执行顺序如何控制
- 15、Spring 事务中哪几种事务传播行为
- 16、Spring 中的设计模式
- 17、BeanFactory 和 FactroyBean 的关系
- 18、Spring MVC 工作原理
- 19、 Spring MVC 常用的注解有哪些
- 20、Spring MVC 是如何将不同的 Request 路由到不同 Controller 中的
- 21、Spring MVC 里面拦截器是怎么写的
- 22、什么是 MyBatis
- 23、#{ } 和 ${ } 的区别是什么
- 24、MyBatis 是怎么解决实体类中的属性名和表中的字段名不一样的问题
- 25、如何在 Mapper 中传递多个参数
- 26、MyBatis 的接口绑定是什么,有哪些绑定方式
- 27、在MyBatis 中使用 Mapper 接口开发时有哪些要求
- 28、MyBatis 中 Mapper 接口中的方法支持重载么
- 29、MyBatis 的动态SQL是什么,主要标签有哪些
- 30、MyBatis 的 Mapper 接口工作原理是什么
- 31、MyBatis 的工作原理是什么
- 32、MyBatis 中一对一查询、一对多查询是怎么实现的
- 33、MyBatis 的分页方法有哪些
- 34、MyBatis 中都有哪些Executor执行器,它们之间的区别是什么
- 35、MyBatis 是否支持延迟加载,如果支持,它的实现原理是什么
- 36、MyBatis 是如何防止sql注入的
- 37、SpringBoot + Mybatis 一级缓存和二级缓存详解
- 38、Springboot 自动装配
- 39、Spring Boot 常用注解
1、介绍一下Spring 的 IOC
所谓的 IOC,就是控制反转的意思。何为控制反转?我们可以根据字面意思理解,就是对于某个东西A,原来的控制权在使用方B,B想用就能用,不想用就不用。现在把控制权交还给了A,只有A给了才能用,这样就是控制反转了。
可能说的有点抽象,更具体一点呢,我们拿代码来说话:
class A {}class B {// B需要将A的实例new出来,也就是我们说的控制private A a = new A();public void use() {System.out.print(a);}
}
当有了IOC后
@Component // 说明A自己控制自己,把自己初始化出来,注入给了容器
class A {}class B {// B不需要控制a,直接使用。如果A没有把自己注入给容器,B就不能使用@Resourceprivate A a;public void use() {System.out.print(a);}
}
也就是说,没有Spring的话,我们要使用的对象,需要我们自己创建,而有了Spring的IOC之后,对象由IOC容
器创建并管理,我们只需要在想要使用的时候从容器中获取就行了。
2、将一个类声明为 Bean 的注解有哪些
- @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
- @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
- @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
- @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层使用。
3、Bean 的作用域有哪些
Spring 中 Bean 的作用域通常有下面几种:
- singleton : 只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例,连续 getBean() 两次,得到的是不同的 Bean 实例。
- request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置 bean 的作用域呢
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {return new Person();
}
单例和多例优缺点
- 单例: 所有请求用同一个对象来处理,通过单例模式,可以保证系统中一个类只有一个实例。
- 多例:每一个请求用一个新的对象来处理。
- 单例优点降低了实例创建和销毁所占用的资源,缺点线程共享一个实体,会发生线程安全问题。
- 多例线程之间数据隔离,所以不会有线程安全问题,但是频繁的实例创建和销毁会带来资源的大量浪费。
4、Spring 框架中的 Bean 是线程安全的吗
平常的bean它分为单例bean和多例bean。单例bean创建一个全局共享的实例,多例bean使用时候创建一个新的对象,线程之间不存在bean共享的问题,单例bean是所有线程共享一个实例,这样单例bean就可能会产生线程安全问题。但是也不是绝对的。单例bean分为有状态有无状态两种。
- 有状态Bean:多线程操作中如果需要对bean中的成员变量进行数据更新操作,是非线程安全。
- 无状态Bean:多线程操作中没有成员变量或者只会对bean成员变量进行查询操作,不会修改操作,是线程安全(比如 Controller、Dao、Service等)。
处理有状态的单例bean线程安全问题,常见的有两种解决办法:
- 改为多例bean
- 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)
5、Spring 容器对象的懒加载
意思为控制对象的创建的时机,如果Spring容器创建,对象立即创建,则称为立即加载。如果 Spring 容器创建,对象在被使用的时候创建,则称为懒加载。
注解:@Lazy 表示为懒加载
懒加载XML写法
<!-- 懒加载的局部的写法 -->
<bean id="hello" class="com.ioc.Hello" scope="singleton" lazy-init="true"></bean>
<!-- 懒加载的全局的写法 -->
<beans default-lazy-init="true"></beans>
lazy-init 是否懒加载 | scope 单例多例 | 对象的创建结果 |
---|---|---|
true | singleton | 懒加载 |
true | prototype | 懒加载 |
default/flase | singleton | 立即加载 |
default/flase | prototype | 懒加载 |
只要对象是多例模式 则都是懒加载! 在单例模式中控制懒加载才有效
6、Spring 容器中的 bean 生命周期
+----------------------------+
| 实例化 (Instantiation) |
+----------------------------+|v
+-------------------------------+
| 属性赋值 (Populate Properties) |
+-------------------------------+|v
+----------------------------------+
| BeanNameAware 回调 (setBeanName) |
+----------------------------------+|v
+---------------------------------------+
| BeanFactoryAware 回调 (setBeanFactory) |
+---------------------------------------+|v
+-------------------------------------------------------------+
| BeanPostProcessor 前置处理 (postProcessBeforeInitialization) |
+-------------------------------------------------------------+|v
+---------------------------------------+
| 初始化 (Initialization) |
| - @PostConstruct |
| - InitializingBean.afterPropertiesSet |
| - init-method |
+---------------------------------------+|v
+------------------------------------------------------------+
| BeanPostProcessor 后置处理 (postProcessAfterInitialization) |
+------------------------------------------------------------+|v
+------------------------------+
| Bean 就绪 (Ready) |
+------------------------------+|v
+------------------------------+
| 销毁 (Destruction) |
| - @PreDestroy |
| - DisposableBean.destroy |
| - destroy-method |
+------------------------------+|v
+------------------------------+
| 垃圾回收 (Garbage Collection) |
+------------------------------+
- 实例化
Spring 容器根据配置(如 XML、注解或 Java Config)创建 Bean 的实例。实例化方式包括构造函数实例化、工厂方法实例化等。
- 属性赋值
Spring 容器根据配置为 Bean 的属性注入值(依赖注入),通过 @Autowired 注解自动注入。通过 XML 配置或 @Value 注解手动注入。
- Aware 接口回调
如果 Bean 实现了 BeanNameAware 接口,Spring 会调用 setBeanName() 方法,传入 Bean 的名称。如果 Bean 实现了 BeanFactoryAware 接口,Spring 会调用 setBeanFactory() 方法,传入 BeanFactory 实例。
- BeanPostProcessor 的前置处理
如果容器中注册了 BeanPostProcessor,Spring 会调用其 postProcessBeforeInitialization() 方法。开发者可以在此方法中对 Bean 进行自定义处理。
- 初始化(Initialization)
如果 Bean 实现了 InitializingBean 接口,Spring 会调用 afterPropertiesSet() 方法。如果 Bean 配置了自定义的初始化方法(通过 @PostConstruct 注解或 init-method 属性),Spring 会调用该方法。
- BeanPostProcessor 的后置处理
如果容器中注册了 BeanPostProcessor,Spring 会调用其 postProcessAfterInitialization() 方法。开发者可以在此方法中对 Bean 进行进一步处理。
- Bean 就绪(Ready)
此时 Bean 已经完成初始化,可以被应用程序使用。Bean 被放入 Spring 容器的单例池中(如果是单例作用域)。
- 销毁(Destruction)
如果 Bean 实现了 DisposableBean 接口,Spring 会调用 destroy() 方法。如果 Bean 配置了自定义的销毁方法(通过 @PreDestroy 注解或 destroy-method 属性),Spring 会调用该方法。
- 垃圾回收(Garbage Collection)
当 Bean 不再被引用时,JVM 的垃圾回收器会回收其占用的内存。
7、谈谈自己对于 Spring DI 的了解
从spring容器中取出容器中的对象,然后把对象注入到需要的地方。
Spring支持哪些注入方式
- 字段注入
@Autowired
private Bean bean;
- 构造器注入
@Component
class Test {private final Bean bean;@Autowiredpublic Test(Bean bean){this.bean bean;}
}
- setteri注入
@Component
class Test {private Bean bean;@Autowiredpublic void setBean(Bean bean){this.bean bean;}
}
使用构造器注入可能有哪些问题
如果我们两个bean循环依赖的话,构造器注入就会抛出异常:
@Component
public class BeanTwo implements Bean{Bean beanone;public BeanTwo(Bean beanone){this.beanOne beanone;}
}@Component
public class Beanone implements Bean{Bean beanTwo;public BeanOne(Bean beanTwo){this.beanTwo beanTwo;}
}
如果两个类彼此循环引用,那说明代码的设计一定是有问题的。如果临时解决不了,我们可以在某一个构造器中加
入@Lazy注解,让一个类延迟初始化即可。
@Component
public class Beanone implements Bean{Bean beanTwo;@Lazypublic Beanone(Bean beanTwo){this.beanTwo beanTwo;}
}
8、注入 Bean 的注解有哪些
Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。
Annotaion | Package | Source |
---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-250 |
@Autowired 和@Resource使用的比较多一些。
@Resource( name = “value” )
1. 按照名字装配Bean,即会按照name属性的值来找到具有相同id的Bean并注入。如果没有指定name属性(@Resource 样式),则会根据这个将要被注入的变量名进行注入(value)。如果变量名在容器中也不存在,就按照变量类型注入,如果类型不存在或者存在多个实现类情况下抛出异常。@Resource
private UserMapper userMapper;
@Autowired @Qualifier( “value” )
1.默认属性required= true(属性必须存储对象,不能为null,false可以),按照名字装配Bean,即会按照value值来找到具有相同id的Bean并注入。如果没有指定vaule值(@Autowired 样式),按照类型注入,类型不存在抛出异常,类型存在,如果类型只有一个实现类就按照类型注入,如果类型有多个实现类先按类型匹配再按变量名称匹配,再匹配不到抛出异常。@Autowired @Qualifier("yang")
private UserMapper userMapper;
作用域不同
- Autowired可以作用在构造器,字段,setter方法上
- Resource只可以使用在field,setter方法上
9、Spring Boot 如何让你的 Bean 在其他 Bean 之前加载
- 直接依赖某Bean
@Component
public class A {@Autowiredprivate B b;
}
如上,在加载Bean A的时候,一定会先初始化Bean B
- Dependson
对于应用之外的二方或者三方库来说,因为我们不能修改外部库的代码,如果想要二方库的Ben在初始化之前就
初始化我们内部的某个bean,就不能用第一种直接依赖的方式,可以使用@Dependson注解来完成。
@Configuration
public class BeanorderConfiguration {@Bean@Dependson("beanB")public BeanA beanA(){return new BeanA();}
}
10、Spring 中出现同名 Bean 怎么办
- 默认行为:覆盖
如果 Spring 容器中定义了多个同名的 Bean,默认情况下,后定义的 Bean 会覆盖先定义的 Bean。Spring 会抛出警告日志,提示存在同名 Bean。
@Bean
public MyBean myBean() {return new MyBean("Bean 1");
}@Bean
public MyBean myBean() {return new MyBean("Bean 2");
}
最终容器中只会存在一个 MyBean 实例,名称为 myBean,值为 “Bean 2”。
- 使用 @Primary 注解
如果有多个同类型的 Bean,可以通过 @Primary 注解指定一个主要的 Bean。当注入时未指定具体 Bean 名称时,Spring 会优先选择带有 @Primary 注解的 Bean。
@Bean
@Primary
public MyBean myBean1() {return new MyBean("Bean 1");
}@Bean
public MyBean myBean2() {return new MyBean("Bean 2");
}
当注入 MyBean 时,默认会使用 myBean1。
- 使用 @Resource 注解
默认按名称匹配,如果未指定名称,则按类型匹配。
@Bean
public MyBean myBean1() {return new MyBean("Bean 1");
}@Bean
public MyBean myBean2() {return new MyBean("Bean 2");
}@Resource(name = "myBean2")
private MyBean myBean;
- 使用 @Profile 注解
通过 @Profile 注解根据环境配置文件选择注册哪个 Bean。适用于多环境配置的场景。
@Bean
@Profile("dev")
public MyBean myBean1() {return new MyBean("Bean 1");
}@Bean
@Profile("prod")
public MyBean myBean2() {return new MyBean("Bean 2");
}
11、什么是 Spring 的循环依赖问题
Spring 中的循环依赖问题(Circular Dependency)是指两个或多个 Bean 相互依赖,形成一个闭环,导致 Spring 容器无法正常完成依赖注入。例如,Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A,从而形成循环依赖。
我们看一个简单的 Demo,对标“情况 2”。
@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}
Spring 如何处理循环依赖
Spring 通过三级缓存机制(三级缓存)来解决单例 Bean 的循环依赖问题。Spring 只能解决单例 Bean 的循环依赖问题,且仅限于通过 Setter 注入
或 字段注入
的情况。
一级缓存(Singleton Objects):存储完全初始化好的单例 Bean。二级缓存(Early Singleton Objects):存储提前暴露的 Bean(尚未完成属性注入和初始化)。三级缓存(Singleton Factories):存储 Bean 的工厂对象,用于生成提前暴露的 Bean。
- 当 Spring 容器创建 BeanA 时,会先将 BeanA 的工厂对象放入三级缓存。
- 在 BeanA 的构造函数中,发现需要注入 BeanB,于是开始创建 BeanB。
- 创建 BeanB 时,同样会先将 BeanB 的工厂对象放入三级缓存。
- 在 BeanB 的构造函数中,发现需要注入 BeanA,于是从三级缓存中获取 BeanA 的工厂对象,生成一个提前暴露的 BeanA(此时 BeanA 尚未完成初始化)。
- 将提前暴露的 BeanA 注入到 BeanB 中,完成 BeanB 的创建。
- 将 BeanB 注入到 BeanA 中,完成 BeanA 的创建。
如何避免循环依赖
- 使用 Setter 注入或字段注入
如果必须存在循环依赖,尽量使用 Setter 注入或字段注入,而不是构造函数注入
- 使用 @Lazy 注解:
在依赖的 Bean 上添加 @Lazy 注解,延迟加载 Bean,打破循环依赖。
@Component
public class BeanA {private final BeanB beanB;@Autowiredpublic BeanA(@Lazy BeanB beanB) {this.beanB = beanB;}
}
12、介绍一下 Spring 的 AOP
首先,在面向切面编程的思想里面,把功能分为核心业务功能和周边功能。
- 所谓的核心业务,工作中做的最多的就是增删改查,增删改查都叫核心业务。
- 所谓的周边功能,比如性能统计,权限检验,日志打印,事务管理等等。
核心业务功能和切面功能分别独立进行开发,在程序运行期间,在不修改核心业务的情况下,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。
Spring AOP 有如下概念
术语 | 翻译 | 释义 |
---|---|---|
Aspect | 切面 | 切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义,比如说事务处理和日志处理可以理解为两个切面 |
PointCut | 切入点 | 切入点是对连接点进行拦截的条件定义,决定通知应该作用于截哪些方法 |
Advice | 通知 | 通知定义了通过切入点拦截后,应该在连接点做什么,是切面的具体行为 |
Target | 目标对象 | 目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象 |
JoinPoint | 连接点 | 连接点是程序在运行时的执行点,这个点可以是正在执行的方法,或者是正在抛出的异常。因为Spring.只支持方法类型的连接点,所以在Spring中连接点就是运行时刻被拦截到的方法。 |
Weaving | 织入 | 织入是将切面和业务逻辑对象连接起来,并创建通知代理的过程。在编时进行织入就是静态代理,而在运行时进行织入则是动态代理 |
@Component
@Aspect
@Slf4j
public class AppGrantAop {private ObjectMapper objectMapper = new ObjectMapper();@Pointcut("execution(* com.sitech.ep.appgrant.svc.*.*(..))")public void pointcut() {}@Before("pointcut()")public void before(JoinPoint joinPoint) throws IOException {// 接收到请求,RequestContextHolder来获取请求信息,Session信息ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();log.info("URL : " + request.getRequestURL().toString());log.info("HTTP_METHOD : " + request.getMethod());log.info("IP : " + request.getRemoteAddr());log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."+ joinPoint.getSignature().getName());if ("POST".equals(request.getMethod())) {Object[] args = joinPoint.getArgs();log.info("HEADER : " + objectMapper.writeValueAsString(args[0]));log.info("INPUT_PARAM : " + objectMapper.writeValueAsString(args[1]));} else if ("GET".equals(request.getMethod())) {log.info("INPUT_PARAM : " + objectMapper.writeValueAsString(request.getParameterMap()));}}
}
AOP的动态代理技术
- JDK代理:基于接口的动态代理技术(默认,有接口时用)
- CGLIB代理:基于父类的动态代理技术(没接口时用)
jdk动态代理 和cglib动态代理的区别
- jdk动态代理目标业务类必须有接口,cglib动态代理业务类有无接口皆可。
- jdk动态代理必须实现InvocationHandler接口,cglib动态代理必须实现MethodInterceptor接口。
- jdk动态代理代理类和目标业务类是兄弟关系,因为隶属于同一个接口,cglib动态代理代理类和目标业务类是父子关系,业务类是父类,业务类不能是final类,代理类是子类。
- jdk动态代理创建代理类快,执行代理类慢,cglib动态代理创建代理类慢,执行代理类快。
Spring的AOP在什么场景下会失效
1、私有方法调用 2、静态方法调用 3、final方法调用 4、类内部自调用 5、内部类方法调用
13、Spring 通知有哪些类型
- 前置通知(Before Advice):在连接点(Join point)之前执行的通知。
- 后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
- 后置返回(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)。
- 后置异常通知(AfterThrowing advice):在方法抛出异常退出时执行的通知。
正常情况:环绕前置 ==> 前置通知@Before ==> 目标方法执行 ==> 后置返回通知@AfterReturning ==> 后置通知@After ==> 环绕返回 ==> 环绕最终
异常情况:环绕通知 ==> 前置通知@Before ==> 目标方法执行 ==> 后置异常通知@AfterThrowing ==> 后置通知@After ==> 环绕异常 ==> 环绕最终
14、多个切面的执行顺序如何控制
- 通常使用@Order 注解直接定义切面顺序
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
- 实现Ordered 接口重写 getOrder 方法。
@Component
@Aspect
public class LoggingAspect implements Ordered {// ....@Overridepublic int getOrder() {// 返回值越小优先级越高return 1;}
}
15、Spring 事务中哪几种事务传播行为
7种传播机制的约束条件
约束条件 | 说明 |
---|---|
REQUIRED | 如果当前没有事务,则新建事务,如果当前存在事务,则加入当前事务,合并成一个事务 |
REQUIRES_NEW | 新建事务,如果当前存在事务,则把当前事务挂起,新建事务执行完后再恢复当前事务 |
NESTED | 如果当前没有事务,则新建事务,如果当前存在事务,则创建一个当前事务的子事务(嵌套事务),子事务不能单独提交,只能和父事务一起提交 |
SUPPORTS | 支持当前事务,如果当前没有事务,以非事务的方式执行 |
NOT_SUPPORTED | 以非事务方式执行,如果存在当前事务就把当前事务挂起 |
NEVER | 以非事务方式执行,如果当前存在事务就抛异常 |
MANDATORY | 使用当前事务,如果当前没有事务,就抛异常 |
@Transactional的几种失效场景
- @Transactional应用在非public修饰的方法上
public class MyService {@Transactionalprivate void doInternal() {System.out.println("Doing internal work...");}
}
spring要求被代理方法必须得是public的,在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
private方法,只会在当前对象中的其他方法中调用,也就是会进行对象的自调用,这种情况是用this调用的,并不会走到代理对象,而@Transactionaly是基于动态代理实现的,所以代理会失效。
- 同一个类中的方法直接内部调用,会导致事务失效。
public class MyService {public void doSomething() {doInternal();}@Transactionalpublic void doInternal() {System.out.println("Doing internal work...");}
}
事务的底层是Spring AOP来实现的,这种自调用的方式是不满足AOP的动态代理的,如果你想要让这个事务生效,你在A类A方法中调用的时候不能采用 addMoney1(yang, i) 的方式,应该用A类的对象调用才行。
- final、static方法
有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final会失效,spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。
由于AOP是通过创建代理对象来实现的,而无法对方法进行子类化和覆盖,所以无法栏截这些方法。还有就是调用static方法,因为这类方法是属于这个类的,并不是对象的,所以无法被AOP。
- 类本身未被spring管理。
在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
- 多线程调用。
@Slf4j
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() -> {roleService.doOtherThing();}).start();}
}@Service
public class RoleService {@Transactionalpublic void doOtherThing() {System.out.println("保存role表数据");}
}
从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
- 存储引擎不支持事务。
myisam好用,但有个很致命的问题是:不支持事务。如果只是单表操作还好,不会出现太大的问题。但如果需要跨多张表操作,由于其不支持事务,数据极有可能会出现不完整的情况。此外,myisam还不支持行锁和外键。所以在实际业务场景中,myisam使用的并不多。在mysql5以后,myisam已经逐渐退出了历史的舞台,取而代之的是innodb。
- 自己吞了异常。
spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。尽量不要写 try-catch 如果要写的同时还要保证事务回滚可以尝试在catch最后一行throw一个runtimeException或者手动回滚。
声明式事务与编程式事务
① 声明式事务(使用这种方式,对代码没有侵入性,方法内只需要写业务逻辑就可以了)
通常情况下,我们会在方法上@Transactional注解,填加事务功能,比如:
@Service
public class UserService {@Autowired private RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {query1();query2();query3();roleService.save(userModel);update(userModel);}
}@Service
public class RoleService {@Autowired private RoleService roleService;@Transactionalpublic void save(UserModel userModel) throws Exception {query4();query5();query6();saveData(userModel);}
}
但@Transactional注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。
上面的这个例子中,在UserService类中,其实只有这两行才需要事务:
roleService.save(userModel);
update(userModel);
在RoleService类中,只有这一行需要事务:
saveData(userModel);
现在的这种写法,会导致所有的query方法也被包含在同一个事务当中。如果query方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。
② 编程式事务
- @Transactional注解是通过Spring的AOP起作用的,但是如果使用不当,事务功能可能会失效。
- @Transactional注解一般加在某个业务方法上,会导致整个业务方法都在这个事务中,粒度太大,不好控制事务范围。
上面的这些内容都是基于@Transactional注解的,主要讲的是它的事务问题,我们把这种事务叫做:声明式事务。
其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。例如:
@Autowiredprivate TransactionTemplate transactionTemplate;public void save(final User user) {queryData1();queryData2();transactionTemplate.execute(transactionStatus -> {addData1();updateData2();return Boolean.TRUE;});}
在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。
相较于@Transactional注解声明式事务,更建议大家使用,基于TransactionTemplate的编程式事务。主要原因如下:
- 避免由于spring aop问题,导致事务失效的问题。
- 能够更小粒度的控制事务的范围,更直观。
建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
事务中避免远程调用
我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事物中,这个事物就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发MQ消息,或者连接redis、mongodb保存数据等。
16、Spring 中的设计模式
- 工厂模式(Factory Pattern)
Spring 使用 BeanFactory 和 ApplicationContext 作为 Bean 的工厂,负责创建和管理 Bean 实例。通过工厂模式,Spring 将对象的创建与使用分离,实现了松耦合。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean bean = context.getBean(MyBean.class);
- 单例模式(Singleton Pattern)
Spring 默认将 Bean 的作用域设置为单例(Singleton),确保每个 Bean 在容器中只有一个实例。单例模式减少了对象的创建和销毁开销,提高了性能。
@Component
public class MyBean {// 单例 Bean
}
- 原型模式(Prototype Pattern)
当 Bean 的作用域设置为原型(Prototype)时,每次请求都会创建一个新的实例。原型模式适用于需要保持状态独立的场景。
@Component
@Scope("prototype")
public class MyBean {// 原型 Bean
}
- 代理模式(Proxy Pattern)
Spring AOP(面向切面编程)使用动态代理模式实现切面功能。代理模式可以在不修改原始类的情况下,增强其功能。
@Aspect
@Component
public class MyAspect {@Before("execution(* com.example.MyService.*(..))")public void beforeAdvice() {System.out.println("Before method execution");}
}
- 模板方法模式(Template Method Pattern)
Spring 提供了许多模板类(如 JdbcTemplate、RestTemplate),定义了算法的骨架,而将具体步骤延迟到子类实现。模板方法模式提高了代码的复用性。
@Autowired
private JdbcTemplate jdbcTemplate;public void queryData() {String sql = "SELECT * FROM users";List<User> users = jdbcTemplate.query(sql, new UserRowMapper());
}
- 观察者模式(Observer Pattern)
Spring 的事件机制(ApplicationEvent 和 ApplicationListener)基于观察者模式。观察者模式实现了发布-订阅模型,支持松耦合的事件处理。
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {System.out.println("Event received: " + event.getMessage());}
}
- 适配器模式(Adapter Pattern)
Spring MVC 中的 HandlerAdapter 使用适配器模式,将不同类型的处理器(Controller)适配到统一的处理流程中。适配器模式提高了框架的扩展性。
@Controller
public class MyController {@RequestMapping("/hello")public String hello() {return "hello";}
}
- 装饰器模式(Decorator Pattern)
Spring 的 BeanPostProcessor 使用装饰器模式,在 Bean 初始化前后对其进行增强。装饰器模式动态地扩展对象的功能。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {// 在初始化之前增强 Beanreturn bean;}
}
- 策略模式(Strategy Pattern)
Spring 的资源加载(ResourceLoader)和事务管理(PlatformTransactionManager)使用策略模式。策略模式将算法或行为抽象出来,使得它们可以相互替换。
@Autowired
private PlatformTransactionManager transactionManager;
- 依赖注入模式(Dependency Injection Pattern)
Spring 的核心功能之一就是依赖注入(DI),通过构造函数、Setter 方法或字段注入依赖对象。依赖注入模式实现了松耦合和可测试性。
@Service
public class MyService {private final MyRepository repository;@Autowiredpublic MyService(MyRepository repository) {this.repository = repository;}
}
- 建造者模式(Builder Pattern)
Spring 中的 RestTemplateBuilder 和 Jackson2ObjectMapperBuilder 使用建造者模式,简化复杂对象的创建过程。
RestTemplate restTemplate = new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(5)).setReadTimeout(Duration.ofSeconds(5)).build();
17、BeanFactory 和 FactroyBean 的关系
从类名后缀就可以体现这两个类的作用,BeanFactory 是一个Bean工厂,FactoryBean 是一个java bean 明白了这两点我们再来详细介绍下 BeanFactory 和 FactoryBean。
BeanFactory
BeanFactory 是 IOC 容器的底层实现接口,Spring 不允许我们直接操作 BeanFactory 工厂,所以 BeanFactory 接口又衍生很多接口,其中我们经常用到的是 ApplicationContext 接口,这个接口此接口继承 BeanFactory 接口,包含 BeanFactory 的所有功能,同时还进行更多的扩展。
FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext:是用来读取xml文件创建bean对象
ClassPathXmlApplicationContext:读取类路径下xml 创建bean
FileSystemXmlApplicationContext:读取文件系统下xml创建bean
AnnotationConfigApplicationContext:主要是注解开发获取ioc中的bean实例
使用
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestApplication.class);
applicationContext.getBean("yang");
FactoryBean
工厂 Bean 跟普通 Bean 不同,其返回的对象不是指定类的一个实例,,其返回的是该工厂 Bean 的 getObject 方法所返回的对象。
- T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,那么该实例会放到Spring容器中单实例缓存池中
- Class getObjectType():返回FactoryBean创建的bean实例的类型
- boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype
演示
/*** @Description: 创建一个Spring定义的FactoryBean,T(泛型):指定我们要创建什么类型的对象* @Author: yangjj_tc* @Date: 2023/5/9 0:23*/
public class Yang implements FactoryBean<TopCharts> {// 返回一个Color对象,这个对象会添加到容器中@Overridepublic TopCharts getObject() throws Exception {System.out.println("TopCharts...getObject...");return new TopCharts();}// 返回这个对象的类型@Overridepublic Class<?> getObjectType() {return TopCharts.class;}// 是单例吗?// 如果返回true,那么代表这个bean是单实例,在容器中只会保存一份;// 如果返回false,那么代表这个bean是多实例,每次获取都会创建一个新的bean@Overridepublic boolean isSingleton() {return FactoryBean.super.isSingleton();}
}
使用 getBean 返回 getObject 中的对象
@Configuration
public class TestYang {@Beanpublic Yang yang() {return new Yang();}@Testpublic void testImport() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestApplication.class);// 工厂bean获取的是调用getObject方法创建的对象System.out.println(applicationContext.getBean("yang").getClass());System.out.println(applicationContext.getBean("&yang"));}
}TopCharts...getObject...
class com.example.test.entity.TopCharts
com.example.test.controller.Yang@6fca5907
18、Spring MVC 工作原理
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为Controller 层(控制层,返回数据给前台页面)、 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)。
- 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 Dispatcher Servlet(前端控制器)
- 由 Dispatcher Servlet 调用 Handler Mapping(处理器映射器),并返回一个执行链(Handler Execution Chain)
- Dispatcher Servlet 将 Handler Mapping 返回的执行链中的 Handler 信息发送给 Handler Adapter(处理器适配器)
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet
- DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析
- ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图)
- 视图负责将结果显示到浏览器(客户端)
19、 Spring MVC 常用的注解有哪些
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
Sping MVC 中的控制器的注解一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。
20、Spring MVC 是如何将不同的 Request 路由到不同 Controller 中的
Spring MVC 通过 前端控制器模式(Front Controller Pattern) 和 处理器映射机制(Handler Mapping) 将不同的 HTTP 请求路由到对应的 Controller 中。以下是 Spring MVC 处理请求路由的详细过程:
- 前端控制器:DispatcherServlet
- 作用:
- DispatcherServlet 是 Spring MVC 的核心组件,作为前端控制器,负责接收所有的 HTTP 请求,并将请求分发给相应的处理器(Controller)。
- 它是整个请求处理流程的入口。
- 配置:
- 在 web.xml 或 Java 配置类中配置 DispatcherServlet,并将其映射到特定的 URL 模式(如 /)。
- 请求处理流程
Spring MVC 的请求处理流程可以分为以下几个步骤:
(1)接收请求
客户端发送 HTTP 请求到服务器,DispatcherServlet 接收请求。
(2)查找处理器(Handler Mapping)
- DispatcherServlet 通过 处理器映射器(HandlerMapping) 查找能够处理当前请求的处理器(Controller)。
- Spring MVC 提供了多种 HandlerMapping 实现,如:
- RequestMappingHandlerMapping:基于 @RequestMapping 注解的映射。
- BeanNameUrlHandlerMapping:基于 Bean 名称的映射。
- SimpleUrlHandlerMapping:基于 URL 模式的映射。
@Controller
public class MyController {@RequestMapping("/hello")public String hello() {return "hello";}
}
当请求路径为 /hello 时,RequestMappingHandlerMapping 会将请求映射到 MyController 的 hello() 方法。
(3)调用处理器适配器(Handler Adapter)
- DispatcherServlet 通过 处理器适配器(HandlerAdapter) 调用具体的处理器方法。
- Spring MVC 提供了多种 HandlerAdapter 实现,如:
- RequestMappingHandlerAdapter:用于处理 @RequestMapping 注解的方法。
- HttpRequestHandlerAdapter:用于处理 HttpRequestHandler 接口的实现类。
- SimpleControllerHandlerAdapter:用于处理 Controller 接口的实现类。
(4)执行处理器方法
- 处理器适配器调用 Controller 中的方法,并传入请求参数。
- Controller 方法执行业务逻辑,并返回一个视图名称或数据。
(5)处理返回结果
- 如果 Controller 返回视图名称,DispatcherServlet 会通过 视图解析器(ViewResolver) 解析视图名称,找到对应的视图(如 JSP、Thymeleaf 模板等)。
- 如果 Controller 返回数据(如 JSON),DispatcherServlet 会通过 消息转换器(HttpMessageConverter) 将数据转换为客户端需要的格式(如 JSON、XML)。
(6)渲染视图
- 视图渲染器将模型数据填充到视图中,生成最终的响应内容。
(7)返回响应
- DispatcherServlet 将最终的响应内容返回给客户端。
21、Spring MVC 里面拦截器是怎么写的
有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可
https://blog.csdn.net/yy139926/article/details/127916974
22、什么是 MyBatis
MyBatis是一个半ORM框架(模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术,将Java中的对象和数据库中的表关联对应起来,理解为:Java对象与数据库表的映射管理框架),它内部封装了JDBC,开发的时候只需要关注SQL语句本身就可以了,我们不需要花太多精力去处理原生的JDBC那一套流程,比如 加载驱动、创建connection连接、创建statement等。
为什么说 MyBatis 是半ORM框架,与 Hibernate 有哪些不同
Hibernate是全自动ORM框架,而Mybatis是半自动的。hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。
23、#{ } 和 ${ } 的区别是什么
- #{ } 是预编译处理当中的一个占位符。MyBatis在处理 #{ } 的时候,会将SQL中的 #{ } 替换成 ? ,然后,再调用PreparedStatement对象的set方法进行赋值,由此来防止SQL注入的问题。底层如下:
// 实例化
PreparedStatement pstmt = con.prepareStatement(sql);// 装载占位符
pstmt.setString(1, "6");
pstmt.setString(2, "bb");// 执行sql语句
ResultSet rs = pstmt.executeQuery();
- ${ } 是单纯的字符串文本替换。MyBatis在处理 ${ } 的时候,只是简单的把 ${ } 替换为变量的值而已,由此会造成SQL注入,带来不必要的风险。
- 大部分情况下,我们都是使用 #{ } 来处理业务。但是,针对一些特殊的情况,比如 通过一个“变化的字段”做排序等,也可以使用 ${ } 。
${param} 传入的参数会被当成SQL语句中的一部分,举例:order by ${param},则解析成的sql为:order by id
24、MyBatis 是怎么解决实体类中的属性名和表中的字段名不一样的问题
(1)第一种是使用 <resultMap>
标签,逐一定义列名和实体类对象属性名之间的映射关系。
<resultMap id="orderResultMap" type="com.wind.OrderEntity"><!–用id标签来映射主键字段,用result属性来映射非主键字段–><!–其中,用property为实体类属性名,column为数据表中的属性–><id property="id" column="user_id"><result property="no" column="user_no"/>
</reslutMap>
(2)第二种是使用在SQL中定义列的别名,将列的别名与实体类对象的属性名一一对应起来
<select id="selectUserById" parameterType="java.lang.Integer" resultetype="com.wind.UserEntity">select user_id as id, user_no as no from Test where user_id = #{id}
</select>
25、如何在 Mapper 中传递多个参数
(1)第一种是使用 @param 注解的方式。比如:
user selectUser(@param("username") string username, @param("password") string password);
(2)第二种是使用Java对象的方式。此时,在Java对象中可以有多个属性,每一个属性其实都是一个参数,这样也可以实现在Mapper中传递多个参数。
(3)第三种是使用map集合的方式。此时,需要使Mapper接口方法的输入参数类型和mapper.xml中定义的每个SQL的parameterType的类型都相同。
26、MyBatis 的接口绑定是什么,有哪些绑定方式
接口绑定,就是在MyBatis中定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口中的方法就可以操作数据库了。这样比起直接使用SqlSession对象提供的原生态的方法,更加灵活与简单。
(1)第一种是通过注解绑定,也就是说 在接口的方法上面加上 @Select、@Update 等注解,注解里面包含SQL语句来进行绑定。这种方式可以省去SQL的 xml 映射文件,对于简单的SQL来说比较适用,后期维护比较困难,平时在业务中基本不怎么使用。
(2)第二种是通过在SQL的xml映射文件里面写SQL来进行绑定, 在这种情况下,要指定 xml 映射文件里面的 namespace 参数必须为接口的全类名。不管是SQL简单还是复杂,xml 文件的方式 都比较简单高效,也是最常用的。
27、在MyBatis 中使用 Mapper 接口开发时有哪些要求
一般情况下,在日常开发的时候,会遵循一个mapper.xml映射文件对应于一张表的增删改查。
- mapper.xml映射文件中的namespace属性,必须要定义为对应的Mapper接口的全类名,以此来标识一个mapper级别的二级缓存。
- Mapper接口中的方法名要和mapper.xml中定义的每个SQL语句的id属性相同。
- Mapper接口中的方法的输入参数类型要和mapper.xml中定义的每个SQL语句的parameterType的类型相同。
- Mapper接口中的方法的输出参数类型要和mapper.xml中定义的每个SQL语句的resultType的类型相同,或者使用resultMap也行。
28、MyBatis 中 Mapper 接口中的方法支持重载么
通常一个 xml 映射文件,都会写一个 Dao 接口与之对应。Dao 接口就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。
Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement
举例: com.mybatis3.mappers. StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers. StudentDao 下面 id = findStudentById 的 MappedStatement,在 MyBatis 中,每一个 <select>
、 <insert>
、 <update>
、 <delete>
标签,都会被解析为一个 MappedStatement 对象。
Dao 接口里的方法可以重载,但是 Mybatis 的 xml 里面的 ID 不允许重复。
/*** Mapper接口里面方法重载*/
public interface StuMapper {List<Student> getAllStu();List<Student> getAllStu(@Param("id") Integer id);
}
然后在 StuMapper.xml 中利用 Mybatis 的动态 sql 就可以实现。
<select id="getAllStu" resultType="com.pojo.Student">select * from student<where><if test="id != null">id = #{id}</if></where>
</select>
能正常运行,并能得到相应的结果,这样就实现了在 Dao 接口中写重载方法。Mybatis 的 Dao 接口可以有多个重载方法,但是多个接口对应的映射必须只有一个,否则启动会报错。
29、MyBatis 的动态SQL是什么,主要标签有哪些
MyBatis的动态SQL标签,主要有以下几类:(1) if 标签,配合 test 标签用来做简单的条件判断。(2)choose 标签,配合 when、otherwise 标签,相当于Java语言中的switch…case语句实现分支选择功能。(3)where 标签,主要是用来简化SQL语句中where条件判断的,能智能的处理 and、or,不用担心有多余的 and 或者 or 导致语法错误。(4)set 标签,主要用来做数据update的时候。(5)trim 标签,对包含的内容加上前缀 prefix、或者后缀 suffix。 (6)foreach 标签,主要用在 Mybatis in 语句中。
(1)if 标签
<select id="findUsersByIf" parameterType="User" resultType="User">
<include refid="select"></include> where age=23
<if test="name!=null"> and username like #{name}</if>
<if test="address!=null"> and address like #{address}</if>
</select>
name->Name->getName 用getName去parameterType指定的类中寻找是否有此方法,如果有就反射调用,调用完反射结果不为null就拼装sql语句 null就不拼装,address同理。
如果两个条件都不为null
select * from user where age=20 username like ? and address like ?
(2)choose when otherwise 标签
<select id="findUsersByChoose" parameterType="java.util.Map" resultType="User">
<include refid="select"></include> where age=23
<choose><when test="uname !=null">and username like #{uname}</when><when test="uaddress !=null">and address like #{uaddress}</when>
<otherwise>and username like '%a%'and address like '%b%'
</otherwise>
</choose>
</select>
choose when otherwise 标签 多个when条件同时成立,就取第一个条件成立的when。
(3)where 标签
<select id="findUsersByWhere" parameterType="java.util.Map" resultType="User"><include refid="select"></include><where><if test="uname != null">username like #{uname}</if><if test="uaddress !=null">and address like #{uaddress}</if></where>
</select>
where标签是为了给sql语句添加where关键字,where标签中的条件都不成立,where关键字就不添加了,如果两个条件都成立
select * from t_user where username like ? and address like ?
如果第一个不成立,第二个条件成立
select * from t_user where and address like ?
他会自动去掉and关键字
(4)set 标签
<update id="updateUserBySet" parameterType="java.util.Map"> update user<set><if test="uname != null">username=#{uname},</if><if test="uaddress !=null">address=#{uaddress}</if></set>where id=#{uid}
</update>
set 标签只能用于更新语句,第一个条件成立,第二条件不成立,则自动取消逗号。
(5)trim 标签
trim 替换where标签
<select id="findUsersByTrim" parameterType="java.util.Map" resultType="User"><include refid="select"></include><trim prefix="where" prefixOverrides="and|or"><if test="uname != null">username like #{uname}</if><if test="uaddress !=null">and address like #{uaddress}</if></trim>
</select>
trim标签替换 set标签
<update id="updateUserByTrim" parameterType="java.util.Map"> update t_user<trim prefix="set" suffixOverrides=","><if test="uname != null">username=#{uname},</if><if test="uaddress !=null">address=#{uaddress}</if></trim>where id=#{uid}
</update>
可以替换where标签和set标签。
(6)foreach 标签
<select id="findUsersByForeach" parameterType="list" resultType="User"><include refid="select"></include>where id in <foreach collection="list"item="id"index="index"open="("close=")"separator=",">#{id}</foreach>
</select>
30、MyBatis 的 Mapper 接口工作原理是什么
Mapper接口,它是没有实现类的。当调用接口方法的时候,它是采用了JDK的动态代理的方式。
UserMapper userMapper=session.getMapper(UserMapper.class);
- Mapper接口的Class对象会被包装成MapperProxyFactory对象,通过MapperProxyFactory对象调用newInstance创建Mapper接口动态代理对象MapperProxy。
- 执行Mapper接口方法时候,其实执行的就是用代理对象执行接口方法,本质执行的是MapperProxy代理类的invoke方法,invoke方法中使用MapperMethod对象执行execute方法。
- 在execute方法中根据MapperMethod对象中的操作类型选择调用的原生方法(接口名,参数)。
每次通过调用接口方法操作数据库的时候,Mybatis都会利用MapperProxyFactory创建当前Mapper接口对应的MapperProxy代理实现类,在此代理类定义的增强中,会利用sqlSession、接口、方法等信息构造MapperMethod。MapperMethod是Mybatis对Mapper的接口方法生成的对应封装类,此封装类定义了真正的操作数据库的代码实现,最终对数据库的操作就是依赖他实现的。
31、MyBatis 的工作原理是什么
- 加载配置文件,构建 SqlSessionFactory。
- MyBatis 的配置文件主要包括:
- mybatis-config.xml:全局配置文件,用于配置数据源、事务管理器、类型处理器、插件等。
- Mapper 文件(XML 或注解):定义 SQL 语句和结果映射。
- SqlSessionFactory 创建 SqlSession 用于与数据库交互
SqlSessionFactory 是 MyBatis 的核心对象,用于创建 SqlSession。它通过解析配置文件(mybatis-config.xml 和 Mapper 文件)构建而成。SqlSession 是 MyBatis 与数据库交互的会话对象,提供了执行 SQL、提交事务、获取 Mapper 接口等方法。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
- 执行 SQL
(1)通过 Mapper 接口
Mapper 接口是 MyBatis 的核心概念之一,开发者只需定义接口,MyBatis 会自动生成实现类。
public interface UserMapper {@Select("SELECT * FROM users WHERE id = #{id}")User getUserById(int id);
}UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
(2)直接使用 SqlSession
开发者可以直接通过 SqlSession 执行 SQL 语句。
User user = sqlSession.selectOne("com.example.UserMapper.getUserById", 1);
- SQL 解析与执行
- MyBatis 通过 Executor 执行 SQL 语句。
- Executor 负责管理缓存、处理事务、调用 StatementHandler 等。
- StatementHandler 负责创建 PreparedStatement、设置参数、执行 SQL 并处理结果。
- 结果映射
MyBatis 通过 ResultSetHandler 将查询结果映射为 Java 对象。结果映射可以通过 XML 或注解定义。
<resultMap id="userResultMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><result property="email" column="email"/>
</resultMap>
- 事务管理
- MyBatis 支持两种事务管理方式:
- JDBC 事务:通过 Connection 手动提交或回滚事务。
- Managed 事务:由外部容器(如 Spring)管理事务。
sqlSession.commit(); // 提交事务
sqlSession.rollback(); // 回滚事务
- 缓存机制
- MyBatis 提供了一级缓存和二级缓存:
- 一级缓存:默认开启,缓存范围是 SqlSession 级别。
- 二级缓存:需要手动开启,缓存范围是 Mapper 级别。
<cache/> <!-- 开启二级缓存 -->
- 关闭资源
使用完 SqlSession 后,需要手动关闭以释放资源。
sqlSession.close();
32、MyBatis 中一对一查询、一对多查询是怎么实现的
- 在MyBatis中,使用association标签来解决一对一的关联查询。association标签可用的属性有:(1)property:对象属性的名称(2)javaType:对象属性的类型(3) column:对应的外键字段名称(4)select:使用另一个查询封装的结果。
- 在MyBatis中,使用collection标签来解决一对多的关联查询。collection标签可用的属性有:(1)property:指的是集合属性的值(2)ofType:指的是集合中元素的类型(3)column:所对应的外键字段名称(4)select:使用另一个查询封装的结果。
一对一关联查询举例如下
<?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.wind.repository.StudentRepository"><resultMap id="studentMap" type="com.wind.entity.StudentEntity"><result column="Id" property="id"/><result column="Name" property="name"/><result column="ClassId" property="classId"/><result column="Status" property="status"/><result column="AddTime" property="addTime"/><result column="UpdateTime" property="updateTime"/></resultMap><resultMap id="studentMap2" type="com.wind.entity.StudentEntity"><result column="Id" property="id"/><result column="Name" property="name"/><result column="ClassId" property="classId"/><result column="Status" property="status"/><result column="AddTime" property="addTime"/><result column="UpdateTime" property="updateTime"/><association property="classEntity" javaType="classEntity"><id column="cId" property="id"/><result column="cClassName" property="className"/><result column="cStatus" property="status"/></association></resultMap><sql id="sql_select">select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student</sql><select id="queryStudent" parameterType="int" resultMap="studentMap"><include refid="sql_select"/>where id = #{id} and status = 1</select><select id="queryStudentWithClass" parameterType="int" resultMap="studentMap2">select r.Id, r.Name, r.ClassId, r.Status, r.AddTime, r.UpdateTime, c.id as cid, c.ClassName as cClassName, c.Status as cStatusfrom RUN_Student r join RUN_Class c on r.classId = c.idwhere r.id = #{id}</select></mapper>
一对多关联查询举例如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wind.repository.ClassRepository"><resultMap id="classStudentMap" type="com.wind.entity.ClassEntity"><result column="Id" property="id"/><result column="ClassName" property="className"/><result column="Status" property="status"/><result column="AddTime" property="addTime"/><result column="UpdateTime" property="updateTime"/><collection property="studentEntities" ofType="com.wind.entity.StudentEntity"><result column="sId" property="id"/><result column="sName" property="name"/><result column="sClassId" property="classId"/><result column="sStatus" property="status"/><result column="sAddTime" property="addTime"/><result column="sUpdateTime" property="updateTime"/></collection></resultMap><sql id="sql_select_join_student">select c.Id, c.ClassName, c.Status, c.AddTime, c.UpdateTime,s.Id as sId, s.Name as sName, s.ClassId as sClassId, s.Status as sStatus, s.AddTime as sAddTime, s.UpdateTime as sUpdateTimefrom RUN_Class c join RUN_Student s on c.Id = s.classId</sql><select id="queryClassByClassId" parameterType="int" resultMap="classStudentMap"><include refid="sql_select_join_student"/>where c.id = #{id} and c.status = 1 and s.status =1</select></mapper>
33、MyBatis 的分页方法有哪些
(1)在MyBatis中,是使用RowBounds对象进行分页的,它是针对ResultSet结果集执行的逻辑分页,而不是物理分页。
(2)另外,我们可以在SQL内,直接书写带有物理分页的参数来完成物理分页功能,也可以使用第三方的分页插件PageHelper来完成物理分页。
<!-- SQL物理分页 -->
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper"> select * from student limit #{start} , #{end}
</select>
int perPage=3; 每页现实的记录数
int page=1; 页数
int begin=(page-1)*perPage; 计算起点
(3)分页插件的原理(物理分页):就是使用MyBatis提供的插件接口,来实现自定义插件。在自定义插件的拦截方法内,拦截待执行的SQL,然后根据设置的分页参数 重写SQL ,生成带有分页语句的SQL,最终执行的是重写之后的SQL,从而实现分页。 举例:
select * from student,分页插件拦截SQL后 重写SQL为:select t.* from (select * from student)t limit 0,10;
34、MyBatis 中都有哪些Executor执行器,它们之间的区别是什么
在 MyBatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 SqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。
BaseExecutor:基础抽象类,实现了Executor接口的大部分方法,主要提供了缓存管理和事务管理的能力,使用了模板模式,doUpdate、doQuery、doQueryCursor 等方法的具体实现交给不同的子类去实现。
-
SimpleExecutor:BaseExecutor的具体子类实现,且为默认配置,在doQuery方法中使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象,用完立刻关闭PrepareStatement。
-
ReuseExecutor:BaseExecutor的具体子类实现,与SimpleExecutor不同的是,在doQuery方法中,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,会重用缓存中的statement对象,而不是每次都创建新的PrepareStatement。
-
BatchExecutor:BaseExecutor的具体子类实现,在doUpdate方法中,提供批量执行多条SQL语句的能力。将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。
-
CachingExecutor:直接实现Executor接口,使用装饰器模式提供二级缓存能力。先从二级缓存中查询,缓存没有命中再从数据库中查询,最后将结果添加到缓存中再返回给用户。如果在xml文件中配置了节点,则会创建 CachingExecutor。
35、MyBatis 是否支持延迟加载,如果支持,它的实现原理是什么
MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 MyBatis 配置文件中,可以配置是否启用延迟加载 。
什么是延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
局部延迟加载
在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user"><id column="id" property="id"></id><result column="username" property="username"></result><result column="password" property="password"></result><result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
--><collection property="orderList" ofType="order" column="id"select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy"></collection>
</resultMap>
<select id="findAll" resultMap="userMap">SELECT * FROM `user`
</select>
全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
<settings><!--开启全局延迟加载功能--><setting name="lazyLoadingEnabled" value="true"/>
</settings>
局部的加载策略的优先级高于全局的加载策略
延迟加载原理实现
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName() ,拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理。
36、MyBatis 是如何防止sql注入的
Mybatis也是用的预编译进行防护SQL注入。其实在框架底层,是JDBC中的PreparedStatement类在起作用。
@Test
public void test02() throws SQLException {Connection con = JdbcUtil.getConn();String sql = "SELECT * FROM test WHERE ID = ? AND NAME = ?";// 实例化PreparedStatement pstmt = con.prepareStatement(sql);// 装载占位符pstmt.setString(1, "6");pstmt.setString(2, "bb");// 执行sql语句ResultSet rs = pstmt.executeQuery();System.out.println(rs);while (rs.next()) {System.out.println(rs.getInt("id") + " " + rs.getString("name"));}JdbcUtil.close(con);
}
37、SpringBoot + Mybatis 一级缓存和二级缓存详解
一级缓存
一级缓存在 mybatis 中默认是开启的并且是 session 级别,它的作用域为一次 sqlSession 会话。 一个SqlSession对象中创建一个本地缓存(local cache),在同一个SqlSession中,执行相同的SQL查询时,会尝试去本地缓存中查找是否在缓存,如果在缓存中,就直接从缓存中取出,然后返回给用户,否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
代码演示:
@Test@Transactional(rollbackFor = Throwable.class)public void testFistCache(){// 第一次查询,缓存到一级缓存userMapper.selectById(1);// 第二次查询,直接读取一级缓存userMapper.selectById(1);}
console 2023-03-15 14:53:58.084 DEBUG [BaseJdbcLogger.java:137] : ==> Preparing: select * from user where id = ?
console 2023-03-15 14:53:58.084 DEBUG [BaseJdbcLogger.java:137] : ==> Parameters: 12(Integer)
console 2023-03-15 14:53:58.103 DEBUG [BaseJdbcLogger.java:137] : <== Total: 1
User{userId=12, userName='Endo Riku', userSex='M', userAge=238}
User{userId=12, userName='Endo Riku', userSex='M', userAge=238}
可以看到,虽然进行了两次查询,但最终只请求了一次数据库,第二次查询命中了一级缓存,直接返回了数据。
这里有两点需要说明一下:
- 为什么开启事务
使用了数据库连接池,默认每次查询完之后自动 commite,这就导致两次查询使用的不是同一个sqlSessioin,根据一级缓存的原理,它将永远不会生效。当我们开启了事务,两次查询都在同一个 sqlSession 中,从而让第二次查询命中了一级缓存。
- 两种一级缓存模式
一级缓存的作用域有两种:session(默认)和 statment,可通过设置 local-cache-scope 的值来切换,默认为 session。二者的区别在于 session 会将缓存作用于同一个 sqlSesson,而 statment 仅针对一次查询,所以,local-cache-scope: statment 可以理解为关闭一级缓存。
- 一级缓存总结
mybatis 默认的 session 级别一级缓存,由于 springboot 中默认使用了 hikariCP,所以基本没用,需要开启事务才有用。但一级缓存作用域仅限同一 sqlSession 内,无法感知到其他 sqlSession 的增删改,所以极易产生脏数据。
二级缓存
默认情况下,mybatis 打开了二级缓存,但它并未生效,因为二级缓存的作用域是 namespace,所以还需要在 Mapper.xml 文件中配置一下才能使二级缓存生效
<cache></cache>
测试 单表二级缓存:
/*** 测试二级缓存效果* 需要*Mapper.xml开启二级缓存**/@Testpublic void testSecondCache(){userMapper.selectById(1);userMapper.selectById(1);}
console 2023-03-15 14:52:54.318 DEBUG [LoggingCache.java:60] : Cache Hit Ratio [com.example.canal.mybatis.mapper.UserMapper]: 0.5
console 2023-03-15 14:52:54.319 DEBUG [LoggingCache.java:60] : Cache Hit Ratio [com.example.canal.mybatis.mapper.UserMapper]: 0.6
User{userId=12, userName='Endo Riku', userSex='M', userAge=238}
User{userId=12, userName='Endo Riku', userSex='M', userAge=238}
这里可以看到,第二次查询直接命中了缓存,日志还打印了该缓存的命中率。读者可以自行关闭二级缓存查看效果,通过注掉对应 mapper.xml 的 cache 标签,或者 cache-enabled: false 均可
第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。
缓存的优先级
通过 mybatis 发起的查询,作用顺序为:二级缓存 -> 一级缓存 -> 数据库 ,其中任何一个环节查到不为空的数据,都将直接返回结果。
如果多表联查的二级缓存,user 表 left join user_order 表 on user.id = user_order.user_id
我们考虑这样一种情况,该联查执行两次,第二次联查前更新 user_order 表,如果只使用 cache 配置,将会查不到更新的 user_orderxi,因为两个 mapper.xml 的作用域不同,要想合到一个作用域,就需要用到 cache-ref
userOrderMapper.xml
<cache></cache>
userMapper.xml
<cache-ref namespace="com.zhengxl.mybatiscache.mapper.UserOrderMapper"/>
二级缓存可通过 cache-ref 让多个 mapper.xml 共享同一 namespace,从而实现缓存共享,但多表联查时配置略微繁琐。所以生产环境建议将一级缓存设置为 statment 级别(即关闭一级缓存),如果有必要,可以开启二级缓存
38、Springboot 自动装配
自动装配(Auto-Configuration)是 Spring Boot 的核心特性之一,它的作用是 根据项目的依赖和配置,自动配置 Spring 应用所需的 Bean。换句话说,Spring Boot 会“智能地”帮你配置好很多常用的功能,而不需要你手动去写大量的配置代码。
具体例子
假设你的项目中需要用到 JdbcTemplate
,以下是 Spring Boot 自动装配的过程:
- 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
- 配置数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456
- 自动装配的过程
-
Spring Boot 启动时,会扫描 spring.factories 或 AutoConfiguration.imports 文件,找到DataSourceAutoConfiguration 和 JdbcTemplateAutoConfiguration。
-
由于类路径中有 DataSource 和 JdbcTemplate 相关的类,Spring Boot 会自动配置 DataSource 和 JdbcTemplate Bean。
-
你不需要手动写任何配置代码,就可以直接注入 JdbcTemplate 使用:
39、Spring Boot 常用注解
@Autowired
private JdbcTemplate jdbcTemplate;
@Controller
1.注解在类上,声明该类是Controller层的Bean,将该类声明进入Spring容器中进行管理。@Controller
public class UserController {}
@ResponseBody
1.表明该类的所有方法的返回值都直接进行提交而不经过视图解析器,且返回值的数据自动封装为json的数据格式@ResponseBody
public class UserController {}
@RestController
1.包含上面两个的作用,且支持Restful风格的数据提交方式@RestController
public class UserController {}
@Service( value = “name” )
1.注解在类上,表示这是一个服务层的bean。
2.等价于@Service("name"),注解在类上,表示这是一个业务层bean,以value的值做为bean的id存入容器中。如果没有value值(@Service),修饰在哪个类上就实例化哪个类的对象,类的第一个字母小写作为容器中的对象的id,但前提类的名字必须遵守帕斯卡命名法。@Service
public class UserServiceImpl implements IUserService {}
@Repository
1.注解在类上,表示这是一个数据访问层的bean。同Service。@Repository
public class UserServiceImpl implements IUserService {}
@Component
1.注解在类上,表示这是一个未归类的bean。同Service。@Component
public class UserServiceImpl implements IUserService {}
@Resource( name = “value” )
1. 按照名字装配Bean,即会按照name属性的值来找到具有相同id的Bean并注入。如果没有指定name属性(@Resource 样式),则会根据这个将要被注入的变量名进行注入(value)。如果变量名在容器中也不存在,就按照变量类型注入,如果类型不存在或者存在多个实现类情况下抛出异常。@Resource
private UserMapper userMapper;
@Autowired @Qualifier( “value” )
1.默认属性required= true(属性必须存储对象,不能为null,false可以),按照名字装配Bean,即会按照value值来找到具有相同id的Bean并注入。如果没有指定vaule值(@Autowired 样式),按照类型注入,类型不存在抛出异常,类型存在,如果类型只有一个实现类就按照类型注入,如果类型有多个实现类先按类型匹配再按变量名称匹配,再匹配不到抛出异常。@Autowired @Qualifier("yang")
private UserMapper userMapper;
@Inject注解
1.用于对象的在注入,注解隶属于jsr330规范,用于对象的在注入,需要导入一个第三方的jar包。
@Override
1.覆盖的意思,表示该方法是继续过来或者实现的方法,如果加了该注解,它的父类或者实现的接口中没有该方法,则ide会报错。这种机制其实是将运行期的错误放到编译期进行处理了。@Override
public User getById(int id) {return userMapper.getById(id);
}
@Value(“#{xx}”)和@Value(“${xx}”)
1.@Value("#{xx}")表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量。@Value("#{1}")
private int number; //获取数字 1 @Value("#{'Spring Expression Language'}") //获取字符串常量
private String str; @Value("#{dataSource.url}") //获取bean的属性
private String jdbcUrl; 2.@Value("${xx}")注解从配置文件读取值的用法。@Value("${init.password}")
private String initPwd;
@RequestMapping(value = “”, method = RequestMethod.POST)
1.RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。用于方法上则是对方法进行注解以产生访问的路径。@RequestMapping(value = "/getById", method = RequestMethod.POST)
public Object getById(int id){return userService.getById(id);
}
@RequestBody
1. @RequestBody主要用来接收前端传递给后端的json字符串中的数据。public Object getById(@RequestBody String jsonString){}
@RequestHeader
1.获取请求头中的数据,通过指定参数 value 的值来获取请求头中指定的参数值。其他参数用法和 @RequestParam 完全一样。
2.如果@RequestHeader绑定的变量,如果在请求头中不存在。Spring会将控制器中的参数初始化为null。public Object getById(@RequestHeader("token") String token){}
@RequestParam(value = “id”, required = false)
1.@RequestParam接收的是请求参数部分,也就是请求路径问号后面的值。
2.不加@RequestParam前端的参数名需要和后端控制器的变量名保持一致才能生效。
3.不加@RequestParam参数为非必传,加@RequestParam写法参数为必传。但@RequestParam可以通过@RequestParam(required = false)设置为非必传。
4.@RequestParam可以通过@RequestParam("userId")或者@RequestParam(value = "userId")指定传入的参数名。
5.@RequestParam可以通过@RequestParam(defaultValue = "0")指定参数默认值public Object getById(@RequestParam(required = false) Map<String, Object> totalParams) {}
@PathVariable
1.通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中。@RequestMapping(value = "/getById/{id}", method = RequestMethod.POST)
public Object getById(@RequestBody String jsonString, @RequestHeader("token") String token, @PathVariable("id") int id) {}
@Bean
1.@Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名。@Configuration
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}}
2.bean的别名@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })3.bean的描述@Bean
@Description("Provides a basic example of a bean")
@Mapper
1.添加了@Mapper注解之后这个接口在编译时会生成相应的实现类,需要注意的是:这个接口中不可以定义同名的方法,因为会生成相同的id,也就是说这个接口是不支持重载的,如果想每个mapper接口都变成实现类,那么需要在每个接口上添加@Mapper注解这样是很麻烦的,解决这个问题就出现了MapperScan,他的作用就是指定实现类接口所在的包,然后包下面所有的接口都会被编译成实现类,启动类上标注@Mapper
public interface UserMapper {User getById(@RequestParam("id") int id);}
@Transactional(rollbackFor = Exception.class)
事务注解
@SpringBootApplication
@SpringBootApplication包含了@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration三个注解。@ComponentScan:作用就是根据定义的扫描路径,把符合扫描规则的类装配到spring的bean容器中。
@SpringBootConfiguration :等同于spring的XML配置文件;使用Java代码可以检查类型安全。
@EnableAutoConfiguration :自动配置。@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,借助AutoConfigurationImportSelector,它可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。@SpringBootApplication
public class CanalApplication {public static void main(String[] args) {SpringApplication.run(CanalApplication.class,args);}
}
@SelectProvider(type=xxxx.class,method=”xxxx”)
1.@SelectProvider声明在方法上,声明的方法定义在Mapper对应的的interface中,type 是获取sql语句的指定类,method 是指定类中要执行获取sql语句的方法。
2.@SelectProvider注解用于生成查询用的sql语句,有别于@Select注解,@SelectProvide指定一个Class及其方法,并且通过调用Class上的这个方法来获得sql语句。type的类必须要能够通过无参的构造函数来初始化且type类中的方法必须是public的,返回值必须为String。
3.对于只有一个参数的情况下,声明方法的参数如果使用@Param注解的话那么相应type类中的method方法必须接受Map<String, Object>做为参数,在超过一个参数的情况下,声明方法的参数如果使用@Param,@SelectProvide方法也必须接受Map<String, Object>做为参数,参数使用了@Param注解,那么参数在Map中以@Param的值为key,参数没有使用@Param注解,那么参数在Map中以参数的顺序为key。
@InsertProvider
1.同@SelectProvider
@UpdateProvider
1.同@SelectProvider
@DeleteProvider
1.同@SelectProvider
@GeneratedValue
1.@GeneratedValue注解存在的意义主要就是为一个实体生成一个唯一标识的主键、@GeneratedValue提供了主键的生成策略。
2.@GeneratedValue注解有两个属性,分别是strategy和generator。generator属性的值是一个字符串,默认为"",其声明了主键生成器的名称(对应于同名的主键生成器@SequenceGenerator和@TableGenerator)。strategy属性:提供四种值:-AUTO,把主键生成策略交给持久化引擎,持久化引擎会根据数据库在以上三种主键生成策略中选择其中一种。此种主键生成策略比较常用,由于JPA默认的生成策略就是GenerationType.AUTO,所以使用此种策略时.可以显式的指定@GeneratedValue(strategy = GenerationType.AUTO)也可以直接@GeneratedValue。
-IDENTITY,此种主键生成策略就是通常所说的主键自增长,数据库在插入数据时,会自动给主键赋值,比如MYSQL可以在创建表时声明"auto_increment" 来指定主键自增长。该策略在大部分数据库中都提供了支持(指定方法或关键字可能不同),但还是有少数数据库不支持,所以可移植性略差。Oracle不支持这种方式。
-SEQUENCE,在某些数据库中,不支持主键自增长,比如Oracle,其提供了一种叫做"序列(sequence)"的机制生成主键。此时,GenerationType.SEQUENCE就可以作为主键生成策略。该策略的不足之处正好与TABLE相反,由于只有部分数据库(Oracle,PostgreSQL,DB2)支持序列对象,所以该策略一般不应用于其他数据库。类似的,该策略一般与另外一个注解一起使用@SequenceGenerator,@SequenceGenerator注解指定了生成主键的序列.然后JPA会根据注解内容创建一个序列(或使用一个现有的序列)。
-Table,使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键,这种策略的好处就是不依赖于外部环境和数据库的具体实现,在不同数据库间可以很容易的进行移植,但由于其不能充分利用数据库的特性,所以不会优先使用。该策略一般与另外一个注解一起使用@TableGenerator,@TableGenerator注解指定了生成主键的表(可以在实体类上指定也可以在主键字段或属性上指定),然后JPA将会根据注解内容自动生成一张表作为序列表(或使用现有的序列表)。如果不指定序列表,则会生成一张默认的序列表,表中的列名也是自动生成,数据库上会生成一张名为sequence的表(SEQ_NAME,SEQ_COUNT)。序列表一般只包含两个字段:第一个字段是该生成策略的名称,第二个字段是该关系表的最大序号,它会随着数据的插入逐渐累加。@GeneratedValue(strategy = GenerationType.TABLE)