一、SpringBoot 背景内容梳理
-
SpringBoot是一个基于Spring框架的开源框架,用于简化Spring应用程序的初始搭建和开发过程。它通过提供约定优于配置的方式,尽可能减少开发者的工作量,使得开发Spring应用变得更加快速、便捷和高效。
-
SpringBoot的主要特点包括:
- 简化配置:SpringBoot遵循约定优于配置的原则,减少了传统Spring应用中的大量配置。它通过自动配置(auto-configuration)和起步依赖(starterdependencies)来简化项目的配置过程,让开发者可以快速搭建起一个可运行的Spring应用。
- 集成性强:SpringBoot提供了大量的开箱即用的特性和功能,如内嵌的Servlet容器(如Tomcat、Jetty或Undertow)、健康检查、指标监控等。它还整合了诸多常用的库和框架,如SpringData、SpringSecurity等,使得开发者可以快速构建出功能完善的应用。
- 微服务支持:SpringBoot非常适合用于构建微服务架构。它提供了丰富的支持,如通过SpringCloud进行微服务架构的开发,集成了服务发现、配置中心、负载均衡等功能,帮助开发者构建可伸缩、高可用的微服务系统。
- 内嵌服务器:SpringBoot可以将应用程序打包成一个可执行的JAR文件,并内置了常用的Servlet容器,如Tomcat、Jetty或Undertow。这样一来,开发者可以通过简单的java-jar命令来运行应用程序,而无需部署到外部应用服务器。
- 生态丰富:由于SpringBoot的广泛应用和强大生态系统,开发者可以轻松地使用各种扩展和插件,如Actuator、SpringBootDevTools等,提高开发效率和应用质量。
- 从前端的api到我后端的接受再到数据库:
- 发请求——>控制层接受——> 调用服务层的函数——>调用Repository——>找到对应的实体然后进行数据库的交互
其中额外设计的有 Response 的设计 ,方便前端对于后端返回数据的统一规范处理;
DTO类(用于封装数据,通常在层与层之间传递,避免直接暴露实体);
转换器的设计,方便后端设置哪些数据可以返回给对应的前端
二、Rest api 规范
-
路径
- 路径又称"终点”(endpoint),表示API的具体网址。
- 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。
-
Http 动词
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(将户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
三、JPA
JPA 提供了一种标准化的方式来进行对象关系映射,是 Java 应用程序中进行数据持久化的关键技术。通过使用 JPA,开发者可以更高效地管理数据库操作,专注于业务逻辑开发
JPA 是一个规范,多个框架实现了该规范,常见的实现包括:
- Hibernate:最流行的 JPA 实现,提供丰富的功能和强大的社区支持。
- EclipseLink:Oracle 官方的 JPA 实现,支持多种数据库。
- OpenJPA:Apache 提供的 JPA 实现。
3.1. JPA内部有关的注解
1. @Entity
- 定义:标识一个类为 JPA 实体,表示该类对应于数据库中的一张表。
2. @Table
- 定义:指定实体类对应的数据库表的名称。
- 用法:
@Table(name = "table_name")
3. @Id`
- 定义:标识实体类的主键字段。
4. @GeneratedValue`
- 定义:指定主键的生成策略。
- 常用策略:
GenerationType.IDENTITY
:主键由数据库自动生成。GenerationType.SEQUENCE
:使用数据库序列生成主键。GenerationType.AUTO
:根据数据库自动选择生成策略。
5. @Column`
- 定义:映射实体类的字段到数据库表的列。
- 常用属性:
name
:指定数据库列名。nullable
:指定该列是否可以为null
。length
:指定字符串列的最大长度。
6.@Builder
- 用于简化对象的构建过程。它通过生成一个构建器模式的实现,减少频繁的属性赋值
- 用法:
Student student = Student.builder() .name("Alice") .age(20).email("alice@example.com") .build();
3.2. JPA 的使用(JpaRepository<T, ID>)
-
JpaRepository<T, ID>是一个 Spring Data JPA 提供的接口,允许你执行 CRUD 操作。
- 第一个泛型参数
T
表示实体类的类型,即与数据库相对应的Java类 - 第二个泛型参数
ID
表示主键的类型,即用于唯一标识实体的字段,确保记录的唯一性(通常是Long
,Integer
,String
等) - 必须遵循
JpaRepository<实体类, 主键类型>
的格式。交换位置会导致错误。
- 第一个泛型参数
-
JPA的方法用法:
- 基本 CRUD 操作:
- 通过继承
JpaRepository
,可以获得基本的 CRUD 操作,如save()
,findById()
,delete()
,findAll()
等。
- 通过继承
- 查询方法命名约定:
- JPA 提供了一种约定优于配置的方法命名规则。你可以通过特定的命名规则自动生成查询。
- 示例:
findByEmail(String email)
:根据email
查找学生。findByAgeBetween(int min, int max)
:查找年龄在min
和max
之间的学生。findByNameStartingWith(String namePrefix)
:查找姓名以namePrefix
开头的学生。
- 使用
@Query
注解:- 可以使用
@Query
注解来定义自定义查询。 - 原始 SQL 查询:
- 使用
nativeQuery = true
指定这是一个原始 SQL 查询。 - 示例:
- 使用
- 可以使用
- 基本 CRUD 操作:
@Query(value = "select * from Student where email = :email", nativeQuery = true)
List<Student> findByEmail2(@Param("email") String email);
query里面的用 :email 做占位符,用来读取@Parm 中的形参
.
- JPQL 查询:
- JPQL 用于面向对象的查询,允许你直接操作实体。
- 使用 SELECT NEW
创建新对象。
- 示例:`findByEmail3(@Param(“email”) String email)
@Query(value = "SELECT NEW com.demo.springbootstudy.dao.Student(s.name, s.email) FROM Student s WHERE s.email = :email")
List<Student> findByEmail3(@Param("email") String email);
query里面的用 :email 做占位符,用来读取@Parm 中的形参
3.3 JpaSpecificationExecutor< T > 的使用
JpaSpecificationExecutor
是 Spring Data JPA 提供的接口,用于支持动态查询。通过该接口,可以使用 JPA Criteria API 构建复杂的查询条件.
.
在面对一个大项目,里面复杂的查询情况,可以单独创建一个文件夹存放不同实体的 Specification的静态定义,后续在服务层根据对应的情况,设置不同属性进行传参调用静态函数
使用:
- 创建Repority接口去继承 JpaSpecificationExecutor< T >(其中T指的是数据库表实体)
- 定义一个类存放Specification的静态方法,模板如下:
public static Specification<Student> filterByCriteria(StudentDTO studentDTO) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (studentDTO.getName() != null) {
predicates.add(cb.equal(root.get("name"), studentDTO.getName()));
}
if (studentDTO.getMinAge() != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("age"), studentDTO.getMinAge()));
}
if (studentDTO.getMaxAge() != null) {
predicates.add(cb.lessThanOrEqualTo(root.get("age"), studentDTO.getMaxAge()));
}
//两个 return 实现的效果一样
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
return cb.and(predicates.toArray(new Predicate[0]));
};
}
- `root`:表示查询的根实体,即在这里是 `Student`。
- `query`:表示查询的上下文。
- `cb`:表示构建条件的 `CriteriaBuilder`对象。
- 其中这个cb里面集成多种方法用于构建查询条件
- cb.and(predicates.toArray(new Predicate[0]));
- 动态地将所有的查询条件结合起来,形成一个整体的查询条件
- `Predicate`:表示查询条件,通过add函数来添加不同的查询条件
- .getRestriction() 用于获取它所表示的条件
- 后续调用先构建好对应的形参,然后调用上面的静态方法获取Specification;
- 最后调用Repository接口中的函数findAll,形参传入的是上面的Specification对象;
3.4 分页查找的用法
四、Mybatis 的用法
MyBatis 是一个 持久层框架,用于简化数据库操作。它提供了一种映射关系,将数据库中的记录映射到 Java 对象,并支持通过 XML 或注解的方式配置 SQL 语句。
.
MyBatis Plus 是在 MyBatis 的基础上开发的增强工具包,旨在简化开发,提高效率。它提供了 CRUD 操作、条件构造器、分页插件、代码生成器等多种功能。
4.1 配置
Pom.xml 配置
<dependencies>
<!-- MyBatis Plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- 其他依赖 -->
<!-- ... -->
</dependencies>
Spring Boot 配置(以 .yml 文件为主)
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl //sql打印到控制台
编写实体类
@Data
@TableName(value = "user")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
// @TableField(fill = FieldFill.INSERT, value = "create_time")
// private Long createTime;
// @TableField(fill = FieldFill.INSERT_UPDATE, value = "update_time")
// private Long updateTime;
// @Version
// private Long version;
}
编写Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
添加注解MapperScan
在Application类上添加@MapperScan
注解并指定mapper的包路径
Mybatis-plus 拦截器配置
配置类用于添加拦截器,如乐观锁、分页等插件
@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁拦截器
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 添加分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
4.2 常用功能介绍
1. CRUD 操作
MyBatis Plus 提供了基础的 CRUD 方法,包括 insert
、delete
、update
、select
等。
@Test
void testInsert() {
User user = User.builder()
.name("fengzhu")
.age(18)
.email("abc@a.com")
.build();
userMapper.insert(user);
}
@Test
void testSelect() {
User user = userMapper.selectById(24L);
System.out.println(user.getName());
}
@Test
void testUpdate() {
User user = User.builder()
.id(27L)
.email("fasfas@a.com").build();
int count = userMapper.updateById(user);
System.out.println("update success:" + count);
}
@Test
void testDelete() {
userMapper.deleteById(24L);
}
2. 条件构造器
条件构造器主要涉及到3个类,AbstractWrapper
,QueryWrapper
,UpdateWrapper
在AbstractWrapper中提供了非常多的方法用于构建WHERE条件,而QueryWrapper针对SELECT语句,提供了select()方法,可自定义需要查询的列,而UpdateWrapper针对UPDATE语句,提供了set()方法,用于构造set语句,条件构造器也支持lambda表达式。
AbstractWrapper中用于构建SQL语句中的WHERE条件的方法进行部分列举
- eq:equals,等于
- allEq:all equals,全等于
- ne:not equals,不等于
- gt:greater than ,大于 >
- ge:greater than or equals,大于等于≥
- lt:less than,小于<
- le:less than or equals,小于等于≤
- between:相当于SQL中的BETWEEN
- notBetween
- like:模糊匹配。like(“name”,“黄”),相当于SQL的name like ‘%黄%’
- likeRight:模糊匹配右半边。likeRight(“name”,“黄”),相当于SQL的name like ‘黄%’
- likeLeft:模糊匹配左半边。likeLeft(“name”,“黄”),相当于SQL的name like ‘%黄’
- notLike:notLike(“name”,“黄”),相当于SQL的name not like ‘%黄%’
- isNull
- isNotNull
- in
- and:SQL连接符AND
- or:SQL连接符OR
- apply:用于拼接SQL,该方法可用于数据库函数,并可以动态传参
// 条件查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
List<User> users = userMapper.selectList(queryWrapper);
// 条件更新
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name", "Tom").set("age", 27);
userMapper.update(updateWrapper);
3. Condition
条件构造器的诸多方法中,均可以指定一个boolean类型的参数condition,用来决定该条件是否加入最后生成的WHERE语句中,比如
String name = "黄"; // 假设name变量是一个外部传入的参数
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.hasText(name), "name", name);
// 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中
// 其实就是对下面代码的简化
if (StringUtils.hasText(name)) {
wrapper.like("name", name);
}
4. 实体对象作为条件
调用构造函数创建一个Wrapper对象时,可以传入一个实体对象。后续使用这个Wrapper时,会以实体对象中的非空属性,构建WHERE条件(默认构建等值匹配的WHERE条件,这个行为可以通过实体类里各个字段上的@TableField注解中的condition属性进行改变)
@Test
public void test3() {
User user = new User();
user.setName("abc");
user.setAge(28);
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
5. allEq方法
- allEq方法传入一个map,用来做等值匹配
- 当allEq方法传入的Map中有value为null的元素时,默认会设置为is null
- 若想忽略map中value为null的元素,可以在调用allEq时,设置参数boolean null2IsNull为false
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> param = new HashMap<>();
param.put("age", 40);
param.put("name", null);
wrapper.allEq(param);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
6. lambda条件构造器
- lambda条件构造器,支持lambda表达式,可以不必像普通条件构造器一样,以字符串形式指定列名,它可以直接以实体类的方法引用来指定列。
- 像普通的条件构造器,列名是用字符串的形式指定,无法在编译期进行列名合法性的检查,这就不如lambda条件构造器来的优雅。
@Test
public void testLambda() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "黄").lt(User::getAge, 30);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- `like(User::getName, "黄")`:表示查询 `User` 表中 `name` 字段包含"黄"的记录。
- `lt(User::getAge, 30)`:表示查询 `User` 表中 `age` 字段小于 30 的记录
更新操作
- updateById(T entity)
根据入参entity的id(主键)进行更新,对于entity中非空的属性,会出现在UPDATE语句的SET后面,即entity中非空的属性,会被更新到数据库,示例如下
@Test
public void testUpdate2() {
User user = new User();
user.setId(1L); // 假设要更新的用户 ID
user.setName("新的名字"); // 只更新名字,其他属性保持不变
userMapper.updateById(user);
}
- update(T entity, Wrapper<T> wrapper)
根据实体entity和条件构造器wrapper进行更新,示例如下
@Test
public void testUpdate2() {
User user = new User();
user.setName("abc");
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.between(User::getAge, 26,31).likeRight(User::getName,"吴");
userMapper.update(user, wrapper);
}
删除操作
BaseMapper一共提供了如下几个用于删除的方法
- deleteById 根据主键id进行删除
- deleteBatchIds 根据主键id进行批量删除
- deleteByMap 根据Map进行删除(Map中的key为列名,value为值,根据列和值进行等值匹配)
- delete(Wrapper<T> wrapper) 根据条件构造器Wrapper进行删除
7. 自定义Sql
- 注解
@Select("select * from user")
List<User> selectRaw();
- xml
<?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.example.mp.mappers.UserMapper">
<select id="selectRaw" resultType="com.example.mp.po.User">
SELECT * FROM user
</select>
</mapper>
public interface UserMapper extends BaseMapper<User> {
List<User> selectRaw();
}
8. 分页插件
- 配置分页插件,在
MybatisPlusConfig
类中添加分页插件配置。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
- 在查询时使用
Page
对象进行分页。
@Test
public void testPage() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 28);
// 设置分页信息, 查第3页, 每页2条数据
Page<User> page = new Page<>(3, 2);
// 执行分页查询
Page<User> userPage = userMapper.selectPage(page, wrapper);
System.out.println("总记录数 = " + userPage.getTotal());
System.out.println("总页数 = " + userPage.getPages());
System.out.println("当前页码 = " + userPage.getCurrent());
// 获取分页查询结果
List<User> records = userPage.getRecords();
records.forEach(System.out::println);
}
9. 代码生成器
- MyBatis Plus 提供了代码生成器,可以根据数据库表自动生成实体类、Mapper 接口、Service 类和 Controller 类。
public class Generator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8",
"root", "")
.globalConfig(builder -> builder
.author("aa")
.outputDir("your-path")
)
.packageConfig(builder -> builder
.parent("your-package")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.xml("mapper.xml")
)
.strategyConfig(builder -> builder
.entityBuilder()
.enableLombok()
)
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
4.3 高级功能
1. 自动填充
通过在实体类里面的字段进行设置时间戳, create_time, update_time等字段需要自动填充
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private long createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入/更新时自动填充
private long updateTime;
自定义类 MetaObjectHandler 实现 MetaObjectHandler接口,并且去重写其两个方法:
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入填充...");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新填充...");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
2. 乐观锁(并发的情况)
乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。
乐观锁的实现原理包括以下步骤:
- 读取记录时,获取当前的版本号(version)。
- 在更新记录时,将这个版本号一同传递。
- 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion。
- 如果版本号不匹配,则更新失败。
配置乐观锁插件:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
Entity对象version字段加上@version
注解
import com.baomidou.mybatisplus.annotation.Version;
@Data
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
}