1 Maven

依赖传递性

路径最短者优先【就近原则】

先声明者优先

2 Spring Framework

概述

IOC:“控制反转”,指把创建对象过程交给 Spring 进行管理

AOP:“面向切面编程”,减少系统的重复代码,降低模块间的耦合度。

IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。

spring的依赖:spring-context

通过反射机制调用无参数构造方法创建对象

spring源码底层就是一个map集合

log4j依赖

1
2
3
4
5
6
7
8
9
10
11
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>

IoC容器在Spring的实现

BeanFactory,这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用

ApplicationContext,BeanFactory 的子接口,面向 Spring 的使用者

基于XML管理Bean

获取bean:根据id获取,根据类型获取,根据id和类型

依赖注入

setter注入,构造器注入

对象类型属性赋值

方式一:引用外部bean

1
2
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazzOne"></property>

方式二:内部bean

方式三:级联属性赋值

为数组类型属性赋值

为集合类型属性赋值(List,Map)

引用集合类型的bean

bean的作用域

scope属性:取值singleton(默认值)

scope属性:取值prototype,bean在IOC容器中可以有多个实例

生命周期过程

  • bean对象创建(调用无参构造器)

  • 给bean对象设置属性

  • bean的后置处理器(初始化之前)

  • bean对象初始化(需在配置bean时指定初始化方法)

  • bean的后置处理器(初始化之后)

  • bean对象就绪可以使用

  • bean对象销毁(需在配置bean时指定销毁方法)

  • IOC容器关闭

bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

FactoryBean

配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。

基于xml自动装配

autowire=”byType”

autowire=”byName”

组件扫描的一些配置

情况二:指定要排除的组件

情况三:仅扫描指定组件

@Autowired注入

默认根据类型装配。【默认是byType】

属性注入

@Autowired
private UserDao userDao;

set注入

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

构造方法注入

@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

形参上注入

public UserServiceImpl(@Autowired UserDao userDao) {
this.userDao = userDao;
}

当有参数的构造方法只有一个时,@Autowired注解可以省略。

场景六:@Autowired注解和@Qualifier注解联合

@Resource注解

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分
  • @Autowired注解是Spring框架自己的。

Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖

1
2
3
4
5
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>

@Resource注解用在属性上、setter方法上。

@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。

动态代理和静态代理

AOP概念

1 横切关注点——要解决的问题,如,要解决日志管理的问题

2 通知(增强)——就是你想要增强的功能,比如日志

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

后置通知(@After)总是在返回通知(@AfterReturning)之后执行,无论方法是否成功完成。

3 切面——封装通知方法的类

4 目标——被代理的目标对象。

5 代理——向目标对象应用通知之后创建的代理对象。

6 连接点——spring允许你使用通知的地方

AOP

动态代理分为JDK动态代理和cglib动态代理,当目标类没有接口时只能使用cglib动态代理,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口`

  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类,所以不需要目标类实现接口。

AspectJ:

是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。`

依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactIlld>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>

步骤

1 创建切面类并配置

image-20240130191405595

重用切入点表达式,@Pointcut注解

①声明

1
2
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}

②在同一个切面中使用

@Pointcut注解后+方法名

1
2
3
4
5
6
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

获取连接点信息

1
2
3
4
5
//获取连接点的签名信息
String methodName = joinPoint.getSignature().getName();

//获取目标方法到的实参信息
String args = Arrays.toString(joinPoint.getArgs());

②获取目标方法的返回值

@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

1
2
3
4
5
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③获取目标方法的异常

@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

1
2
3
4
5
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

使用@Order注解可以控制切面的优先级:

junit5

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--spring对junit的支持相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.2</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
</dependency>

整合

1
2
3
4
5
6
7
8
9
10
//两种方式均可
//方式一
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:beans.xml")
//方式二
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class SpringJUnit5Test {

...
}

而junit4

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class SpringJUnit4Test {

...
}

