Maven

Maven 可以用于管理 Java 项目的依赖

pom.xml 文件中指定了 Maven 坐标,其构成如下:

  • groupId:通常是域名反写
  • artifactId:通常是模块名称
  • version:版本号

引入依赖:在 <dependencies> 标签下,使用 <dependency> 引入标签,并定义 Maven 坐标

依赖有传递性,可以使用 <exclusion> 手动排除依赖的资源

使用 <scope> 设置其作用范围,通常使用默认的和 <test>

Maven 有 3 套独立的生命周期:

  • clean
  • default:编译、测试、打包、部署等
  • site:生成报告、发布站点

可以手动执行其中的若干个生命周期

请求与响应

Tomcat 是一个轻量级的 web 服务器,支持 Servlet 规范

几种请求参数:

简单参数:名字一致,也可以使用一个对象接收多个参数

GET http://localhost:8080/simpleParam?name=Tome
@RequestMapping("/simpleParam")
public String simpleParam(String name) {
return "OK";
}

数组集合:可以直接用数组封装或通过 @RequestParam 绑定关系

日期参数:使用 @DateTimeFormat 转换格式,如:

@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocaoDateTime updateTime) {
return "OK";
}

json 参数,使用 @RequestBody 封装到参数中

路径参数:

@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id) {
return "OK";
}

控制器类外面加上 @RestController 注解,实现一个 RESTful 服务器,可以自动返回一个 json 对象

分成三层架构:

  • controller:接受前端的请求并响应数据
  • service:处理具体的业务逻辑
  • dao:负责数据访问操作,即增删改查

为了实现高内聚低耦合,spring 使用到了

  • 控制反转 inversion of control(IOC):对象的创建控制权由程序自身转移到容器
  • 依赖注入 dependency injection(DI):容器为程序提供运行时所依赖的资源
  • bean:IOC 容器中创建、管理的对象

要把某个对象交给 IOC 容器管理,需要加上以下注解之一:

  • @Controller:控制器
  • @Service:业务类
  • @Repository:数据访问类

以上三者和 @Component 没有什么区别,只是为了区分而取了不同的名字

可以通过 value 属性知道 bean 的名字,如果没有,则默认为类首字母小写

想要被组件扫描到需要 @ComponentScan,而 @SpringBootApplication 中默认扫描的是所在类的包及子包:

@SpringBootApplication
public class SpringBootHelloWorld {
public static void main(String[] args) {
SpringApplication.run(SpringBootHelloWorld.class, args);
}
}

使用 bean 对象时使用 @Resource 注解指定要使用注入哪一个实现:

@Resource(name = "empServiceB")
private EmpService empService;

MySQL

登录:

mysql -u用户名 -p密码 [-h服务器IP地址 -P端口号]

查询所有数据库:show databases;

查询当前数据库:select database();

使用数据库:use 数据库名;

创建数据库:create database 数据库名;

删除数据库:drop database 数据库名;

其中的 database 也可以替换为 schema

一些数据类型:charvarchartextintdoubledatedatetime

comment 注释

修改表:alter table 表名

  • 添加:add
  • 修改类型:modify 字段名 新数据类型
  • 修改字段名和类型:change 旧字段名 新字段名 类型
  • 删除:drop column 字段名
  • 修改表明:rename table 表名 to 新表名

添加数据:insert into 表名(字段1,字段2) values (值1,值2);

更新数据:update 表名 set 字段名1 = 值1 [where 条件]

删除数据:delete from 表名

MyBatis

需要引入的依赖有 MyBatis 和响应的数据库驱动(如 MySQL Driver

然后配置用户信息,如:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=1234

定义对象模型:

public class User {
private Integer id;
private String name;
}

并定义 Mapper

@Mapper
public interface UserMapper {
@Select("select * from user")
public List<User> list();
}

JDBC 是使用 Java 语言操作关系型数据库的一套 API,比较原始,所以才要使用 MyBatis 简化

数据库连接池是一个容器,负责分配、管理数据库连接,产品有 Druid、Hikari 等

Lombok 可以通过注解的形式自动生成构造器,如

注解 作用
@Data @Getter + @Setter + @ToString + @EqualsAndHashCode
NoArgsConstructor 无参构造函数
AllArgsConstructor 有各参数的构造函数

一个使用参数删除的例子:

@Delete("delete from emp where id = #{id}")
public void delete(Integer id);

注意如果使用的是 ${...},则是直接将参数拼接在 SQL 语句中

如果想要新增数据并获取返回的逐渐,则加上注解:

@Options(keyProperty = "id", useGeneratedKeys = true)

因为数据库中用下划线分割,代码中用驼峰命名法,所以需要打开自动转换的开关:mybatis.configuration.map-underscore-to-camel-case=true

XML 映射文件与 Mapper 接口同包同名,支持动态查询,

ifwhere 标签可以自动处理掉多余的 and 等):

<select id="list" resultType="com.itheima.pojo.Emp">
select id, username from emp
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
</where>
</select>

同理,还有 <set> 标签,用于 update 语句中

一个使用 <foreach> 循环生成的例子:

<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" seperator="," open="(" close=")" >
#{id}
</foreach>
</delete>

不少 sql 片段有重用,可以使用 <sql id="名字"> 并使用 <include refid="名字"> 来引用

登录

这里使用 JWT 令牌校验,依赖为 jjwt

public void genjwt(){
Map<String,Object> claims =new HashMap>();
claims.put("id" ,1);
claims.put("username" , "Tom" );
String jwt = Jwts.builder()
.setClaims(claims) // 自定义内容(载荷)
.signWith(SignatureAlgorithm.HS256, "itheima") // 签名算法
.setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)) // 有效期
.compact();
System.out.println(jwt);
}

解析令牌:

public void parsejwt()(
Claims claims =Jwts.parser()
.setSigningKey("itheima") // 指定签名秘钥
.parseClaimsjws("eyJhbGciOijIUzI1Nij9.eyJpZCI6MSwiZXhwljoxNjU50Tk1NTE3LCJ1c2VybmFtZSI6IlRvbSJ9.EUTfeqPkGslekdKBezcWCe7a7xbcllwB1MXllccTMwo") // 解析令牌
.getBody();
System.out.printin(claims);
}

拦截器 interceptor 用于拦截错误请求

首先定义拦截器:

@Component
@Override // 目标资源方法执行前执行,放回true:放行,返回false:不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("preHandle ...");
return true;
}

@Override // 目标资源方法执行后执行
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle ...");
}

@Override // 视图渲染完毕后执行,最后执行
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) {
System.out.println("afterCompletion...");
}

然后注册拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}

对于异常处理,定义一个全局异常处理器:

@RestControllerAdvice
public class GlobalExceptionHandle {
@ExceptionHandler(Exception.class);
public Result ex(Exception ex) {
ex.printStackTrace();
return Result.error("操作无效");
}
}

杂项

分页查询:

引入依赖:pagehelper-spring-boot-starter

PageHelper.startPage(pageNum, pageSize);
List<Emp> list = empMapper.list();
Page<Emp> page = (Page<Emp>)list();

也可以直接在 SQL 语句中使用 limit 实现

参数配置化:

外部服务的配置信息可以定义在 application.yml 中,然后在代码中引用,如:

aliyun:
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com

然后获取该配置:

@Value("${aliyun.oss.endpoint}")
private String endpoint;

当然,因为有共同的前缀 aliyun.oss,可以使用 @ConfigurationProperties 合并:

引入依赖:spring-boot-configuration-processor,然后在类前面加上注解 @ConfigurationProperties(prefix = "aliyun.oss") 保持变量和配置名称相同即可自动配置

文件上传

获取的是一个 MultipartFile,同时因为可能出现文件名重复的问题,要使用一个随机的 UUID 给文件命名:

@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
// 获取原始文件名
String originalFilename = image.getOriginalFilename();
// 构建新的文件名
String newFilename = UUID.randomUUID().toString()
+ originalFilename.substring(originalFilename.lastindexOf("."));
// 将文件保存在服务器端 E:/images/ 目录下
image.transferTo(new File("E:/images/" + newFilename));
return Result.success();
}
}

可以在 application.yml 中修改上传文件大小的限制:

spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件上传大小限制
max-request-size: 100MB # 多个文件上传大小限制

事务管理

事务是一组操作的集合,这些操作要么同时成功,要么同时失败

  • 开启事务
  • 提交事务
  • 回滚事务

开启事务:在 service 层的方法上、类上、接口上添加注解 @Transactional

默认情况下,只有 RuntimeException 才回滚异常,可以手动配置出现何种异常类型时回滚事务:@Transactional(rollbackFor = Exception.class)

事务传播行为:当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

  • REQUIRED:默认值,有则加入,无则创建新事物
  • REQUIRES_NEW:无论有无,总是创建新事物

还有很多其他的属性,设置方法:@Transactional(propagation = Propagation.REQUIRES_NEW)

AOP

AOP (Aspect Oriented Programming) 面向切面编程

导入依赖:spring-boot-starter-aop

一个简单的例子:

@Component
@Aspect
public class TimeAspect {
@Around("execution(* com.itheima.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object object = proceedingJoinPoint.proceed(); // 调用原始方法运行
long end = System.currentTimeMillis();
log.info(proceedingJoinPoint.getSignature() + "执行耗时:{}ms", end - begin);
return object;
}
}

核心概念:

  • 连接点 JoinPoint:可以被 AOP 控制的方法
  • 通知 Advice:共性功能
  • 切入点 PointCut:匹配连接点的条件
  • 切面 Aspect:描述通知与切入点的对应关系
  • 目标对象 Target:通知所应用的对象

通知类型:

  • @Around:在前、后都执行
  • @Before:前
  • @After:后
  • @AfterReturning:返回后通知,有异常不会执行
  • @AfterThrowing:异常后通知

注意 @Around 需要自己调用 proceedingJoinPoint.proceed() 来让原始方法运行

通知顺序:用 @Order(数字) 来控制顺序

  • 前:数字小的先执行
  • 后:数字小的后执行

切入点表达式:

execution:根据方法的签名匹配

  • 格式为 execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
  • * 单个独立的任意符号
  • .. 多个连续的任意符号

@annotation:根据注解匹配

先自定义一个注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mylog {
}

然后在需要目标对象前加上 @Mylog 即可

设置通知:

@Pointcut("@annotation(com.itheima.aop.Mylog)")
private void pt() { }

@Before("pt()")
public void before() {
log.info("MyAspect");
}

连接点可以获取方法执行时的目标类名、方法名等:

@Before("execution(* com.*(..))")
public void before(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName(); // 获取目标类名
Signature signature = joinPoint.getSignature(); // 获取目标方法签名
String methodName = joinPoint.getSignature().getName(); // 获取目标方法名
Object[] args = joinPoint.getArgs(); // 获取目标方法运行参数
}

bean 的管理

主动获取 bean:

  • 根据名称获取:Object getBean(String name)
  • 根据类型获取:<T> T getbean(Class<T> requiredType)
  • 也可以两者结合

bean 有多种作用域

  • singleton 单例,默认
  • prototype 每次使用 bean 都会创建新的实例

默认容器启动时创建,可以使用 @Lazy 延迟初始化到第一次使用时

第三方 bean 的引入:

@Configuration
public class CommonConfig {
@Bean
public SAXReader saxReader() { // 默认 bean 名称是方法名
return new SAXReader();
}
}