1-Spring框架
1. Spring框架
- Spring框架优势:简化开发、框架整合;
- Spring架构图
- Spring的学习内容:Spring的IOC/DI、Spring的AOP、AOP的具体应用与事务管理、IOC/DI的具体应用与整合Mybatis。
- IOC:控制反转;IOC容器;Bean;DI依赖注入。
2. IOC相关内容
- bean的基础配置
<bean
id="bean的唯一标识"
class="bean的全类名"
scope="bean的作用范围,有singleton(默认)和prototype"
name="为bean取的别名"
/>
- bean的实例化:
- bean的创建:构造方法
- Spring的IOC实例化对象的三种方式
- 构造方法(常用)
- 静态工厂(了解)
- 实例工厂(了解)
- FactoryBean(实用)
- bean的生命周期
3. DI相关内容
3.1 setter注入:
- 对于引用数据类型:
<bean ...>
<property name="" ref=""/>
</bean>
- 对于简单数据类型:
<bean ...>
<property name="" value=""/>
</bean>
3.2 构造器注入
- 引用数据类型
<bean ...>
<constructor-arg name="" index="" type="" ref=""/>
</bean>
- 简单数据类型
<bean ...>
<constructor-arg name="" index="" type="" value=""/>
</bean>
3.3 自动配置
- 将
<property
标签删除; - 在
<bean>
标签中添加 autowire 属性。 - 分类:按照类型注入、按照名称注入
3.4 集合注入
在 <property>
或 <constructor-arg>
标签内部写 array
、 list
、 set
、 map
、 props
标签。
4. IOC/DI配置管理第三方bean
4.1 数据源对象管理
管理第三方bean步骤:
- 添加依赖;
- 配置第三方 bean,通过 setter 注入为第三方 bean 注入必要的参数;
- 从 IOC 容器获取对应的 bean 对象使用。
4.2 加载 properties 文件
- 开启 context 命名空间:
- 加载 properties 配置文件
<context:property-placeholder location="" system-properties-mode="NEVER"/>
- 在applicationContext.xml引入properties配置文件中的值
${key}
5. 核心容器
- 容器创建的两种方式
- ClassPathXmlApplicationContext[掌握]
- FileSystemXmlApplicationContext[知道即可]
- 获取 Bean 的三种方式
- getBean(“名称”):需要类型转换
- getBean(“名称”,类型.class):多了一个参数
- getBean(类型.class):容器中不能有多个该类的bean对象
- 容器类层次结构:最顶层的父接口 BeanFactory
- BeanFactory:
- 使用BeanFactory创建的容器是延迟加载
- 使用ApplicationContext创建的容器是立即加载
- 具体BeanFactory如何创建只需要了解即可。
- bean 相关:
- 依赖注入相关
6. IOC/DI 注解开发
6.1 纯注解开发模式
- 创建配置类 SpringConfig;
- 在配置类上添加
@Configuration
注解,将其标识为一个配置类,替换applicationContext.xml
; - 在配置类上添加包扫描注解
@ComponentScan
替换<context:component-scan base-package=""/>
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
6.2 注解开发bean作用范围与生命周期管理
@Scope
:设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象@PostConstruct
:设置该方法为初始化方法@PreDestroy
:设置该方法为销毁方法
6.3 注解开发依赖注入
注解开发只提供了自动装配的注解实现。
@Autowired
:为引用类型属性设置值,自动装配;@Qualifier
:为引用类型属性指定注入的beanId,即可以在多个实现类中指定注入Bean的 Id;@Value
:为 基本数据类型 或 字符串类型 属性设置值;@PropertySource
:加载properties文件中的属性值,属性值可以使用@Value("${key}")
注入。
7. IOC/DI注解开发管理第三方bean
7.1 注解开发管理第三方bean步骤
- 定义外部配置类管理第三方bean
- 定义获取第三方bean的方法,并通过
@Bean
注解将方法的返回值制作为Spring管理的一个bean对象 @Bean
:设置该方法的返回值作为spring管理的bean- 在Spring配置类中使用
@Import({JdbcConfig.class})
引入第三方bean @Import
:导入配置类
7.2 注解开发实现为第三方bean注入资源
- 简单数据类型:在类中提供属性,用
@Value
注解引入值,在第三方bean的方法中传递参数进去即可; - 引用数据类型:让 Spring 扫描管理到需要的bean,为第三方bean定义方法设置形参即可,容器会根据类型自动装配对象。
7.3 注解开发与配置文件开发对比
8. Spring整合
8.1 Spring整合Mybatis
要整合的内容:
第一件事是:Spring要管理MyBatis中的SqlSessionFactory
第二件事是:Spring要管理Mapper接口的扫描
整合步骤:
- 项目中导入整合需要的jar包:spring-jdbc、mybatis-spring
- 创建Spring的主配置类
- 创建数据源的配置类 JdbcConfig ,配置 DruidDataSource
- 主配置类中读properties并引入数据源配置类
- 创建Mybatis配置类并配置SqlSessionFactory,类里面定义两个Bean,一个用于产生SqlSessionFactory对象;一个返回MapperScannerConfigurer对象,用来处理原始配置文件中的mappers相关配置,加载数据层的Mapper接口类
- SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息
- MapperScannerConfigurer加载Dao接口,创建代理对象保存到IOC容器中
- SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息
- 主配置类中引入Mybatis配置类
- 编写运行类运行
8.2 Spring整合Junit
- 引入依赖:junit、spring-test
- 在test\java下创建一个测试类,添加两个注解:
- 设置类运行器:
@RunWith(SpringJUnit4ClassRunner.class)
- 设置Spring环境对应的配置类:
@ContextConfiguration(classes = {SpringConfig.class}) //加载配置类
类里面自动装配注入 service bean,正常写 @Test 运行测试即可。
- 设置类运行器:
9. AOP
9.1 AOP 实现步骤
- 添加依赖:
spring-context
、aspectjweaver
- 定义接口与实现类、Spring的配置类、App运行类
- 定义通知类和通知
- 定义切入点
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
- 制作切面:通知方法上添加注解
@Before("pt()")
- 将通知类配给容器并标识其为切面类
@Component
、@Aspect
- 开启注解格式AOP功能:在Spring配置类上添加注解
@EnableAspectJAutoProxy
- 运行程序
9.2 AOP 的核心概念
- 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
- 作用:在不惊动原始设计的基础上为方法进行功能增强
- 核心概念:
- 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
- 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
- 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
- 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
- 切面(Aspect):描述通知与切入点的对应关系
- 目标对象(Target):被代理的原始对象称为目标对象
9.3 切入点表达式
- 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution(* com.itheima.service.*Service.*(..))
- 切入点表达式描述通配符:
- 作用:用于快速描述,范围描述
- *:匹配任意符号(常用)
- … :匹配多个连续的任意符号(常用)
- +:匹配子类类型
9.4 五种通知类型
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回后通知
- 抛出异常后通知
9.5 通知中获取参数
- 获取切入点方法的参数,所有的通知类型都可以获取参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedingJoinPoint:适用于环绕通知
- 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
- 返回后通知
- 环绕通知
- 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
- 抛出异常后通知
- 环绕通知
9.6 知识点
- AOP 配置
@EnableAspectJAutoProxy
:配置类定义上方,开启注解格式AOP功能@Aspect
:切面类定义上方,设置当前类为AOP切面类@Pointcut
:切入点方法定义上方,设置切入点方法@Before
:通知方法定义上方,设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
- 通知类型总结
@Before
@After
:通知方法定义上方,设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行@AfterReturning
:通知方法定义上方,设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行@AfterThrowing
:通知方法定义上方,设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行@Around
:通知方法定义上方,设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行,对原始方法的调用通过pjp.proceed();
实现
10. AOP 事务管理
10.1 事务管理步骤
- 在需要被事务管理的方法上添加注解
@Transactional
,可以写在接口类上、接口方法上、实现类上和实现类方法上,建议写在实现类或实现类的方法上 - 在JdbcConfig类中配置事务管理器,配置第三方 bean :DataSourceTransactionManager
- 开启事务注解:在SpringConfig的配置类中开启
@EnableTransactionManagement
- 运行测试
- 知识点:
@EnableTransactionManagement
:配置类定义上方,设置当前Spring环境中开启注解式事务支持;@Transactional
:业务层接口上方、业务层实现类上方、业务方法上方,为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)
10.2 Spring事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
10.3 Spring事务属性
- 事务配置,作为属性在
@Transactional
注解的参数上进行设置
- 事务传播行为:转账业务追加日志案例
- 创建日志表
- 添加LogDao接口
- 添加LogService接口与实现类,实现类方法配置
@Transactional
- 在转账的业务中添加记录日志
- 结果运行:正常运行时,日志表中日志记录成功;出现异常时,日志表未添加数据——日志的记录与转账操作隶属同一个事务,同成功同失败
- 需要效果:无论转账操作是否成功,日志必须保留
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
- 解决:修改logService改变事务的传播行为,在记录日志的业务方法上设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
2-SpringMVC
1. SpringMVC概述与入门案例
1.1 SpringMVC
- SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
- 优点
- 使用简单、开发便捷(相比于Servlet)
- 灵活性强
- 负责功能
- controller如何接收请求和数据
- 如何将请求和数据转发给业务层
- 如何将响应数据转换成json发回到前端
1.2 入门案例流程
- 创建 Maven 项目,不使用骨架,设置打包方式为 war 包——定义为 web 项目
- 补全目录结构
- 导入jar包:javax.servlet-api、spring-webmvc、tomcat7-maven-plugin插件
- 创建配置类
SpringMvcConfig
,添加注解:
@Configuration
@ComponentScan("com.itheima.controller")
- 创建Controller类,添加
@Controller
注解,定义方法添加@RequestMapping("/save")
注解设置当前控制器方法请求访问路径,添加注解@ResponseBody
设置返回数据为json - 使用配置类替换web.xml:将web.xml删除,换成ServletContainersInitConfig
public class ServletContainerInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
- 配置Tomcat环境
- 启动运行项目
1.3 bean加载控制 – 如何让Spring和SpringMVC分开加载各自的内容
- Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
@Configuration
@ComponentScan(value="com.itheima",
excludeFilters=@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
) p
ublic class SpringConfig {
}
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
1.4 知识点
@Controller
:SpringMVC控制器类定义上方,设定SpringMVC的核心控制器bean;@RequestMapping
:SpringMVC控制器类或方法定义上方,设置当前控制器方法请求访问路径;@ResponseBody
:SpringMVC控制器类或方法定义上方,设置当前控制器方法响应内容为当前返回值,无需解析@ComponentScan
:类定义上方,设置spring配置类扫描路径,用于加载使用注解格式定义的bean;excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes)、includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes)
1.5 PostMan 工具的使用
2. 请求与响应
2.1 请求参数
- 设置请求映射路径:当有多个 Controller 时,可能具有相同的方法名,此时可以通过在 Controller 类上面添加
@@RequestMapping("/book")
、@RequestMapping("/user")
进行区分 - GET请求中文乱码:修改pom.xml中 tomcat 插件配置来解决GET请求中文乱码问题
- POST请求中文乱码:在 ServletContainersInitConfig 中配置过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
2.2 五种类型参数传递
- 普通参数:
- 地址参数名与形参变量名相同:定义形参即可接收参数
- 形参与地址参数名不一致:写上
@RequestParam
注解
- POJO数据类型:请求参数与形参对象中的属性对应即可完成参数传递
- 嵌套POJO类型参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
- 数组类型参数:同名请求参数可以直接映射到对应名称的形参数组对象中
- 集合类型参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
- 知识点
@RequestParam
:SpringMVC控制器方法形参定义前面,绑定请求参数与处理器方法形参间的关系
2.3 JSON数据传输参数
- SpringMVC接收JSON数据的实现步骤为:
- 导入jackson包,SpringMVC默认使用的是jackson来处理json的转换
- 使用PostMan发送JSON数据:JSON普通数组、JSON对象数据、JSON对象数组
- 开启SpringMVC注解驱动,在配置类上添加
@EnableWebMvc
注解 - Controller方法的参数前添加
@RequestBody
注解
- 知识点:
@EnableWebMvc
:SpringMVC配置类定义上方,开启SpringMVC多项辅助功能@RequestBody
:SpringMVC控制器方法形参定义前面,将请求中请求体所包含的数据传递给请求参数——用于接收 json 数据@RequestParam
:用于接收url地址传参,表单传参
2.4 日期类型参数传递
@DateTimeFormat
:SpringMVC控制器方法形参前面,设定日期时间型数据格式- 参数传递内部原理:SpringMVC 做了类型转换,SpringMVC中提供了很多类型转换接口和实现类。
2.5 响应
- 响应页面(了解):将页面名称返回回去,方法返回类型为 String,此处不能添加
@ResponseBody
,如果加了该注解,会直接将页面名称 page.jsp 当字符串返回前端 - 返回文本数据(了解):添加
@ResponseBody
注解,文本数据return回去即可 - 响应 json 数据:依赖
@ResponseBody
注解和@EnableWebMvc
注解,将实体类对象或集合对象 return 即可 @ResponseBody
:SpringMVC控制器方法定义上方和控制类上,设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能。方法上有@ReponseBody
注解后- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成JSON响应给前端
3. Rest风格
3.1 REST简介
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
- http://localhost/users 查询全部用户信息 GET(查询)
- http://localhost/users/1 查询指定用户信息 GET(查询)
- http://localhost/users 添加用户信息 POST(新增/保存)
- http://localhost/users 修改用户信息 PUT(修改/更新)
- http://localhost/users/1 删除用户信息 DELETE(删除)
按照不同的请求方式代表不同的操作类型:
- 发送GET请求是用来做查询
- 发送POST请求是用来做新增
- 发送PUT请求是用来做修改
- 发送DELETE请求是用来做删除
3.1 RESTful快速开发
- 将
@RequestMapping
提到类上面,用来定义所有方法共同的访问路径/books
、/user
- 使用
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
设置当前控制器方法请求访问路径与请求动作 - 使用
@RestController
注解替换@Controller
与@ResponseBody
注解写在 Controller 类上方 - 使用
@PathVariable
解决方法形参的名称和路径{}中的值不一致问题与多个参数的区分问题 - 知识点:
@PathVariable
:SpringMVC控制器方法形参定义前面,绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应@RestController
:基于SpringMVC的RESTful开发控制器类定义上方,设置当前控制器类为RESTful风格,等同于@Controller
与@ResponseBody
两个注解组合功能@GetMapping @PostMapping @PutMapping @DeleteMapping
:基于SpringMVC的RESTful开发控制器方法定义上方,设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping
对应GET请求
3.2 案例-页面访问处理
- 问题:静态页面存放在项目的webapp目录下,访问pages目录下的books.html,SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错误。
- 解决:在 config 包下定义 SpringMvcSupport 类将静态资源进行放行
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
//设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry){
//当访问/pages/????时候,从/pages目录下查找内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
同时注意让 SpringMvcConfig 配置类扫描到 config 包中的 SpringMvcSupport 类。
4. SSM 整合
流程:
- 创建Maven的web项目,设置打包方式,补全目录
- 添加 SSM 需要的依赖:
- spring:
spring-jdbc
、spring-test
- 数据库:
mybatis
、mysql-connector-java
、druid
、mybatis-spring
- 单元测试:
junit
- servlet:
spring-webmvc
、javax.servlet-api
、jackson-databind
- tomcat:
tomcat7-maven-plugin
- spring:
- 创建项目包结构
- 创建SpringConfig配置类
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
- 创建JdbcConfig配置类,包含数据库连接池 bean 和 事务管理的 bean
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
return dstm;
}
}
- 创建MybatisConfig配置类,包含 SqlSessionFactoryBean 和 mapper映射文件的bean
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.domain");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.itheima.dao");
return mapperScannerConfigurer;
}
}
- 创建jdbc.properties
- 创建SpringMVC配置类
@Configuration
@ComponentScan({"com.itheima.controller", "com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
- 创建Web项目入口配置类
// Web 项目入口配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 加载 Spring 配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// 加载 SpringMvc 配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
// 配置 SpringMVC 请求地址拦截规则
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 设置 post 请求中文乱码过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}
- 创建数据库及表并初始化数据
- 编写模型类:domain 包下 Book 类
- 编写Dao接口:用注解编写 sql 语句:增、删、改、按 id 查询、查询所有。查询会返回查询的对象或者null,而正常增删改是没有返回值的,此时不能判断到底有没有增删改成功:将增删改的返回值改为 int ,会返回操作影响的行数,当成功操作,返回值 >0 再在 service 层返回 true 表示操作成功;否则返回 false 表示操作失败。
- 编写Service接口和实现类
- 编写Contorller类,使用 REST 风格
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book){
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
@PutMapping
public Result update(@RequestBody Book book){
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id){
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id){
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code, book, msg);
}
@GetMapping
public Result getAll(){
List<Book> list = bookService.getAll();
Integer code = list != null ? Code.GET_OK : Code.GET_ERR;
String msg = list != null ? "" : "数据查询失败,请重试!";
return new Result(code,list,msg);
}
}
- 单元测试 Service 类
- PostMan测试各个功能
5. 统一结果封装
5.1 结果封装思路
- 为了封装返回的结果数据:创建结果模型类,封装数据到data属性中
- 为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中
- 操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中
5.2 实现
- 在 controller 包下创建 Result 类:三个属性、构造方法、setter、getter
- 在 controller 包下定义返回码 Code 类:各种操作成功、失败编码(自己定义)
- 修改Controller类的返回值,让所有方法的返回值类型都是 Result ,在 return 语句中将封装好的 Result 对象返回回去
6. 异常处理
目的:出现异常的时候,也会返回一个 Result 标准结果对象回去,在 msg 属性中添加异常信息。
6.1 异常分类
- 业务异常(BusinessException):规范的用户行为产生的异常如输入错数据类型、不规范的用户行为操作产生的异常如故意传递错误的请求数据
- 解决:发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException):项目运行过程中可预计但无法避免的异常如系统宕机
- 解决:发送固定消息传递给用户,安抚用户;发送特定消息给运维人员,提醒维护;记录日志
- 其他异常(Exception):编程人员未预期到的异常如用到的文件不存在
- 解决:发送固定消息传递给用户,安抚用户;发送特定消息给编程人员,提醒维护(纳入预期范围内);记录日志
6.2 异常解决方案的实现
- 在 exception 包下定义自定义异常类,封装业务异常和系统异常
// 自定义业务异常类
public class BusinessException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message){
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause){
super(message,cause);
this.code = code;
}
}
// 自定义系统异常类
public class SystemException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message){
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause){
super(message,cause);
this.code = code;
}
}
- 将其他异常包成自定义异常:即在代码处理中遇到可以预测到的异常情况时,抛出我们自定义的异常
- try{}catch(){}在catch中重新throw我们自定义异常
- 直接throw自定义异常即可
@Override
public Book getById(Integer id) {
// 模拟业务异常,包装成自定义异常
if(id == 1){
throw new BusinessException(Code.BUSINESS_ERR,"请不要用你的技术挑战我的耐性");
}
// 模拟系统异常,将可能出现的异常包装转换成自定义异常
try {
int i = 1 / 0;
} catch (Exception e){
throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}
return bookDao.getById(id);
}
- 在 controller 包下定义异常处理器类 ProjectExceptionAdvice,在出现异常的时候依然正常返回标准的 Result 对象给前端,同时要确保SpringMvcConfig能够扫描到异常处理器类
@RestControllerAdvice
public class ProjectExceptionAdvice {
// @ExceptionHandler 用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
//其他处理:记录日志、发送消息给运维、发送邮件给开发人员
// 接替 controller 返回结果
return new Result(ex.getCode(),null,ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex){
return new Result(ex.getCode(),null,ex.getMessage());
}
// 除了自定义的异常处理器,保留对 Exception 类型的异常处理
@ExceptionHandler(Exception.class)
public Result doOtherException(Exception ex){
//其他处理
return new Result(Code.SYSTEM_UNKNOW_ERR, null, "系统繁忙,请稍后再试");
}
}
6.3 知识点
- 异常处理器:集中的、统一的处理项目中出现的异常
@RestControllerAdvice
:Rest风格开发的控制器增强类定义上方,为Rest风格开发的控制器类做增强——此注解自带@ResponseBody
注解与@Component
注解,具备对应的功能@ExceptionHandler
:专用于异常处理的控制器方法上方,设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行
7. 拦截器
7.1 拦截器概念
- 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 总结:拦截器就是用来做增强
- 拦截器和过滤器:
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
7.2 拦截器开发
- 在 controller 包下添加 interceptor 包,创建拦截器类 ProjectInterceptor,让类实现 HandlerInterceptor 接口,重写接口中的三个方法;拦截器类要被SpringMVC容器扫描到
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
@Override
//原始方法调用前执行的内容
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
@Override
//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
//原始方法调用完成后执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
System.out.println("afterCompletion...");
}
}
- 配置拦截器类:在 config 包下的 SpringMvcSupport 类中,定义一个 ProjectInterceptor 对象并自动装配,覆盖 addInterceptors 方法配置拦截器
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
}
}
- SpringMVC添加SpringMvcSupport包扫描
- 修改拦截器拦截规则,让 /books 下一级目录也被包含
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
}
- 简化SpringMvcSupport的编写
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
7.3 拦截器参数
- 前置处理方法
- 后置处理方法
- 完成处理方法
7.4 拦截器链配置
3-SpringBoot
1. SpringBoot 简介
1.1 SpringBoot 入门案例
- 创建新模块
- 选择 Spring Initializr ,用来创建 SpringBoot 工程,模块名、路径、包名自己更改
- 选中 Web ,然后勾选 Spring Web
- 经过以上步骤后就创建了如下结构的模块,它会帮我们自动生成一个 Application 类
- 选择 Spring Initializr ,用来创建 SpringBoot 工程,模块名、路径、包名自己更改
- 创建 Controller:在 com.itheima.controller 包下使用 Rest 风格创建 BookController
- 启动服务器:运行项目 com.itheima 包下的 Application 类即成功启动
- 使用 Postman 工具测试程序
- 对比
1.2 官网构建工程
- 进入SpringBoot 官网
- 选择依赖:ADD DEPENDENCES
- 生成工程:GENERATE
1.3 SpringBoot工程快速启动
- 打包:使用 Maven 的 package 指令在 target 目录下生成对应的 Jar 包
- 进入 jar 包所在位置,在 命令提示符 中输入如下命令
java -jar springboot_01_quickstart-0.0.1-SNAPSHOT.jar
1.4 SpringBoot 概述
- 起步依赖
- starter : SpringBoot 中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的
- parent : 所有 SpringBoot 项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
- 实际开发 : 使用任意坐标时,仅书写GAV中的 G(groupid) 和 A(artifactId) ,V(version) 由SpringBoot提供
- 程序启动
创建的每一个 SpringBoot 程序时都包含一个引导类:
- SpringBoot 在创建项目时,采用jar的打包方式
- SpringBoot 的引导类是项目的入口,运行 main 方法就可以启动项目
- 切换web服务器:将启动工程的服务器由 tomcat 切换为 jetty
- 排除 tomcat 服务器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
- 在 pom.xml 中引入 jetty 的起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
2. 配置文件
SpringBoot 程序的配置文件名必须是 application
2.1 yaml格式
- YAML(YAML Ain’t Markup Language),一种数据序列化格式
- 优点:
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式
- YAML 文件扩展名:
- .yml (主流)
- .yaml
- 语法规则
- 核心规则:数据前面要加空格与冒号隔开
- 三种配置文件的优先级:
application.properties
>application.yml
>application.yaml
2.2 yaml配置文件数据读取
- 环境准备:
- 创建 SpringBoot 工程
- controller 包下创建控制器
- domain 包下创建实体类封装数据
- resources 下创建一个名为 application.yml 的配置文件
lesson: SpringBoot
server:
port: 80
enterprise:
name: itcast
age: 16
tel: 138****8888
subject:
- java
- 前端
- 大数据
- 读取配置数据
- 使用
@Value
注解:
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${lesson}")
private String lesson;
@Value("${server.port}")
private Integer port;
@Value("${enterprise.subject[0]}")
private String subject_00;
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(lesson);
System.out.println(port);
System.out.println(subject_00);
return "hello , spring boot!";
}
}
Environment
对象 :SpringBoot 还可以使用@Autowired
注解注入Environment
对象的方式读取数据,SpringBoot
会将配置文件中所有的数据封装到Environment
对象中,如果需要使用哪个数据只需要通过调用Environment
对象的getProperty(String name)
方法获取
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private Environment env;
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println(env.getProperty("lesson"));
System.out.println(env.getProperty("enterprise.name"));
System.out.println(env.getProperty("enterprise.subject[0]"));
return "hello , spring boot!";
}
}
- 自定义对象:
- 将实体类
bean
的创建交给Spring
管理:在类上添加@Component
注解 - 使用
@ConfigurationProperties
注解表示加载配置文件:在该注解中也可以使用prefix
属性指定只加载指定前缀的数据 - 在
BookController
中进行注入
- 将实体类
2.3 多环境配置
- yaml文件:在
application.yml
中使用---
来分割不同的配置; - 设置启用的环境
#设置启用的环境
spring:
profiles:
active: pro #表示使用的是生产环境的设置
---
#开发
spring:
config:
activate:
on-profile: dev
server:
port: 80
---
#生产
spring:
config:
activate:
on-profile: pro
server:
port: 81
---
#测试
spring:
config:
activate:
on-profile: test
server:
port: 82
- 命令行启动参数设置
- 在运行 jar 时设置开启指定的环境的方式
java –jar xxx.jar –-spring.profiles.active=test
- 临时修改端口号
java –jar xxx.jar –-server.port=88
2.4 配置文件分类
- 1级:classpath:application.yml
- 2级:classpath:config/application.yml
- 3级:file :application.yml
- 4级:file :config/application.yml
说明:级别越高优先级越高
3. SpringBoot 整合 junit
- 环境准备:SpringBoot 工程,待测试的 service 接口、实现类,
@Service
注解 - 编写测试类:在
test/java
下创建com.itheima
包,在该包下创建测试类SpringbootQuickstartApplicationTests
(默认创建工程时生成有),将 BookService 注入到该测试类中
@SpringBootTest
class SpringbootQuickstartApplicationTests {
@Autowired
private BookService bookService;
@Test
void testSave() {
bookService.save();
}
}
4. SpringBoot 整合 Mybatis
- 创建模块:选择 Spring Initializer ,配置模块相关基础信息;选择当前模块需要使用的技术集(MyBatis、MySQL)
- 定义实体类:在 domain 包下定义实体类 Book
- 在 dao 包下定义 BookDao 接口,注解编写 getById sql语句
- 在 test/java 包 com.example 下修改测试类
SpringbootMybatisApplicationTests
:
@SpringBootTest
class SpringbootMybatisApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void testGetById() {
Book book = bookDao.getById(1);
System.out.println(book);
}
}
- 编写配置,指定要连接的数据库和连接需要的信息,在 resources 目录下
application.yml
配置文件中配置如下内容:\
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db
username: root
password: 1234
- 运行测试方法
testGetById
测试报错:在 Spring 容器中没有 BookDao 类型的 bean ,原因 ⇒Mybatis
会扫描接口并创建接口的代码对象交给Spring
管理,但是现在并没有告诉Mybatis
哪个是dao
接口,即没有最开始的指定Mapper
⇒ 在BookDao
接口上使用@Mapper
:
@Mapper
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
}
- 再次运行,测试得到 id 为1的书籍结果
- 指定 Druid 数据源: 导入 Druid 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 在
application.yml
配置文件中通过spring.datasource.type
来配置使用什么数据源配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db
username: root
password: 1234
type: com.alibaba.druid.pool.DruidDataSource
4-Maven高级
1. 分模块开发
1.1 分模块开发设计
- 按照功能拆分
- 按照模块拆分
1.2 项目拆分步骤
- 创建Maven模块
- 书写模块代码
- 通过maven指令安装模块到本地仓库(install 指令)
- 在需要该模块的地方导入依赖坐标
2. 依赖管理
2.1 依赖传递
- 依赖传递
- 依赖冲突:项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突
- 依赖冲突时版本优先级:
- 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的
- 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
- 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
- 各个坐标的依赖关系:点击Maven面板中的show Dependencies
2.2 可选依赖
- 可选依赖指对外隐藏当前所依赖的资源 —— 不透明
- 格式:
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置为可选依赖-->
<optional>true</optional>
</dependency>
2.3 排除依赖
- 排除依赖指主动断开依赖的资源,被排除的资源无需指定版本—不需要
- 格式:
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_04_dao</artifactId>
<version>1.0-SNAPSHOT</version>
<!--排除依赖-->
<exclusions>
<exclusion>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
</exclusion>
</exclusions>
</dependency>
- 可选依赖与排除依赖对比:A依赖B,B依赖C,C通过依赖传递会被A使用到,现在要想办法让A不去依赖C
- 可选依赖是在B上设置
<optional>
,A不知道有C的存在 - 排除依赖是在A上设置
<exclusions>
,A知道有C的存在,主动将其排除掉
- 可选依赖是在B上设置
3. 聚合和继承
3.1 聚合
- 聚合概念:
- 聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
- 聚合工程:通常是一个不具有业务功能的"空"工程(有且仅有一个pom文件)
- 作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
- 聚合实现步骤
- 步骤1:创建一个空的maven项目
- 步骤2:将项目的打包方式改为pom
<packaging>pom</packaging>
- 步骤3:pom.xml添加所要管理的项目
<!--设置管理的模块名称-->
<modules>
<module>../maven_02_ssm</module>
<module>../maven_03_pojo</module>
<module>../maven_04_dao</module>
</modules>
- 步骤4:使用聚合统一管理项目:执行聚合工程的
compile
指令,所有被其管理的项目都会被执行编译操作
3.2 继承
- 继承概念:
- 继承:描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
- 作用:
- 简化配置
- 减少版本冲突
- 继承实现步骤
- 步骤1:创建一个空的Maven项目并将其打包方式设置为pom,可以直接和聚合公用一个工程、
- 步骤2:在子项目中设置其父工程
<!--配置当前工程继承自 parent 工程-->
<parent>
<groupId>com.itheima</groupId>
<artifactId>maven_01_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置父项目 pom.xml 位置路径-->
<relativePath>../maven_01_parent</relativePath>
</parent>
- 步骤3:优化子项目共有依赖导入问题:将子项目共同使用的jar包都抽取出来,维护在父项目的pom.xml中;删除子项目中已经被抽取到父项目的pom.xml中的jar包;父项目中有依赖对应的jar包,子项目虽然已经将重复的依赖删除掉了,但是刷新的时候,子项目中所需要的jar包依然存在
- 步骤4:优化子项目依赖版本问题:在父工程mavne_01_parent的pom.xml来定义依赖管理
<!--定义依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencyManagement>
标签不真正引入jar包,而是配置可供子项目选择的jar包依赖;子项目要想使用它所提供的这些jar包,需要自己添加依赖,并且不需要指定<version>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
3.3 聚合与继承的对比
- 作用
- 聚合用于快速构建项目,对项目进行管理
- 继承用于快速配置和管理子项目中所使用jar包的版本
- 相同点
- 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点
- 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
- IDEA构建聚合与继承工程
- 步骤1:创建一个空的Maven项目,可以将项目中的src目录删除掉,这个项目作为聚合工程和父工程
- 步骤2:创建子项目,使该项目可以被聚合工程管理,同时会继承父工程
4. 属性
4.1 属性
- 目的:解决如 Spring 多个依赖的版本的统一管理问题
- 实现步骤:
- 步骤1:父工程中定义属性
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<mybatis-spring.version>1.3.0</mybatis-spring.version>
</properties>
- 步骤2:修改依赖的version
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
- 效果:只需要更新父工程中properties标签中所维护的jar包版本,所有子项目中的版本也就跟着更新
4.2 配置文件加载属性
- 目的:管理项目中的配置文件如
jdbc.properties
的属性 - 实现步骤:
- 步骤1:父工程定义属性
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
- 步骤2:jdbc.properties文件中引用属性
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root
- 步骤3:设置maven过滤文件范围:Maven在默认情况下是从当前项目的src\main\resources下读取文件进行打包。现在我们需要打包的资源文件是在maven_02_ssm下,需要我们通过配置来指定下具体的资源目录
<build>
<resources>
<!--设置资源目录-->
<resource>
<directory>../maven_02_ssm/src/main/resources</directory>
<!--设置能够解析${},默认是false -->
<filtering>true</filtering>
</resource>
</resources>
</build>
- 多个项目需要属性配置:
<build>
<resources>
<!--${project.basedir}: 当前项目所在目录,子项目继承了父项目,相当于所有的子项目都添加了资源目录的过滤 -->
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
- 解决有的项目是 web 项目,打包时 Maven 去找web项目的入口web.xml[配置文件配置的方式] 找不到而报错的问题:配置maven打包war时,忽略web.xml检查
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
- Maven 的内置系统属性
4.3 版本管理
- SNAPSHOT(快照版本):项目开发过程中临时输出的版本,称为快照版本
- RELEASE(发布版本):项目开发到一定阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的
- alpha版:内测版,bug多不稳定内部版本不断添加新功能
- beta版:公测版,不稳定(比alpha稳定些),bug相对较多不断添加新功能
- 纯数字版
5. 多环境配置与应用
5.1 多环境开发
实现步骤:
- 父工程配置多个环境,并指定默认激活环境
<profiles>
<!--开发环境-->
<profile>
<id>env_dep</id>
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
<!--设定是否为默认启动环境-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!--生产环境-->
<profile>
<id>env_pro</id>
<properties>
<jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
</properties>
</profile>
<!--测试环境-->
<profile>
<id>env_test</id>
<properties>
<jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
</properties>
</profile>
</profiles>
- 执行安装
install
查看env_dep环境是否生效:即安装项目,到压缩文件里找 jdbc.properties 里面的 url 配置的到底是哪个 - 切换默认环境为生产环境:在生产环境下配置
<activation>
标签 - 执行安装并查看env_pro环境是否生效
- 命令行实现环境切换:
mvn install -P env_test
- 执行安装并查看env_test环境是否生效
5.2 跳过测试
- 方式一:IDEA工具实现跳过测试
- 方式二:配置插件实现跳过测试:在父工程中的pom.xml中添加测试插件配置
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>false</skipTests>
<!--排除掉不参与测试的内容-->
<excludes>
<exclude>**/BookServiceTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
- 方式三:命令行跳过测试
6. 私服
6.1 私服
私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题
6.2 私服安装
Sonatype公司的一款maven私服产品:Nexus
下载地址:https://help.sonatype.com/repomanager3/download
6.3 私服仓库分类
- 宿主仓库hosted:保存无法从中央仓库获取的资源 —— 自主研发、第三方非开源项目
- 代理仓库proxy:代理远程仓库,通过nexus访问其他公共仓库,例如中央仓库
- 仓库组group:将若干个仓库组成一个群组,简化配置;仓库组不能保存资源,属于设计型仓库
6.4 本地仓库访问私服配置![在这里插入图片描述](https://img-blog.csdnimg.cn/3d39d843b6a446f49662e0805409bde7.png)
6.5 私服资源上传与下载
- 步骤1:配置工程上传私服的具体位置
- 步骤2:发布资源到私服
7-MyBatisPlus
1. 入门
1.1 入门案例
- 步骤1:创建数据库及表
- 步骤2:创建SpringBoot工程
- 步骤3:勾选配置使用技术
MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加。MyBatis整合Spring的jar包通过MP的依赖关系传入。 - 步骤4:pom.xml补全依赖
<!--mybatisplus 依赖导入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--druid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 步骤5:添加MP的相关配置信息:在 resources 目录下创建 application.yml 配置文件,并在文件中配置数据库连接的相关信息
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: xxxx
- 步骤6:根据数据库表创建实体类
- 步骤7:创建Dao接口,继承
BaseMapper<User>
接口
// @Mapper
public interface UserDao extends BaseMapper<User> {
}
- 步骤8:编写引导类 Mybatisplus01QuickstartApplication,通过
@MapperScan
注解扫描 Dao 包下面的所有接口,就不用在每个 Dao 接口上写@Mapper
注解
@MapperScan("com.example.dao")
@SpringBootApplication
public class Mybatisplus01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
}
}
- 步骤9:编写测试类
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//查询所有
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
1.2 MybatisPlus简介
- MyBatisPlus:基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
- 官网:https://mp.baomidou.com/
2. 标准数据层开发
2.1 标准CRUD使用
2.2 新增
在测试类中进行新增操作:
//增
@Test
void testSave() {
User user = new User();
user.setName("黑马程序员");
user.setPassword("itheima");
user.setAge(12);
user.setTel("13800001111");
userDao.insert(user);
}
2.3 删除
//删
@Test
void testDelete(){
userDao.deleteById(1608813761737584642L);
}
2.4 修改
//改
@Test
void testUpdate(){
User user = new User();
user.setId(1L);
user.setName("Tom666");
user.setPassword("tom666");
userDao.updateById(user);
}
2.5 根据 ID 查询
//根据Id查询
@Test
void testGetById() {
User user = userDao.selectById(2L);
System.out.println(user);
}
2.6 查询所有
//查询所有
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
2.7 Lombok
- Lombok 概念:一个Java类库,提供了一组注解,简化POJO实体类开发。
- 使用步骤
- 步骤1:添加lombok依赖
<!--lombok:简化实体类开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
- 步骤2:安装Lombok的插件:新版本 IDEA 已内置
- 步骤3:模型类上添加注解
- @Setter:为模型类的属性提供setter方法
- @Getter:为模型类的属性提供getter方法
- @ToString:为模型类的属性提供toString方法
- @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
- @Data:是个组合注解,包含上面的注解的功能
- @NoArgsConstructor:提供一个无参构造函数
- @AllArgsConstructor:提供一个包含所有参数的构造函数
2.8 分页功能
- 分页查询方法:
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage:用来构建分页查询条件:IPage是一个接口,其有一个实现类为Page
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
- 使用步骤
- 步骤1:调用方法传入参数获取返回值
//分页查询
@Test
void testSelectPage() {
//1.创建 IPage 分页对象,设置分页参数
IPage<User> page = new Page<>(1, 3);
//2.执行分页查询
userDao.selectPage(page,null);
//3.获取分页结果
System.out.println("当前页码值:" + page.getCurrent());
System.out.println("每页条目数:" + page.getSize());
System.out.println("一共多少页:" + page.getPages());
System.out.println("一共多少条记录:" + page.getTotal());
System.out.println("当前页数据:" + page.getRecords());
}
- 步骤2:设置分页拦截器:该拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可:创建 config 包,新建 MyBatisPlusConfig 类
@Configuration
public class MybatisPlusConfig {
//将分页拦截器插件配置成 Spring 管理的 bean 对象
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建分页拦截器对象
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加分页拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
- 步骤3:运行测试程序
- 想查看MP执行的SQL语句,可以修改application.yml配置文件
mybatis-plus:
configuration:
#打印 sql 日志到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. DQL编程控制
3.1 条件查询
- 条件查询的类:Wrapper 类
- 环境构建
- 创建一个SpringBoot项目:选择 sqlDriver
- pom.xml中添加对应的依赖:
mybatis-plus-boot-starter
、druid
、lombok
- 编写UserDao接口,添加
Mapper
注解 - 编写模型类 User,添加
Data
注解 - 编写引导类(系统生成)
- 编写配置文件:dataSource、mp日志
- 编写测试类
- 取消初始化spring日志打印:在resources目录下添加logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
- 取消MybatisPlus启动banner图标:application.yml添加如下内容:
# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
- 取消SpringBoot的log打印:application.yml添加如下内容:
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
- 构建条件查询
- QueryWrapper
@Test
void testGetAll(){
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
- QueryWrapper的基础上使用lambda
@Test
void testGetAll(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);//添加条件
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
- LambdaQueryWrapper
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- 多条件构建
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30);
lqw.gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- 构建多条件的时候,可以支持链式编程
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
- or() 查询
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- null 判定
- 前端传过来两个数据供范围查询判定是非为null,而实体类只有一个对应属性:新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性
@Data
public class UserQuery extends User {
private Integer age2;
}
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//if(null != uq.getAge2()){
// lqw.lt(User::getAge, uq.getAge2());
//}
//if( null != uq.getAge()) {
// lqw.gt(User::getAge, uq.getAge());
//}
// 简化
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.2 查询投影
- 查询指定字段
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName,User::getAge);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- 聚合查询
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
//lqw.select("count(*) as count");
//SELECT count(*) as count FROM user
//lqw.select("max(age) as maxAge");
//SELECT max(age) as maxAge FROM user
//lqw.select("min(age) as minAge");
//SELECT min(age) as minAge FROM user
//lqw.select("sum(age) as sumAge");
//SELECT sum(age) as sumAge FROM user
lqw.select("avg(age) as avgAge");
//SELECT avg(age) as avgAge FROM user
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
}
- 分组查询
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String, Object>> list = userDao.selectMaps(lqw);
System.out.println(list);
}
3.3 查询条件
- 查询条件
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
- 等值查询
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
}
- 范围查询
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge, 10, 30);
//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND?)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
- 模糊查询
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getName, "J");
//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- like():前后加百分号,如 %J%
- likeLeft():前面加百分号,如 %J
- likeRight():后面加百分号,如 J%
- 排序查询
@Test
void testGetAll(){
LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getId);
userDao.selectList(lwq)
}
3.4 映射匹配兼容性问题
- 问题1:表字段与编码属性设计不同步:
- 问题2:编码中添加了数据库中未定义的属性
- 问题3:采用默认查询开放了更多的字段查看权限(即查询不把敏感数据返回)
- 问题4:表名与编码开发设计不同步
- 知识点:
@TableField
: 模型类属性定义上方,设置当前属性对应的数据库表中的字段关系@TableName
:模型类定义上方,设置当前类对应于数据库表关系
4. DML编程控制
4.1 id生成策略控制
@TableId
:模型类中用于表示主键的属性定义上方,设置当前类中主键属性的生成策略- AUTO策略:
@TableId(type = IdType.AUTO)
private Long id;
- NONE: 不设置id生成策略,MP不自动生成,约等于INPUT
- INPUT:用户手工输入id
- ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型);可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢。
- ASSIGN_UUID:以UUID生成算法作为id生成策略;可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键。
- 简化配置:在一处进行配置,就能让所有的模型类都可以使用该主键ID策略:
mybatis-plus:
global-config:
db-config:
id-type: assign_id
- 数据库表与模型类的映射关系的简化配置:所有模型类可以不写统一的数据库表前缀
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
4.2 多记录操作
- 多条删除
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
- 批量查询
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
4.3 逻辑删除
- 删除操作:
- 物理删除:业务数据从数据库中丢弃,执行的是delete操作
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作
- 实现步骤:
- 步骤1:修改数据库表添加deleted列:自定义内容如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常
- 步骤2:实体类添加属性:
- 添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用
@TableField
进行关系映射,如果一致,则会自动对应 - 标识新增的字段为逻辑删除字段,使用
@TableLogic
- 添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用
@TableLogic(value="0",delval="1")
//value为正常数据的值,delval为删除数据的值
private Integer deleted;
- 步骤3:运行删除方法,逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。
- 执行查询操作:mp打印出来的sql语句中会多一个查询条件
- 想把已经删除的数据都查询出来:在 dao 接口中自己定义一个查询所有的方法
@Mapper
public interface UserDao extends BaseMapper<User> {
//查询所有数据包含已经被删除的数据
@Select("select * from tbl_user")
public List<User> selectAll();
}
- 简化配置:
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
@TableLogic
:模型类中用于表示删除字段的属性定义上方,标识该字段为进行逻辑删除的字段
4.4 乐观锁
- 实现思路(如秒杀应用场景):
- 数据库表中添加version列,比如默认值给1
- 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第一个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
假如这两个线程都来更新数据,第一个和第二个线程都可能先执行 - 假如第一个线程先执行更新,会把version改为2,
- 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
- 假如第二个线程先执行更新,会把version改为2
- 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
- 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
- 实现步骤:
- 步骤1:数据库表添加列
- 步骤2:在模型类中添加对应的属性
@Version
private Integer version;
- 步骤3:添加乐观锁的拦截器:可参考 mp 官方文档
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
- 步骤4:执行更新操作
@Test
void testUpdate() {
User user = userDao.selectById(3L);
User user2 = userDao.selectById(3L);
user2.setName("Jock aaa");
userDao.updateById(user2);
user.setName("Jock bbb");
userDao.updateById(user);
}
5. 快速开发
- 代码生成器实现步骤
- 步骤1:创建一个 SpringBoot Maven项目,勾选 mysqlDriver
- 代码2:导入对应的jar包:mybatisplus、druid、lombok、代码生成器mybatis-plus-generator、velocity模板引擎velocity-engine-core
- 步骤3:编写引导类
- 步骤4:创建代码生成类,可参考 MybatisPlus 官网-核心功能-代码生成器
- 步骤5:运行程序
- MP中Service的CRUD:MP提供了一个Service接口和实现类,分别是:IService和ServiceImpl,后者是对前者的一个具体实现。修改以后的好处是,MP已经帮我们把业务层的一些基础的增删改查都已经实现了,可以直接进行使用。
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test
void testFindAll() {
List<User> list = userService.list();
System.out.println(list);
}
}
未解决问题:报错 Failed to determine a suitable driver class,未找到原因