junit4和5有什么区别?

声明式事务

添加注解@Transactional

@Transactional标识在方法上,则只会影响该方法

@Transactional标识的类上,则会影响类中所有的方法

事务属性

事务属性:只读

(readOnly = true)

事务属性:超时

超时时间单位秒

(timeout = 3)

回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象

  • rollbackForClassName属性:需要设置一个字符串类型的全类名

  • noRollbackFor属性:需要设置一个Class类型的对象

  • rollbackFor属性:需要设置一个字符串类型的全类名

隔离级别

1
2
3
4
5
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

事务的传播行为

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

@Bean是方法级别注解,用于显式定义bean

@Component是类级别注解,用于自动检测和注册bean

资源操作:Resources

数据校验:Validation

提前编译:AOT

JIT, Just-in-time,动态(即时)编译,边运行边编译;

AOT,Ahead Of Time,指运行前编译,预先编译。

云原生破局利器

3. SpringMVC

SpringMVC涉及组件理解:

  1. DispatcherServlet : 整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  2. HandlerMapping : 它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
  3. HandlerAdapter : 它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
  4. Handler : handler又称处理器,他是Controller类内部的具体方法的简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
  5. ViewResovler : 视图解析器,主要作用简化模版视图页面查找的,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,相对其他的组件不是必须的![财务]

Spring MVC核心组件配置类

可以不在配置类里添加RequestMappingHandlerMappingRequestMappingHandlerAdapter,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载spring-webmvc包下的配置

@ImportResource(“classpath:spring-mvc.xml”) 注解

1
2
3
4
5
6
7
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!-- 对DispatcherServlet来说,url-pattern有两种方式配置 -->
<!-- 配置“/”,表示匹配整个Web应用范围内所有请求。这里有一个硬性规定:不能写成“/*”。
只有这一个地方有这个特殊要求,以后我们再配置Filter还是可以正常写“/*”。 -->
<url-pattern>/</url-pattern>
</servlet-mapping>

SpringMVC接收数据

模糊路径匹配

/product/*

/* 为单层任意字符串,/** 为任意层任意字符串

HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了RequestMethod枚举类

违背请求方式,会出现405异常!!!

8种请求方式

1
2
3
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

进阶注解只能添加到handler方法上,无法添加到类上!

4 接收参数

  1. 参数编码

    param 类型的参数会被编码为 ASCII 码,JSON 类型的参数会被编码为 UTF-8

  2. 参数顺序:

    param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。

  3. 数据类型:

    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。

  4. 可读性:

    param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。

1 param参数接收

只要形参参数名和类型与传递参数相同,即可自动接收!

@RequestParam还可以为请求参数提供默认值

实体接收

2 路径参数接收

@PathVariable

1
2
getUser(@PathVariable Long id, 
@PathVariable("name") String uname)

3 json参数接收

1
2
3
4
5
6
7
@PostMapping("/person")
@ResponseBody
public String addPerson(@RequestBody Person person) {

// 在这里可以使用 person 对象来操作 JSON 数据中包含的属性
return "success";
}

@EnableWebMvc,json数据处理,必须使用此注解,因为他会加入json处理器

1
2
3
4
5
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = ...)
public class SpringMvcConfig implements WebMvcConfigurer {
}

jackson依赖

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>

4 接收cookie

(@CookieValue(“JSESSIONID”) String cookie)

原生Api对象操作

还有model,map,moedlmap,三个对象没讲,下一章节讲,都是request级别的作用域,放在形参列表,另一个request级别的:ModelAndView,放在方法区。

共享域对象操作

Request级别属性(共享)域

Model 类型

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/attr/request/model")
@ResponseBody
public String testAttrRequestModel(

// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {

model.addAttribute("requestScopeMessageModel","i am very happy[model]");

return "target";
}

ModelMap 类型

Map 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/attr/request/map")
@ResponseBody
public String testAttrRequestMap(

// 在形参位置声明Map类型变量,用于存储模型数据
Map<String, Object> map) {

// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
map.put("requestScopeMessageMap", "i am very happy[map]");

return "target";
}

原生 request

5 ModelAndView 对象

session级别

HttpSession session

Application级别属性(共享)域

1
servletContext.setAttribute("appScopeMsg", "i am hungry...");

SpringMVC响应数据

3.2.2 转发和重定向

返回JSON数据(重点)

导入jackson依赖

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>

四,RESTFul风格设计和实战

客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词

URI(统一资源标识符)或者一个 URL(统一资源定位符)

使用习惯

路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。

对于敏感信息,最好使用 POST 和请求体来传递参数。

@PatchMapping更新局部,@PutMapping更新整体

五、SpringMVC其他扩展

全局异常处理机制

编程式异常处理 X

声明式异常处理 √

标注相应的注解(如 @Throws@ExceptionHandler

基于注解声明异常处理

  1. 声明异常处理控制器类
1
2
3
4
5
@RestControllerAdvice
public class GlobalExceptionHandler {


}
  1. 声明异常处理hander方法

@ExceptionHandler(HttpMessageNotReadableException.class)

该注解标记异常处理Handler,并且指定发生异常调用该方法!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){

return null;
}

/**
* 当发生空指针异常会触发此方法!
* @param e
* @return
*/
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

