SSM知识快速复习
- Spring
- IOC
- DI
- IOC容器在Spring中的实现
- 常用注解
- @Autowired注解的原理
- AOP
- 相关术语
- 作用
- 动态代理
- 实现原理
- 事务@Transactional
- 事务属性:只读
- 事务属性:超时
- 事务属性:回滚策略
- 事务属性:事务隔离级别
- 事务属性:事务传播行为
- SpringMVC
- @RequestMapping
- @RequestMapping的派生注解
- @PathVariable
- 获取请求参数
- 通过ServletAPI获取
- 通过控制器方法的形参获取请求参数
- @RequestParam
- @RequestHeader
- @CookieValue
- 通过POJO获取请求参数
- 域对象共享数据
- 使用ServletAPI向request域对象共享数据
- 使用ModelAndView向request域对象共享数据
- 使用Model向request域对象共享数据
- 使用map向request域对象共享数据
- 使用ModelMap向request域对象共享数据
- Model、ModelMap、Map的关系
- 向session域共享数据
- 向application域共享数据
- Restful
- 处理ajax请求
- @RequestBody
- @RequestBody 获取json格式的请求参数
- @ResponseBody
- @ResponseBody响应浏览器json数据
- @RestController
- 文件上传和下载
- 文件下载
- 文件上传
- 拦截器
- 方法
- 多个拦截器的执行顺序
- 配置springmvc(了解)
- SpringMVC执行流程
- SpringMVC常用组件
- Handler 和 HandlerAdapter 区别
- SpringMVC的执行流程
- MyBatis
- 特性
- 使用
- MyBatis的增删改查
- 获取参数值
- 使用@Param标识参数(适合单个和多个字面量类型)
- Map集合类型的参数
- 实体类类型的参数
- 自定义映射resultMap
- resultMap处理字段和属性的一对一映射关系
- 多对一映射处理
- 一对多映射处理
- 分步查询
- 动态SQL
- MyBatis的缓存
- MyBatis的一级缓存
- MyBatis的二级缓存
- 二级缓存的相关配置
- MyBatis缓存查询的顺序
- MyBatis的逆向工程
- MyBatis-Plus
- 特性
- 使用
- 流程
- BaseMapper -- CRUD
- 通用Service -- CRUD
- 常用注解
- @TableName
- @TableId
- @TableField
- @TableLogic
- @EnumValue
- 雪花算法
- 条件构造器和常用接口
- Condition
- 插件
- 分页插件
- 乐观锁
- 代码生成器
- 多数据源
- MyBatisX插件
Spring
IOC
IOC:Inversion of Control,翻译过来是反转控制。主动的从容器中获取所需要的资源,变成被动的查找形式(主动做菜变成点外卖)
DI
DI:Dependency Injection,翻译过来是依赖注入
IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。说白了就是给spring所管理对象的属性进行赋值
IOC容器在Spring中的实现
IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。
- BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。 - ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用
ApplicationContext 而不是底层的 BeanFactory。
常用注解
@Component:将类标识为普通组件
@Controller:将类标识为控制层组件
@Service:将类标识为业务层组件
@Repository:将类标识为持久层组件
@Autowired:在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。
- 标识在成员变量上
- 标识在set方法上
- 为当前成员变量赋值的有参构造上
@Autowired注解的原理
- 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
- 若有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配的效果
即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值 - 若byType和byName的方式都无法实现自动装配,即IOC容器中有多个类型匹配的bean
且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NoUniqueBeanDefinitionException - 此时可以在要赋值的属性上,添加一个注解 @Qualifier
通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值
注意:若IOC容器中没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
在@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配
可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值
AOP
例如:日志的实现
核心就是:解耦,基于代理的设计模式,相比于静态代理(写死的不具有灵活性),动态代理更加灵活。
通过预编译方式
和运行期动态代理
方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
相关术语
- 横切关注点:从每个方法中抽取出来的同一类非核心业务。
非核心代码
- 通知:每一个横切关注点上要做的事情需要的实现方法
类的方法
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 切面:
封装通知方法的类
类 - 目标:被代理的目标对象
- 代理:向目标对象应用通知之后创建的代理对象
- 连接点:抽取横切关注点的位置,是一个
概念
而不涉及代码 - 切入点:定位连接点的方式。从代码角度来说是一个
表达式
作用
简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,
提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就
被切面给增强了。
动态代理
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因
为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。 - cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最
终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*
(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参
数:"+args);
}
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
@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(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);
}
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
加了Aspect注解后无法通过getBean获取实现对象,而是应该通过代理对象进行访问,可以使用接口获取对象(因为代理对象和原对象的接口相同);
要用代理对象才能获取到是因为使用了aop,而aop的底层使用的就是动态代理,所以要获取代理对象,而获取代理对象要通过接口来进行获取。
实现原理
- AOP的实现原理通常涉及以下几个方面:
-
连接点(Join Point):AOP框架需要确定在什么地方(如方法执行前、后或异常抛出时等)注入额外的逻辑代码,这个确定的位置就是连接点。
-
切面(Aspect):切面是一个模块化的横切关注点,它定义了在切入点注入的额外逻辑代码。切面可以是一个类或一个通知(Advice)函数。
-
通知(Advice):通知是切面实际注入的代码逻辑,它定义了在切入点执行前、后或抛出异常时需要执行的代码。
-
织入(Weaving):织入是将切面应用到目标对象上的过程。织入可以在编译时、类加载时或运行时进行。
-
切点(Pointcut):切点是一个表达式,它定义了哪些切入点需要被织入。切点可以是一个方法名、一个正则表达式或者一个类、方法的匹配规则。
-
引入(Introduction):引入是在目标对象中添加新的方法或属性,以增强其功能。
AOP框架通常会提供以上这些概念的API,以便开发人员使用。通过AOP框架,开发人员可以将不同的切面织入到目标对象中,从而实现不同的横切关注点,提高代码的可重用性和灵活性。
- 动态代理
- 动态代理是一种在运行时生成代理对象的技术,它允许程序在运行时创建一个实现了给定接口的代理类对象,该代理类对象可以拦截目标对象方法的调用,并在调用前、调用后或异常抛出时注入额外的逻辑代码。这个代理类对象即为AOP中的切面。
- Java中有两种动态代理方式:JDK动态代理和CGLIB动态代理。JDK动态代理是基于接口的动态代理,它只能代理实现了接口的类,而CGLIB动态代理是基于继承的动态代理,它可以代理没有实现接口的类。在AOP中,通常使用JDK动态代理来代理接口,使用CGLIB动态代理来代理没有实现接口的类。
- 动态代理是AOP实现的一种常用技术,但并非AOP的全部实现原理,还包括切点、通知、织入等概念和实现方式
事务@Transactional
@Transactional标识在方法上,只会影响该方法
@Transactional标识的类上,会影响类中所有的方法
事务属性:只读
@Transactional(readOnly = true)
事务属性:超时
概括来说就是一句话:超时回滚,释放资源。
@Transactional(timeout = 3)
事务属性:回滚策略
@Transactional(noRollbackFor = ArithmeticException.class)
默认只针对运行时异常回滚,编译时异常不回滚
可以通过@Transactional中相关属性设置回滚策略
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符串类型的全类名
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
事务属性:事务隔离级别
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
隔离级别一共有四种:
- 读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。 - 读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。 - 可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它
事务对这个字段进行更新。 - 串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它
事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
事务属性:事务传播行为
@Transactional(propagation = Propagation.REQUIRED)
事务传播行为是指在一个事务中的多个操作之间的关系,以及当一个事务方法调用另一个事务方法时,这些事务之间的关系。在这种情况下,需要确定调用方法时如何处理事务,以确保整个操作的数据完整性和一致性。
在Spring框架中,定义了7种事务传播行为:
-
PROPAGATION_REQUIRED(默认值):如果当前已经存在一个事务,则加入该事务,否则创建一个新的事务。
-
PROPAGATION_SUPPORTS:如果当前已经存在一个事务,则加入该事务,否则以非事务的方式执行。
-
PROPAGATION_MANDATORY:如果当前已经存在一个事务,则加入该事务,否则抛出异常。
-
PROPAGATION_REQUIRES_NEW:创建一个新的事务,并挂起当前事务(如果存在)。
-
PROPAGATION_NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在一个事务,则挂起该事务。
-
PROPAGATION_NEVER:以非事务的方式执行操作,如果当前存在一个事务,则抛出异常。
-
PROPAGATION_NESTED:如果当前存在一个事务,则在嵌套事务中执行操作,否则创建一个新的事务。嵌套事务可以单独提交或回滚,但是最终的提交或回滚操作将由最外层的事务控制。
通过指定事务传播行为,可以在不同的方法之间控制事务的行为。根据具体的业务场景,选择适当的事务传播行为可以保证数据的完整性和一致性。
事务挂起
事务挂起是指在嵌套事务中,内部事务挂起外部事务的执行,直到内部事务完成后再恢复外部事务的执行。嵌套事务是指在一个已经存在的事务中,开启了新的事务。
当外部事务和内部事务使用相同的数据源时,内部事务在执行期间会阻塞外部事务对于相同数据的访问,这可能会导致性能问题或死锁问题。因此,事务管理器提供了事务挂起的机制,可以让内部事务暂停执行,让外部事务继续执行,从而避免死锁和性能问题。
在Java中,使用JDBC或Hibernate等ORM框架进行数据库操作时,可以通过设置事务传播行为为PROPAGATION_REQUIRES_NEW,来开启一个新的事务。当内部事务执行时,外部事务会被挂起,直到内部事务完成后再恢复执行。在Spring框架中,使用TransactionTemplate或@Transactional注解来管理事务,也可以通过设置事务传播行为来实现事务挂起。
SpringMVC
@RequestMapping
@RequestMapping标识一个类:设置映射请求的请求路径的初始信息
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息
@RequestMapping注解的value属性是一个字符串类型的数组
,表示该请求映射能够匹配多个请求地址
所对应的请求
@RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射
@RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配
多种请求方式的请求
@RequestMapping(
value = {"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST}
)
@RequestMapping的派生注解
处理get请求的映射–>@GetMapping
处理post请求的映射–>@PostMapping
处理put请求的映射–>@PutMapping
处理delete请求的映射–>@DeleteMapping
@PathVariable
占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username")
String username){
System.out.println("id:"+id+",username:"+username);
return "success";
}
获取请求参数
通过ServletAPI获取
HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象
@RequestMapping("/testParam")
public String testParam(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}
通过控制器方法的形参获取请求参数
在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在
DispatcherServlet中就会将请求参数赋值给相应的形参
@RequestMapping("/testParam")
public String testParam(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "success";
}
@RequestParam
@RequestParam是将请求参数和控制器方法的形参创建映射关系
@RequestParam注解一共有三个属性:
- value:指定为形参赋值的请求参数的参数名
- required:设置是否必须传输此请求参数,默认值为true
若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置
defaultValue属性,则页面报错400:Required String parameter ‘xxx’ is not present;若设置为
false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为
null - defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值
为""时,则使用默认值为形参赋值
@RequestHeader
@RequestHeader是将请求头信息和控制器方法的形参创建映射关系
@RequestHeader注解一共有三个属性:value、required、defaultValue,用法同@RequestParam
@CookieValue
@CookieValue是将cookie数据和控制器方法的形参创建映射关系
@CookieValue注解一共有三个属性:value、required、defaultValue,用法同@RequestParam
通过POJO获取请求参数
可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实
体类中的属性名一致,那么请求参数就会为此属性赋值
@RequestMapping("/testpojo")
public String testPOJO(User user){
System.out.println(user);
return "success";
}
域对象共享数据
使用ServletAPI向request域对象共享数据
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope", "hello,servletAPI");
return "success";
}
使用ModelAndView向request域对象共享数据
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("testScope", "hello,ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
return mav;
}
使用Model向request域对象共享数据
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testScope", "hello,Model");
return "success";
}
使用map向request域对象共享数据
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testScope", "hello,Map");
return "success";
}
使用ModelMap向request域对象共享数据
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testScope", "hello,ModelMap");
return "success";
}
Model、ModelMap、Map的关系
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的
向session域共享数据
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}
向application域共享数据
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}
Restful
Rest原理(表单提交要使用REST的时候)
- 表单提交会带上
\_method=PUT
- 请求过来被
HiddenHttpMethodFilter
拦截- 请求是否正常,并且是POST
- 获取到
\_method
的值。 - 兼容以下请求;PUT.DELETE.PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
- 获取到
- 请求是否正常,并且是POST
HiddenHttpMethodFilter 处理put和delete请求的条件:
- 当前请求的请求方式必须为post
- 当前请求必须传输请求参数_method
处理ajax请求
@RequestBody
@RequestBody可以获取请求体信息,使用@RequestBody注解标识控制器方法的形参,当前请求的请
求体就会为当前注解所标识的形参赋值
@RequestMapping("/test/RequestBody")
public String testRequestBody(@RequestBody String requestBody){}
@RequestBody 获取json格式的请求参数
对于json数据,操作json的相关jar包gson或jackson处理此类请求参数,可以将其转换为指定的实体类对象或map集合
在SpringMVC中,直接使用@RequestBody注解标识控制器方法的形参即可将此类请求参数转换为java对象
public void testRequestBody(@RequestBody User user, HttpServletResponse
response) throws IOException {}
@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到
浏览器
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
//此时响应浏览器数据success
return "success";
}
@ResponseBody响应浏览器json数据
使用操作json数据的jar包gson或jackson将java对象转换为json字符串。
在SpringMVC中,我们可以直接使用@ResponseBody注解实现此功能
可以是list,map,实体类
//响应浏览器list集合
@RequestMapping("/test/ResponseBody/json")
@ResponseBody
public List<User> testResponseBody(){
User user1 = new User(1001,"admin1","123456",23,"男");
User user2 = new User(1002,"admin2","123456",23,"男");
User user3 = new User(1003,"admin3","123456",23,"男");
List<User> list = Arrays.asList(user1, user2, user3);
return list;
}
@RestController
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了
@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
文件上传和下载
文件下载
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文,使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers,
statusCode);
//关闭输入流
is.close();
return responseEntity;
}
文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}
拦截器
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor
方法
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返
回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法 - postHandle:控制器方法执行之后执行postHandle()
- afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
多个拦截器的执行顺序
- 若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterCompletion()会按照配置的反序执行 - 若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false
的拦截器之前的拦截器的afterCompletion()会执行
配置springmvc(了解)
在SpringMVC的配置中,需要配置以下文件:
1 、组件扫描器(Compnent-Scan)
2 、Thymeleaf视图解析器
3 、MVC视图解析器(View-Controller)
4 、 静态资源访问(Default-servlet-handler)
5 、MVC注解驱动(annotation-driver)
6 、文件上传解析器(CommonsMultiPartResolver)
7 、异常处理器(SimpleMappingExceptionResolver)
8 、拦截器(InterCeptor)
@Configuration//将当前类标识为配置类
@ComponentScan// 1 、组件扫描器
@EnableWebMvc// 5 、MVC注解驱动
public class implements WebMvcConfigurer{ //实现WebMvcConfigurer 接口
// 4 、配置静态资源访问 Ctrl+o,选择configureDefaultServletHandling
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//enable开启静态资源访问
configurer.enable();
}
// 8 、配置拦截器 Ctrl+o,选择addInterCeptor
@Override
public void addInterceptors(InterceptorRegistry registry) {
//new一个拦截器对象
TestInterceptor testInterceptor = new TestInterceptor();
//添加拦截器类().添加拦截的路径()
//addInterceptor(HandlerInterceptor interceptor)类型是拦截器
//所以需要new一个拦截器对象
registry.addInterceptor(testInterceptor).addPathPatterns("/**").excludePathPatterns("/abc");
}
// 3 、配置mvc视图解析器 Ctrl+o,选择addViewControllers
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//添加路径("/hello").设置跳转的视图名("hello")
registry.addViewController("/hello").setViewName("hello");
}
// 6 、配置文件上传解析器 Ctrl+o,选择MultipartResolver
@Bean //要创建一个对象 用@Bean注解
public MultipartResolver multipartResolver(){
//MultipartResolver只是一个接口 所以要new它的实现类
CommonsMultipartResolver commonsMultipartResolver
= new CommonsMultipartResolver();
return commonsMultipartResolver;
}
//另一种写法
// @Bean
// public CommonsMultipartResolver multipartResolver(){
// return new CommonsMultipartResolver();
// }
// 7 、配置异常处理器 Ctrl+o,选择configureHandlerExceptionResolvers
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
//配置bean
SimpleMappingExceptionResolver exceptionResolver
= new SimpleMappingExceptionResolver();
//创建两个属性exceptionMapping、exceptionAttribute;
//new一个property文件,进行键值对的传参
Properties prop = new Properties();
//传入异常,因为键值对是String类型,所以不能用put方法(Object key,Object Value)
prop.setProperty("java.lang.ArithmeticException","error");
//将prop属性传给异常映射
exceptionResolver.setExceptionMappings(prop);
//将异常信息在请求域中进行共享
exceptionResolver.setExceptionAttribute("exception");
//添加异常处理器
resolvers.add(exceptionResolver);
}
/**
* 2 、配置Thymeleaf解析器
*/
//配置生成模板解析器
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver springResourceTemplateResolver = new SpringResourceTemplateResolver();
springResourceTemplateResolver.setApplicationContext(ContextLoader.getCurrentWebApplicationContext());
springResourceTemplateResolver.setPrefix("/WEB-INF/templates/");
springResourceTemplateResolver.setSuffix(".html");
springResourceTemplateResolver.setCharacterEncoding("UTF-8");
springResourceTemplateResolver.setTemplateMode(TemplateMode.HTML);
return springResourceTemplateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
SpringMVC执行流程
SpringMVC常用组件
- DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求 - HandlerMapping:处理器映射器,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法 - Handler:处理器,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理 - HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行 - ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView - View:视图
作用:将模型数据通过页面展示给用户
Handler 和 HandlerAdapter 区别
在Spring MVC中,Handler(处理器)和HandlerAdapter(处理器适配器)是两个不同的概念,它们的作用和职责也不同。
Handler负责处理客户端请求,它通常是一个具有特定注解(例如@RequestMapping)的Java类或方法
,用于处理客户端请求并生成相应的响应结果。Handler的职责是根据客户端请求的URL、参数等信息,执行相应的业务逻辑,并将结果返回给客户端
。
HandlerAdapter是Spring MVC框架中的一个核心组件,它的作用是根据Handler的类型,将其适配为可以被Spring MVC框架处理的对象
。不同类型的Handler可能需要不同的处理逻辑,因此需要不同的HandlerAdapter来进行适配。
HandlerAdapter的主要职责是将Handler的执行结果转换为ModelAndView或者其他形式的响应结果,使其可以被视图解析器(ViewResolver)处理并返回给客户端
。因此,HandlerAdapter起到了框架和Handler之间的桥梁作用。
总结来说,Handler负责业务处理,而HandlerAdapter负责将Handler适配为可以被框架处理的对象。在请求处理的整个流程中,Handler和HandlerAdapter是相互配合的,共同完成请求处理和响应生成的任务。
SpringMVC的执行流程
- 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
- 不存在
i. 再判断是否配置了mvc:default-servlet-handler
ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误
iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
- 不存在
- 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
- DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
- 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定
的响应信息 - 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
- 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
- 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定
- Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
- 此时将开始执行拦截器的postHandle(…)方法【逆向】。
- 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
- 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
- 将渲染结果返回给客户端。
MyBatis
特性
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java
Objects,普通的Java对象)映射成数据库中的记录 - MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
使用
- 创建mapper接口
public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}
- 创建MyBatis的映射文件
<?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.atguigu.mybatis.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'张三','123',23,'女')
</insert>
</mapper>
- 映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml
例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
因此一个映射文件对应一个实体类,对应一张表的操作
MyBatis映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置是src/main/resources/mappers目录下 - MyBatis中可以面向接口操作数据,要保证两个一致:
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
MyBatis的增删改查
- 添加
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男')
</insert>
- 删除
<!--int deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 7
</delete>
- 修改
<!--int updateUser();-->
<update id="updateUser">
update t_user set username='ybc',password='123' where id = 6
</update>
- 查询一个实体类对象
<!--User getUserById();-->
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User">
select * from t_user where id = 2
</select>
- 查询集合
<!--List<User> getUserList();-->
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User">
select * from t_user
</select>
- 查询count数据
<!--int getCount();-->
<select id="getCount" resultType="_integer">
select count(id) from t_user
</select>
- 查询一条数据为map集合
<!--Map<String, Object> getUserToMap(@Param("id") int id);-->
<select id="getUserToMap" resultType="map">
select * from t_user where id = #{id}
</select>
<!--结果:{password=123456, sex=男, id=1, age=23, username=admin}-->
- 查询多条数据为map集合
<!--List<Map<String, Object>> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
- 模糊查询
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
select * from t_user where username like "%"#{mohu}"%"
</select>
- 批量删除
<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
- 动态设置表名
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
select * from ${tableName}
</select>
- 添加功能获取自增的主键
keyProerty的值就是主键会被保存在所传递参数对象上的那个属性
<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>
注意:
- 查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射
关系- resultType:自动映射,用于属性名和表中字段名一致的情况
- resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
- 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常
获取参数值
${}
和#{}
${}
的本质就是字符串拼接
#{}
的本质就是占位符赋值
使用@Param标识参数(适合单个和多个字面量类型)
通过@Param注解标识mapper接口中的方法参数
Map集合类型的参数
只需要通过${}
和#{}
访问map集合的键
就可以获取相对应的值,注意${}
需要手动加单引号
实体类类型的参数
使用${}
和#{}
,通过访问实体类对象中的属性名
获取属性值,注意${}
需要手动加单引号
自定义映射resultMap
resultMap处理字段和属性的一对一映射关系
resultMap:设置自定义映射属性:
- id:表示自定义映射的唯一标识
- type:查询的数据要映射的实体类的类型
- 子标签:
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- association:设置多对一的映射关系
- collection:设置一对多的映射关系
属性 - property:设置映射关系中实体类中的属性名
- column:设置映射关系中表中的字段名
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="userName" column="user_name"></result>
<result property="password" column="password"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</resultMap>
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultMap="userMap">
<!--select * from t_user where username like '%${mohu}%'-->
select id,user_name,password,age,sex from t_user where user_name like
concat('%',#{mohu},'%')
</select>
多对一映射处理
- association:处理多对一的映射关系
- property:需要处理多对的映射关系的属性名
- javaType:该属性的类型
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>
<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>
多对一关系是指多个对象都关联到同一个对象的关系,也可以称之为“多个小的对象引用一个大的对象”。下面举一个生活中的例子来说明。
假设有一个学校和多个班级,每个班级都有多个学生。那么可以把学校看作是“一个大的对象”,每个班级看作是“一个小的对象”,每个学生看作是“一个更小的对象”。这样,就形成了一个多对一的关系:多个班级(多个小的对象)关联到同一个学校(一个大的对象)。
在这个例子中,学校是一个全局的实体,班级是一个局部的实体,而学生则是班级中的局部实体。当需要查询一个学校的所有班级和学生信息时,可以通过多对一的关系,将所有班级和学生的信息与学校的信息进行关联,从而得到完整的信息。
在数据库设计中,多对一的关系也非常常见,例如一个订单(一个大的对象)可以包含多个商品(多个小的对象),而多个订单(多个小的对象)都属于同一个客户(一个大的对象)。这种情况下,可以通过多对一的关系,将所有商品和订单的信息与客户的信息进行关联,从而得到完整的客户信息。
一对多映射处理
- collection
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}
</select>
分步查询
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
- lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
- aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个
属性会按需加载
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和
collection中的fetchType属性
设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加 载)|eager(立即加载)"
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<collection property="emps" fetchType="eager"
select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did">
</collection>
</resultMap>
<!--Dept getDeptByStep(@Param("did") int did);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>
动态SQL
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决
拼接SQL语句字符串时的痛点问题。
- if
- where
- trim
- choose、when、otherwise
相当于if…else if…else - foreach
- SQL片段
sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
MyBatis的缓存
只针对查询功能有效
MyBatis的一级缓存
一级缓存是 MyBatis 中默认开启的缓存,它是指在同一个 SqlSession 中执行的相同 SQL 语句所查询的结果会被缓存起来,下一次执行相同 SQL 语句时会直接从缓存中获取,而不会再去查询数据库。一级缓存的生命周期很短,当 SqlSession 关闭时,一级缓存也会被清空。
使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
MyBatis的二级缓存
二级缓存是指在同一个 namespace 下的不同 SqlSession 中执行相同 SQL 语句所查询的结果会被缓存起来,下一次执行相同 SQL 语句时会直接从缓存中获取,而不会再去查询数据库。二级缓存的生命周期比一级缓存长,它会随着 SqlSessionFactory 的生命周期结束而结束。因为二级缓存是在多个 SqlSession 中共享的,所以在进行增删改操作时,会自动清空所有与该 namespace 相关的二级缓存。
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
总的来说,一级缓存是在 SqlSession 级别的缓存,而二级缓存是在 namespace 级别的缓存。在使用 MyBatis 进行开发时,一般建议使用默认的一级缓存,并根据实际情况进行配置和调整。二级缓存虽然可以提高查询效率,但也存在一些问题,例如缓存数据过期、数据不一致等,需要谨慎使用。
二级缓存的相关配置
- eviction属性:缓存回收策略
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。 - flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 - size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出 - readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了
很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是
false。
MyBatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
MyBatis的逆向工程
- 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程
的。 - 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
执行MBG插件的generate目标
MyBatis-Plus
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分
CRUD 操作,更有强大的条件构造器,满足各类使用需求 - 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由
配置,完美解决主键问题 - 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强
大的 CRUD 操作 - 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、
Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用 - 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等
同于普通 List 查询 - 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、
Postgre、SQLServer 等多种数据库 - 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出
慢查询 - 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防
误操作
使用
流程
- 配置yml
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-
8&useSSL=false
username: root
password: 123456
- 在Spring Boot启动类中添加@MapperScan注解,扫描mapper包
@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
- 添加实体
@Data //lombok注解
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 添加mapper
public interface UserMapper extends BaseMapper<User> {
}
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的
实体类型
- 添加日志
# 配置MyBatis日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
BaseMapper – CRUD
- 插入
MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
@Test
public void testInsert(){
User user = new User(null, "张三", 23, "zhangsan@atguigu.com");
//INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
int result = userMapper.insert(user);
System.out.println("受影响行数:"+result);
//1475754982694199298
System.out.println("id自动获取:"+user.getId());
}
- 删除
通过id删除记录
@Test
public void testDeleteById(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1475754982694199298L);
System.out.println("受影响行数:"+result);
}
通过id批量删除记录
@Test
public void testDeleteBatchIds(){
//通过多个id批量删除
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> idList = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(idList);
System.out.println("受影响行数:"+result);
}
通过map条件删除记录
@Test
public void testDeleteByMap(){
//根据map集合中所设置的条件删除记录
//DELETE FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 23);
map.put("name", "张三");
int result = userMapper.deleteByMap(map);
System.out.println("受影响行数:"+result);
}
- 修改
@Test
public void testUpdateById(){
User user = new User(4L, "admin", 22, null);
//UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println("受影响行数:"+result);
}
- 查询
根据id查询用户信息
@Test
public void testSelectById(){
//根据id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
根据多个id查询多个用户信息
@Test
public void testSelectBatchIds(){
//根据多个id查询多个用户信息
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
List<Long> idList = Arrays.asList(4L, 5L);
List<User> list = userMapper.selectBatchIds(idList);
list.forEach(System.out::println);
}
通过map条件查询用户信息
@Test
public void testSelectByMap(){
//通过map条件查询用户信息
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 22);
map.put("name", "admin");
List<User> list = userMapper.selectByMap(map);
list.forEach(System.out::println);
}
查询所有数据
@Test
public void testSelectList(){
//查询所有用户信息
//SELECT id,name,age,email FROM user
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针
对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所
有数据
通用Service – CRUD
-
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删
除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆, -
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
-
创建
/**
* UserService继承IService模板提供的基础功能
*/
public interface UserService extends IService<User> {
}
/**
* ServiceImpl实现了IService,提供了IService中基础功能的实现
* 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements
UserService {
}
- 查询
@Autowired
private UserService userService;
@Test
public void testGetCount(){
long count = userService.count();
System.out.println("总记录数:" + count);
}
- 批量插入
有id是修改,没有id是添加
@Test
public void testSaveBatch(){
// SQL长度有限制,海量数据插入单条SQL无法实行,
// 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = new User();
user.setName("ybc" + i);
user.setAge(20 + i);
users.add(user);
}
//SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
userService.saveBatch(users);
}
常用注解
@TableName
MyBatis-Plus在确定操作的表时,默认操作的表名和实体类型的类名一致
在实体类类型上添加@TableName(“t_user”),标识实体类对应的表
- 全局配置
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
@TableId
在实体类中uid属性上通过@TableId将其标识为主键
- value属性
指定表中的主键字段,若实体类中主键对应的属性为id,而表中表示主键的字段为uid - type属性
type属性用来定义主键策略- IdType.ASSIGN_ID(默认): 基于雪花算法的策略生成数据id
- IdType.AUTO :使用数据库的自增策略
全局主键策略
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
@TableField
保证实体类中的属性名和表中的字段名一致
默认配置
实体类属性userName,表中字段user_name,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格
@TableLogic
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库
中仍旧能看到此条数据记录
- 默认是0 - 未删除
- 删除是1 - 已删除
@EnumValue
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
配置
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
# 配置扫描通用枚举
type-enums-package: com.atguigu.mybatisplus.enums
雪花算法
单表数据拆分有两种方式:垂直分表和水平分表
水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
原理
长度共64bit(一个long型)
- 一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
- 41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
- 10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
- 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
条件构造器和常用接口
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper
:用于Lambda语法使用的查询WrapperLambdaUpdateWrapper
: Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
Condition
先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果
wrapper中第一个一般是condition条件
插件
分页插件
乐观锁
添加@Version
原理
取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND
`version`=1
配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
代码生成器
多数据源
适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等
配置
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-
8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-
8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
使用
@DS(“master”) //指定所操作的数据源
@DS("master") //指定所操作的数据源
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements
UserService {
}
MyBatisX插件
在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX插件