return null;
}

/**
* 所有异常都会触发此方法!但是如果有具体的异常处理Handler!
* 具体异常处理Handler优先级更高!
* 例如: 发生NullPointerException异常!
* 会触发handlerNullException方法,不会触发handlerException方法!
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){

return null;
}

3.配置文件扫描控制器类配置

确保异常处理控制类被扫描

拦截器

拦截器 Springmvc VS 过滤器 javaWeb:

springmvc怎么使用拦截器?

  1. 创建拦截器类

public class Process01Interceptor implements HandlerInterceptor

  1. 修改配置类添加拦截器

在配置类中添加

1
2
3
4
5
6
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
}

参数校验

spring中也有参数校验?

配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean。启用数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>

<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>

常用的

1
2
3
4
5
6
7
8
9
10
11
12
@Min(10)
@Length(min = 3,max = 10)
@Email

接收错误信息
/**
* @Validated 代表应用校验注解! 必须添加!
*/
@PostMapping("save")
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result)

易混总结

@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

  1. @NotNull (包装类型不为null)

    @NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。

  2. @NotEmpty (集合类型长度大于0)

    @NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。

  3. @NotBlank (字符串,不为null,切不为” “字符串)

    @NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
    总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。

六、SpringMVC总结

核心点 掌握目标
springmvc框架 主要作用、核心组件、调用流程
简化参数接收 路径设计、参数接收、请求头接收、cookie接收
简化数据响应 模板页面、转发和重定向、JSON数据、静态资源
restful风格设计 主要作用、具体规范、请求方式和请求参数选择
功能扩展 全局异常处理、拦截器、参数校验注解

4. Mybatis

mybatis的mysql依赖

1
2
3
4
5
6
<!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>

1
2
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.this0.mapper.EmployeeMapper">

注意:

  • 方法名和SQL的id一致
  • 方法返回值类型和resultType一致
  • 方法的参数和SQL的参数一致
  • 接口的全类名和映射配置文件的名称空间一致

数据库部分:

之所以写成:emp_id empId,emp_name empName, emp_salary empSalary,是为了数据库字段和实体形成映射(一致)

image-20250916174858205

log4j2依赖

1
2
3
4
5
6
7
8
9
10
11
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>

或logback依赖

1
2
3
4
5
6
 <!-- 日志 , 会自动传递slf4j门面-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

日志配置

1
2
3
4
<settings>
<!-- SLF4J 选择slf4j输出! -->
<setting name="logImpl" value="SLF4J"/>
</settings>

传参

#{}形式

Mybatis会将SQL语句中的#{}转换为问号占位符。

${}形式

${}形式传参,底层Mybatis做的是字符串拼接操作。

通常不会采用${}的方式传值。一个特定的适用场景是:通过Java程序动态生成数据库表,表名部分需要Java程序通过参数传入;而JDBC对于表名部分是不能使用问号占位符的,(对于表名、列名等数据库对象的部分,JDBC 预编译语句是不支持使用问号占位符的,这是因为数据库引擎在解释 SQL 语句时需要确定对象的名称,而这些对象名称通常不能通过占位符动态替换)此时只能使用${}

特殊情况: 动态的不是值,是列名或者关键字,需要使用${}拼接

//注解方式传入参数!!

1
2
3
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column,
@Param("value") String value);

表层现象:Mybatis能获取传入的实体类型的属性,也就是传实体,相当于传了实体的所有属性

实际原因:Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。

零散的多个简单类型参数,如果没有特殊处理,那么Mybatis无法识别自定义名称

1
int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);

还可以传Map类型参数

有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用@Param注解一个一个传入又太麻烦了。所以都封装到Map中。

数据输出:

resultType = “全限定符 | 别名 | 如果是返回集合类型,写范型类型即可"

可设置类型别名

其一:

1
2
3
4
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

其二:

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

1
<typeAliases> <package name="domain.blog"/> </typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子

1
2
3
4
@Alias("author")
public class Author {
...
}

驼峰映射

1
2
3
4
5
6
7
8
9
10
11
<!-- 在全局范围内对Mybatis进行配置 -->
<settings>

<!-- 具体配置 -->
<!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

返回Map类型

Mapper接口的抽象方法

1
Map<String,Object> selectEmpNameAndMaxSalary();

SQL语句

1
2
3
4
5
6
7
8
9
10
11
<!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
<!-- 返回工资最高的员工的姓名和他的工资 -->
<select id="selectEmpNameAndMaxSalary" resultType="map">
SELECT
emp_name 员工姓名,
emp_salary 员工工资,
(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
FROM t_emp WHERE emp_salary=(
SELECT MAX(emp_salary) FROM t_emp
)
</select>

junit测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testQueryEmpNameAndSalary() {

EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();

Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();

for (Map.Entry<String, Object> entry : entrySet) {

String key = entry.getKey();

Object value = entry.getValue();

log.info(key + "=" + value);

}
}

//TODO有个爆红,mapkey is required

返回主键值

是将自增主键的值设置到实体类对象中

1
2
3
4
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values(#{empName},#{empSalary})
</insert>

非自增长类型主键

而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素。selectKey 元素将会首先运行,id 会被设置,然后插入语句时会被调用!

使用 selectKey 帮助插入UUID作为字符串类型主键示例:

1
2
3
4
5
6
7
8
9
10
11
12
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="java.lang.String"
order="BEFORE">
SELECT UUID() as id
</selectKey>
INSERT INTO user (id, username, password)
VALUES (
#{id},
#{username},
#{password}
)
</insert>

parameterType指定插入的java对象

通过 keyProperty 属性来指定查询到的 UUID 赋值给对象中的 id 属性,而 resultType 属性指定了 UUID 的类型为 java.lang.String

1
2
3
4
<selectKey keyProperty="id" resultType="java.lang.String"
order="BEFORE">
SELECT UUID() as id
</selectKey>

实体类属性和数据库字段对应关系(3种)

使用resultMap

1
2
3
4
5
6
7
8
9
10
<resultMap id="selectEmployeeByRMResultMap" type="com.this0.pojo.Employee">

<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="emp_id" property="empId"/>

<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="emp_name" property="empName"/>
<result column="emp_salary" property="empSalary"/>
</resultMap>

mybatis配置文件

1
2
3
4
5
6
7
8
9
10
11
12
<configuration>

<environments default="development">

<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>

</environment>
</environments>

</configuration>

junit

1
2
3
@BeforeEach
@Test
@AfterEach

@BeforeEach

select标签

timeout和statementType基本不用

statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

insert标签

keyProperty=”指定主键在实体类对象中对应的属性名”
keyColumn=”设置生成键值在表中的列名”

希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样

实体类设计方案

对一关系下,类中只要包含对方单个对象类型属性即可!

对多关系下,类中只要包含对方类型集合属性即可!

只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!

一般是功能开发完成,再加外键约束检查是否有bug。

多对一映射!多个属性放在一个实体里

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 创建resultMap实现“对一”关联关系映射 -->
<!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
<!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
<resultMap id="selectOrderWithCustomerResultMap" type="order">

<!-- 先设置Order自身属性和字段的对应关系 -->
<id column="order_id" property="orderId"/>

<result column="order_name" property="orderName"/>

<!-- 使用association标签配置“对一”关联关系 -->
<!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
<!-- javaType属性:一的一端类的全类名 -->
<association property="customer" javaType="customer">

<!-- 配置Customer类的属性和字段名之间的对应关系 -->
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>

</association>

</resultMap>

<!-- Order selectOrderWithCustomer(Integer orderId); -->
<select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">

SELECT order_id,order_name,c.customer_id,customer_name
FROM t_order o
LEFT JOIN t_customer c
ON o.customer_id=c.customer_id
WHERE o.order_id=#{orderId}

</select>

在“对一”关联关系中,我们的配置比较多,但是关键词就只有:association和javaType

1对多映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap"

type="customer">

<!-- 映射Customer本身的属性 -->
<id column="customer_id" property="customerId"/>

<result column="customer_name" property="customerName"/>

<!-- collection标签:映射“对多”的关联关系 -->
<!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
<!-- ofType属性:集合属性中元素的类型 -->
<collection property="orderList" ofType="order">

<!-- 映射Order的属性 -->
<id column="order_id" property="orderId"/>

<result column="order_name" property="orderName"/>

</collection>

</resultMap>

<!-- Customer selectCustomerWithOrderList(Integer customerId); -->
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
FROM t_customer c
LEFT JOIN t_order o
ON c.customer_id=o.customer_id
WHERE c.customer_id=#{customerId}
</select>

在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:“collection”和“ofType”

多对多映射,使用中间表

多表映射优化

1
2
<!--开启resultMap自动映射 -->
<setting name="autoMappingBehavior" value="FULL"/>

四,动态语句

if和where标签

where标签会自动去掉标签体内,前面多余的and/or

set标签

使用set标签动态管理set子句,并且动态去掉两端多余的逗号`

trim标签

trim标签控制条件部分两端是否包含某些字符`

  • prefix属性:指定要动态添加的前缀
  • suffix属性:指定要动态添加的后缀
  • prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
  • suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值

choose/when/otherwise标签

在多个分支条件中,仅执行一个。

类似于switch case语句

  • 从上到下依次执行条件判断
  • 遇到的第一个满足条件的分支会被采纳
  • 被采纳分支后面的分支都将不被考虑
  • 如果所有的when分支都不满足,那么就执行otherwise分支

foreach标签

用于遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--
collection属性:要遍历的集合
item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
index属性:这里起一个名字,便于后面引用
遍历List集合,这里能够得到List集合的索引值
遍历Map集合,这里能够得到Map集合的key
-->
<foreach collection="empList" item="emp" separator="," open="values" index="myIndex">
<!-- 在foreach标签内部如果需要引用遍历得到的具体的一个对象,需要使用item属性声明的名称 -->
(#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender})
</foreach>

需要额外配置

1
dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true

五,高级拓展

Mapper批量映射优化

1
2
3
<mappers>
<package name="com.this0.mapper"/>
</mappers>

分页插件

1.引入依赖

2.配置

1
2
3
4
5
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>

3.使用