前言
之前做项目都是照葫芦画瓢,从来没系统性的学习过java、spring、springboot,现在下定决心从0开始学习,本文章只为记录个人遇到的一系列问题,并直接写出来以加深印象。
java部分和spring部分没记录,直接从spring boot开始到spring cloud结束。
知识点
- 用idea初始化spring boot项目后,maven装的特别慢:需要配置maven镜像。参考具体配置
- 创建了一个yml格式的配置文件application.yml,发现中文注释报错,需要设置项目的文件编码都为utf-8。参考具体配置
- 使用@RequestMapping发现所有接口自动跳转login页面,注释pom文件中的
spring-boot-starter-security
,因为它默认所有接口收到保护 - 写了一个接口发现报错:请检查控制器是否添加了
@RestController
注解 - 写了一个bean,发现idea提示
未配置 Spring Boot 配置注解处理器
, 需要pom添加配置spring-boot-configuration-processor
即可解决。参考具体配置
自定义获取默认配置项的bean
- 写了一个获取默认配置项的bean,必须保证Java Bean的属性名称与配置一致,然后,添加两个注解
@Configuration
和@ConfigurationProperties
,就能自动对应配置文件的数据。 使用的时候,必须使用@Autowired自动注入,不能自己new出来,因为要通过springboot来处理注解等框架的东西。
代码语言:javascript复制如果标注@Configuration,则可通过Spring Boot的自动扫描机制自动加载,否则,使用@Import在启动入口引入手动加载
// application.yml配置文件
spring:
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: ${REDIS_DATABASE:0}
代码语言:javascript复制// bean文件夹
@Data
@Configuration
@ConfigurationProperties("spring.data.redis")
public class RedisConfigBean {
private String host;
private int port;
private String password;
private int database;
}
代码语言:javascript复制// 控制器使用 使用Bean来获取配置项,必须使用@Autowired自动注入
@Autowired
RedisConfigBean redisConfigBean;
@Operation(summary = "Redis测试")
@PostMapping(value = "/redis")
public void list2() {
System.out.println(redisConfigBean.getPort());
}
使用@Bean注解来注册bean
- 或者使用@Bean注解来注册一个bean:将第三方类,按照使用Bean的方式注册到容器中,只会调用一次,在其他地方就可以跟正常使用bean一样使用,先自动注入,然后就是使用。
代码语言:javascript复制跟上文中主要区别就是需要用@Bean进行声明,并且没有
@Configuration
注解
// 比如有个class,不能自己添加比如@Configuration等其他注解
public class SomeClass {
private int a = 1;
private int b = 2;
public int getA() {
return a*100;
}
public int getB() {
return b*100;
}
}
代码语言:javascript复制// 想让他变成一个bean注册到容器中,在config或者其他文件夹加一个挂载类
@Component
public class ThirdPartBean {
@Bean
public SomeClass someClass() {
return new SomeClass();
}
}
// 然后就可以在任何地方使用了,跟使用bean一样
@RestController
@RequestMapping("/third")
public class ThirdController {
@Autowired
SomeClass someClass;
@GetMapping(value = "/getbean", produces = "application/json")
public void getBean() {
System.out.println(someClass.getA());
}
}
常见注解的理解
- @Repository、@Component、@Service、@Controller之间的区别与联系具体参考文档:
在Spring2.5版本中,引入了更多的Spring类注解:@Component,@Service,@Controller。
@Component是一个通用的Spring容器管理的单例bean组件。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。
当你的一个类被@Component所注解,那么就意味着同样可以用@Repository, @Service, @Controller来替代它:
@Component最普通的组件,可以被注入到spring容器进行管理
@Repository作用于持久层,作为DAO对象(数据访问对象,Data Access Objects),可以直接对数据库进行操作
@Service作用于业务逻辑层,处理业务逻辑
@Controlle作用于表现层(spring-mvc的注解),前端请求的处理,转发,重定向
总结:@Service, @Controller, @Repository = {@Component 一些特定的功能}。这个就意味着这些注解在部分功能上是一样的。都属于spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理。
使用swagger生成接口文档
swagger3基于openApi3,大部分注解都跟老的不一样了,参考 https://blog.csdn.net/qq_35425070/article/details/105347336
- 添加接口文档包,swagger
<!-- 接口文档 swagger UI 本地访问 http://localhost:8080/swagger-ui/index.html-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
代码语言:javascript复制// 代码示例
@Operation(summary = "测试自动加载通过@Bean注入的bean")
@GetMapping(value = "/getbean", produces = "application/json")
public void getBean() {
System.out.println(someClass.getA());
}
@Operation(summary = "swagger标题摘要:path参数以及url参数", description = "下方描述:访问示例http://localhost:8080/third/list1/12?sort=11212")
@GetMapping(value = "/list1/{id}", produces = "application/json")
public List list1(
@PathVariable(value = "id", required = true) @Parameter(description = "path参数") int id,
@RequestParam(value = "sort", required = false, defaultValue = "0") @Parameter(description = "url参数") int sort
) {
List list = new ArrayList();
list.add("A");
list.add("B");
list.add(32);
list.add("32");
list.add(id);
list.add(sort);
System.out.println(list);
return list;
}
Maven 相关知识
代码语言:javascript复制 // # 模块管理
// 在软件开发中,把一个大项目拆分成多个模块是降低软件复杂度的有效方法
// 对于Maven来说,原来是一个大项目
//
//single-project
//├── pom.xml
//└── src
//
// 现在可以分拆成三个模块
//
//mutiple-project
//├── module-a
//│ ├── pom.xml
//│ └── src
//├── module-b
//│ ├── pom.xml
//│ └── src
//└── module-c
// ├── pom.xml
// └── src
// Maven可以有效的管理多个模块,我们只需要把每个模块当作一个独立的Maven项目,他们有各自独立的pom.xml
// - 首先创建一个parent项目,把abc项目中重复的提取出来,他继承springframework,并定好各个包的版本号以及引入共同依赖包(<packaging>pom</packaging>)
// - 之后,其他项目比如a,b,c就可以继承parent项目(<packaging>jar</packaging>)
// - 最后,在编译的时候,还要在根目录创建一个pom.xml(也就是build pom)统一编译,其中规定了 <modules>,比如
// <modules>
// <module>parent</module>
// <module>module-a</module>
// <module>module-b</module>
// <module>module-c</module>
// </modules>
//
// 这样,在根目录执行 mvn clean package时,maven根据根目录的pom.xml找到包括parent在内的4个<module>,一次性编译
JDBC和连接池
- JDBC链接: JDBC是一套接口规范,类似PHP的PDO,是协议,我们把数据库实现了JDBC接口的jar包称为JDBC驱动,比如mysql的包
mysql-connector-java
jdbc:mysql://<hostname>:<port>/<db>?key1=value1&key2=value2
jdbc:mysql://localhost:3306/fmock?useSSL=false&characterEncoding=utf8
- 连接池:目前使用最广泛的就是hikariCP
// 1. 首先
// 添加他的依赖
// <dependency>
// <groupId>com.zaxxer</groupId>
// <artifactId>HikariCP</artifactId>
// <version>4.0.3</version>
// <type>bundle</type>
// </dependency>
// 2.接着
// 获取数据库连接
@SneakyThrows
public static Connection getConnection() {
String JDBC_URL = "jdbc:mysql://localhost:3306/fmock";
String JDBC_USER = "root";
String JDBC_PWD = "root";
HikariConfig config = new HikariConfig();
config.setJdbcUrl(JDBC_URL);
config.setUsername(JDBC_USER);
config.setPassword(JDBC_PWD);
config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
// 注意创建DataSource也是一个非常昂贵的操作,所以通常DataSource实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期
DataSource ds = new HikariDataSource(config);
// 有了连接池以后,我们如何使用它呢?
// 和前面的代码类似,只是获取Connection时,把DriverManage.getConnection()改为ds.getConnection()
// 第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。
// 当我们调用conn.close()方法时(在try(resource){...}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回
Connection conn = ds.getConnection();
// Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PWD);
return conn;
}
// 关闭数据库连接(释放到连接池)
public static void closeConn(Connection conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
自动生成getter和setter方法的包:lombok
- 自动在类上生成getting()和setting()方法:lombok包
// pom中添加如下依赖即可,版本号统一配置就行,在parent的dependencyManagement里有
// 注意:dependencyManagement中定义的只是依赖的声明,并不实现引入,因此子项目需要显式的声明需要用的依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
// 添加完后,在类上添加 `@Data` 注解即可
关于pom配置文件抽离parent,官方spring-boot就是这么做的
- pom使用parent统一管理的好处:properties和dependencyManagement中统一定义了依赖包的版本号
代码语言:javascript复制参考链接
如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号。
当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要逐个修改子项目;另外如果某个子项目需要另外的一个版本,只需要声明version即可。
想使用redis:
代码语言:javascript复制首先,redis/jedis是redis官方推出的面向JAVA的客户端; 其次,Spring-data-redis是spring大家族的一部分,包含了jedis依赖,同时还有很多其他包,有连接池自动管理功能,提供高度封装的RedisTemplate类,对jedis大量api进行归类封装,并提供很多便捷化方法; 然后,
spring-boot-starter-data-redis
是spring-boot的包,其中就包含了spring-data-redis依赖和lettuce等; 最后Redisson,Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。 (在spring-boot项目中,可以直接用专门的包redisson-spring-boot-starter
,直接帮你了很多autoconfig的事,它里面就有spring-boot-starter-data-redis
,所以可以用RedisTemplate类,清晰了吧。 当然,你也可以直接用Redisson,然后自己进行配置,new对象使用。就好比laravel的一些包,可以直接用"ORM"包,也可以用"Larave-ORM"的专属包)
// 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.4</version>
</dependency>
代码语言:javascript复制// 默认只有`RedisTemplate<Object, Object>` 和 `RedisTemplate<String, String>`, 所以需要
// 配置一个`RedisTemplate<String, Object>`类型的Bean,并初始序列化方式,否则存入的redis会有乱码
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
//key haspmap序列化
template.setHashKeySerializer(redisSerializer);
return template;
}
}
代码语言:javascript复制// 使用
@RestController()
@RequestMapping(value = "four")
public class FourController {
// Java变量的初始化顺序为:静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 –> @Autowired
private RedisService redisService;
/* 构造方法注入
public FourController(RedisService redisService) {
this.redisService = redisService;
}
*/
// 推荐使用基于set的自动注入,可以在构造函数执行之后才自动注入
@Autowired
private void init(RedisService redisService) {
this.redisService = redisService;
}
@GetMapping(value = "redis1")
public void redis1() {
System.out.println(redisService.get("key111"));
System.out.println(1221);
}
}
使用数据库mysql spring-boot-starter-jdbc
代码语言:javascript复制1.首先
mysql-connector-j
的前身是mysql-connector-java
,是MySQL提供的JDBC驱动包drive,实现了java.sql.Driver的规范,换数据库直接换drive即可。 2.spring-boot-starter-jdbc
和spring-boot-starter-data-jdbc
都是springboot提供的,前者是基础包,后者是升级版(是data系列的包,同样的还有data-redis,data-elasticsearch等)。 3.mybatis-spring-boot-starter
包含spring-boot-starter-jdbc
,不再需要单独引入spring-boot-starter-jdbc
了。 4.mybatis
是个第三方包,任何框架都能使用,mybatis-spring-boot-starter
是针对spring-boot专门配置的一个包,包含了starter自动装载等功能。 5.Spring Boot作为Spring的集大成者,spring-jdbc
和spring-data-jdbc
就是spring-boot-starter-jdbc
和spring-boot-starter-data-jdbc
在spring里的东西。
// 先引入依赖包
<!-- 数据库相关 mysql-connector-j是驱动包,使用mysql必须装。driver-class-name中可以指定 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 这个包里就引入了spring-jdbc, 使用JdbcTemplate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
代码语言:javascript复制// application.yml添加数据库配置
spring:
application:
name: ${APP_NAME:我的测试JAVA项目}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fmock
username: root
password: root
代码语言:javascript复制// 已经能使用了,随便写个组件,
// 增删改都是jdbcTemplate.update方法,只有查询方法不一样,有query,queryForObject,queryForList等
@Component
public class DBService {
@Autowired
JdbcTemplate jdbcTemplate;
// 在JdbcTemplate中,除了查询有几个API之外,新增、删除与修改统一都使用update来操作,传入SQL即可.
// update方法的返回值就是SQL执行受影响的行数.
// 插入单条
public int addUser(User user) {
String sql = "insert into users(uuid, email, mobile, name, password) value (?, ?, ?, ?, ?)";
// return jdbcTemplate.update(sql);
return jdbcTemplate.update(sql, user.getUuid(), user.getEmail(), user.getMobile(), user.getName(), user.getPassword());
}
// 批量插入
public int[] batchAddUser(User user) {
String sql = "insert into users(uuid, email, mobile, name, password) value (?, ?, ?, ?, ?)";
List<Object[]> batchValue = new ArrayList<Object[]>();
batchValue.add(new Object[]{"uuid-tets", "8888@qq.com", "12111111111", "测试jdbctemplate", "xxxxxxxx"});
batchValue.add(new Object[]{user.getUuid(), user.getEmail(), user.getMobile(), user.getName(), user.getPassword()});
return jdbcTemplate.batchUpdate(sql,batchValue);
}
// 更新数据
public int updateUser(User user) {
String sql = "update users set name = ?, email = ? where id = ?";
return jdbcTemplate.update(sql, "修改后的名字", user.getEmail(), user.getId());
}
// 删除
public int delUser(long id){
String sql = "delete from users where id = ?";
return jdbcTemplate.update(sql, id);
}
// 查询,查询就不使用update方法了
// queryForObject查询的结果不能空,且只能1条数据:nullableSingleResult
// 使用new BeanPropertyRowMapper<>(User.class)对返回的数据进行封装,它通过名称匹配的方式,自动将数据列映射到指定类的实体类中(如果列名和属性名不同,就需要开发者自己实现 RowMapper 接口)
public User find(long id) {
String sql = "select * from users where id = ?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
return jdbcTemplate.queryForObject(sql, rowMapper, id);
}
// queryForList返回多条或者0条都可以
public List<Map<String, Object>> find2(long id) {
String sql = "select * from users where id > ?";
return jdbcTemplate.queryForList(sql, id);
}
// 直接使用query方法查询,能自定义映射类型rowMapper
public List<User> find3(long id) {
String sql = "select * from users where id > ?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql, rowMapper, id);
}
}
代码语言:javascript复制// 控制器调用测试
@RestController()
@RequestMapping(value = "four")
public class FourController {
// Java变量的初始化顺序为:静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 –> @Autowired
private RedisService redisService;
private DBService dbService;
/* 构造方法注入
public FourController(RedisService redisService) {
this.redisService = redisService;
}
*/
// 推荐使用基于set的自动注入,可以在构造函数执行之后才自动注入
@Autowired
private void init(RedisService redisService, DBService dbService) {
this.redisService = redisService;
this.dbService = dbService;
}
@GetMapping(value = "redis1")
public void redis1() {
System.out.println(redisService.get("key111"));
System.out.println(1221);
}
@GetMapping(value = "db2")
public int addUser() {
User user = new User();
user.setMobile("13333333333");
user.setEmail("13333333333@qq.com");
user.setName("name-db2");
user.setPassword("xxxx");
user.setUuid("uuid-xxxx");
return dbService.addUser(user);
}
@GetMapping(value = "db3")
public int[] batchAddUser() {
User user = new User();
user.setMobile("13333333333");
user.setEmail("13333333333@qq.com");
user.setName("name-db2");
user.setPassword("xxxx");
user.setUuid("uuid-xxxx");
return dbService.batchAddUser(user);
}
@GetMapping(value = "db4")
public int updateUserEmail() {
User user = new User();
user.setEmail("fffffff@qq.com");
user.setId(37);
return dbService.updateUser(user);
}
@Operation(summary = "根据ID删除某个用户")
@GetMapping(value = "db5/{id}", produces = "application/json")
public int delUserById(
@PathVariable(value = "id") @Parameter(description = "待删除的用户ID") long id
) {
return dbService.delUser(id);
}
@Operation(summary = "使用queryForObject查询单个用户")
@GetMapping(value = "db6/{id}", produces = "application/json")
public User findUserById(
@PathVariable(value = "id") @Parameter(description = "查询ID") long id
) {
return dbService.find(id);
}
@Operation(summary = "queryForList返回多条或者0条都可以")
@GetMapping(value = "db7/{id}", produces = "application/json")
public List<Map<String, Object>> getUserList(
@PathVariable(value = "id") @Parameter(description = "查询大于ID的列表") long id
) {
List<Map<String, Object>> list = dbService.find2(id);
return list;
}
@Operation(summary = "query返回多条或者0条都可以")
@GetMapping(value = "db8/{id}", produces = "application/json")
public List<User> getUserListQuery(
@PathVariable(value = "id") @Parameter(description = "查询大于ID的列表") long id
) {
List<User> list = dbService.find3(id);
return list;
}
}
JPA : Java Persistence API
JPA就是JavaEE的一个ORM标准,他只规定了接口,所以还需要选择一个实现产品,跟JDBC接口和Mysql驱动一个意思。
Hibernate
和MyBatis
都是优秀的第三方ORM框架,我们即可以直接使用JPA也就是jakarta.persistence
这个“标准”包,也可以用第三方包如MyBatis
等。
MyBatis & MyBatis-plus:ORM框架
对比Spring提供的JdbcTemplate和ORM框架,有如下差别:
- 查询后需要手动提供mapper实例,以便把ResultSet的每一行变成Java对象(自定义映射类型rowMapper)。
- 增删改操作,所有需要的参数要手动传入,比如传参
user.id/user.name
等这样。 - 但是JdbcTemplate的优势是确定性,即每次读取操作一定是数据库操作而不是缓存,确定是代码比较繁琐。
介于全自动ORM如Hibernate,和手写全部SQL的比如JdbcTemplate之间,还有一种半自动的ORM,它只负责把ResultSet自动映射到JavaBean,或者自动填充JavaBean参数,但仍需要自己写Sql, MyBatis就是这种半自动化ORM框架。
代码语言:javascript复制首先需要引入
mybatis-spring-boot-starter
包,这个包里有mybatis-spring
和mybatis
, 还有spring-boot-starter-jdbc
,以及自动配置的一些starter、autoconfigure包。
// 首先,spring-boot项目中使用的话,需要安装 `MyBatis-Spring-Boot-Starter` 依赖,之后就可以使用`@Mapper`注解(默认搜寻带有 @Mapper 注解的 mapper 接口)
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
代码语言:javascript复制// 创建一个mapper比如/mapper/UserMapper.java,注意mapper必须是接口
// `@Select`中即可以写sql,支持使用`#{}`占位符参数,多个参数时可以用`@Param`进行标记匹配,示例如下:
// `@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")`
// `List<User> getAll(@Param("offset") int offset, @Param("maxResults") int maxResults);`
package com.example.springbootstudy.mapper;
import com.example.springbootstudy.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select(value = "select count(id) from users")
int getAllCount();
@Select("select name from users where id = #{id}")
String getUserName(long id);
// mybatis会根据方法的返回类型自动把ResultSet的每一行转换为User实例,不需要像jdbc那样使用RowMapper手动映射
// 如果列名和属性名不同,可以通过select语句的别名`as`满足匹配
@Select("select * from users where id = #{user_id}")
User getUserInfo(@Param("user_id") long id);
@Select("select * from users where id > #{id}")
List<User> getUserList(@Param("id") long id);
// 使用@Options注解,可以获取插入后的id,通过user对象
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("insert into users(uuid, email, mobile, password, name) values(#{userObj.uuid}, #{userObj.email}, #{userObj.mobile}, #{userObj.password}, #{userObj.name})")
void addUser(@Param("userObj") User user);
// 更新
@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")
void update(@Param("user") User user);
// 删除
@Delete("DELETE FROM users WHERE id = #{id}")
void deleteById(@Param("id") long id);
}
代码语言:javascript复制// 创建一个控制器,依赖注入mapper,即可简单调用mybatis
@RestController
@RequestMapping(value = "/five", name = "测试mybatis的控制器")
public class FiveController {
private final UserMapper userMapper;
// 将mapper依赖注入
public FiveController(UserMapper userMapper) {
this.userMapper = userMapper;
}
@GetMapping(value = "mb1", name = "测试方法mb1")
public int mb1() {
System.out.println("mb1");
return userMapper.getAllCount();
}
@Operation(summary = "获取用户名字")
@GetMapping(value = "mb2")
public String mb2(
@RequestParam(value = "id") @Parameter(description = "url参数") long id
) {
return userMapper.getUserName(id);
}
@Operation(summary = "获取用户信息")
@GetMapping(value = "mb3/{id}")
public User mb3(
@PathVariable(value = "id") @Parameter(description = "path参数") long id
) {
return userMapper.getUserInfo(id);
}
@Operation(summary = "获取用户列表")
@GetMapping(value = "mb4/{id}")
public List<User> mb4(
@PathVariable(value = "id") @Parameter(description = "path参数") long id
) {
return userMapper.getUserList(id);
}
@Operation(summary = "新增用户,并返回ID")
@GetMapping(value = "mb5")
public int mb5() {
User user = new User();
user.setEmail("litblc@xxx.com");
user.setUuid("litblc-uuid");
user.setMobile("13111111111");
user.setPassword("xxx");
user.setName("大修哥");
userMapper.addUser(user);
// 获取刚刚插入的user自增id
return user.getId();
}
}
更多资料参考 https://www.liaoxuefeng.com/wiki/1252599548343744/1331313418174498
使用xml方式使用mybatis
在application.yml中添加mapper-locations
配置,指定待解析的mapper的xml文件
mybatis:
# 待解析的mapper的xml文件
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
在同级文件夹(也就是resources)下创建mapper文件夹,可以用别的名称,与上面配置的路径名称对应上即可。
创建一个UserMapper.xml文件(注意的是<!DOCTYPE mapper
标签必须有,而且指定是mapper):
<?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.springbootstudy.mapper.UserMapper">
<select id="findByUUID" resultType="com.example.springbootstudy.dao.UserInfoDao">
SELECT * FROM users WHERE uuid=#{uuid}
</select>
<select id="getAll" resultType="com.example.springbootstudy.dao.UserInfoDao">
SELECT * FROM users order by id desc
</select>
</mapper>
这时,你的UserMapper.java
中定义这个findByUUID
接口,就不需要使用注解定义sql了:
@Mapper
public interface UserMapper {
@Select(value = "select count(id) from users")
int getAllCount();
@Select("select name from users where id = #{id}")
String getUserName(long id);
// ...
// 测试使用xml中配置sql语句
UserInfoDao findByUUID(@Param("uuid") String uuid);
// 返回map的话必须指定map的key用哪个字段
// List<UserInfoDao> getAll();
@MapKey("id")
Map<Integer, UserInfoDao> getAll();
}
使用上是一样的:
代码语言:javascript复制@Operation(summary = "测试使用mybatis的xml方式")
@GetMapping(value = "mb6/{id}")
public UserInfoDao mb6(
@PathVariable(value = "id") @Parameter(description = "path参数") String uuid
) {
System.out.println(uuid);
return userMapper.findByUUID(String.valueOf(uuid));
}
@Operation(summary = "测试使用mybatis的xml方式-返回list/map")
@GetMapping(value = "mb7")
public Map<Integer, UserInfoDao> mb7() {
return userMapper.getAll();
}
总结:MyBatis完全手写,每增加一个查询都需要先编写SQL并增加接口方法。
更多参考 https://blog.csdn.net/m0_37989980/article/details/105588234
mybatis-plus:更方便的使用mybatis
更多了解参考官网 https://baomidou.com/
mybatis-plus具有无侵入、损耗小、只做增强不做改变、强大的 CRUD 操作、丰富的查询构造器等特点,在国内十分火爆。
- 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
- 创建实体模型entity
package com.example.springbootstudy.entity;
//import jakarta.persistence.Table;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
// `@TableName`是mp的注解,主要是实现实体类型和数据库中的表实现映射。
// 不要将`@TableName`和`@Table`注解认为是一个,虽然功能相同,但是,`@TableName`是`mybatis-plus`中的注解, `@Table`是`Hibernate`中的注解(JPA)
@Data
@TableName(value = "posts")
public class Poster {
private Long id;
private String uuid;
private Long user_id;
private String title;
private String summary;
private String poster;
private String content;
}
- 创建与实体entity对应的mapper接口:PostsMapper
package com.example.springbootstudy.mapper;
import com.example.springbootstudy.entity.Poster;
import org.apache.ibatis.annotations.Mapper;
// `@Mapper`注解可以在每个mapper上定义,也可以在`Spring Boot`启动类中添加 `@MapperScan`直接扫描整个文件夹
@Mapper
public interface PostsMapper extends IBaseMapper<Poster> {
// 添加复杂的操作数据库方法,就可以在xml中写,跟mybatis用法一样
}
上面继承的IBaseMapper是自定义的,继承自mybatis-plus的BaseMapper,提供了一些封装好的通用的curd操作,通过源码可以看到。
代码语言:javascript复制package com.example.springbootstudy.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// mapper父类,注意这个类不要让 mp 扫描到!!
public interface IBaseMapper<T> extends BaseMapper<T> {
// 这里可以放一些自定义的公共的方法
}
- 控制器使用测试(一般是由service调用,这里偷懒了直接控制器写逻辑测试用)
// 使用@RestController替代@Controller后,每个方法自动变成API接口方法。直接返回json。
// @Controller
@RestController
@RequestMapping("/six")
public class SixController {
private final PostsMapper postsMapper;
public SixController(PostsMapper postsMapper) {
this.postsMapper = postsMapper;
}
@GetMapping(value = "f3")
public List<Poster> func3() {
List<Poster> list = postsMapper.selectList(null);
return list;
}
}
- mybatis-plus进阶学习
插入一条数据自增ID, 并学习使用lombok的@Accessors(chain = true)
注解
@Data
// 链式访问,该注解设置chain=true,生成setter方法返回this(也就是返回的是对象),代替了默认的返回void
@Accessors(chain = true)
@TableName(value = "posts")
public class Poster {
// 指定自增策略,这样insert的时候id才是自增的
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String uuid;
// 比如没开启自动转驼峰的配置项map-underscore-to-camel-case:false,或者名字不一样可以自行指定
@TableField(value = "user_id")
private Long userId;
private String title;
private String summary;
private String poster;
private String content;
// 自动维护创建、更新时间
// 如何返回创建后的实体
}
添加mybatis-plus的配置,配置带解析的mapper的xml文件地址,方便进行自定义复杂sql查询
代码语言:javascript复制mybatis:
# 待解析的mapper的xml文件
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
...
mybatis-plus:
# 待解析的mapper的xml文件,跟上面一样意思
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: false
在PostsMapper中添加一个自定义sql的方法,并在PostsMapper.xml中补全
代码语言:javascript复制@Mapper
public interface PostsMapper extends IBaseMapper<Poster> {
// 添加复杂的操作数据库方法,就可以在xml中写,跟mybatis用法一样
List<UserInfoDao> getAll();
}
新建一个xml,主要注意namespace别写错喽,不然找不到
代码语言:javascript复制<mapper namespace="com.example.springbootstudy.mapper.PostsMapper">
<select id="getAll" resultType="com.example.springbootstudy.dao.UserInfoDao">
SELECT * FROM users order by id desc
</select>
</mapper>
控制器调用示例
代码语言:javascript复制 @Operation(summary = "新增文章")
@GetMapping(value = "f4")
public Poster func4() {
// 在entity上定义了`@Accessors(chain = true)`,就可以直接链式调用所有方法,因为setter方法返回当前对象了
Poster poster = new Poster().setPoster("https://xxx/xxx.jpg");
poster.setUuid("xxx-uuid-11");
poster.setTitle("测试").setSummary("摘要").setUserId(10L).setContent("{"a":32}");
postsMapper.insert(poster); // 返回插入数量
return poster;
}
@Operation(summary = "测试xml方式的sql查询")
@GetMapping(value = "f6")
public List<UserInfoDao> func6() {
return this.postsMapper.getAll();
}
- JSON格式字段咋处理
当数据库存储json时候,建议字段仍然用text
、varchar
等字符串格式,entity对应也用String
,保存入库的时候正常按照字符串类型操作;
查询的时候想返回JSON格式的话,可以再写个dto, 把字段改成JSONObject
格式,然后手动赋值进去(JSONObject
是使用com.alibaba
的fastjson
依赖)。
如果数据库字段格式是json,那entity也必须是json格式的了,比如:
- 引入json包
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.41</version>
</dependency>
- entity中的
jsonInfo
字段是json格式
@Data
@Accessors(chain = true)
@TableName(value = "reports", autoResultMap = true)
public class Report {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long resourceId;
private String reason;
private String type;
// json字段要使用`com.alibaba.fastjson.JSONObject`的这个类型,可以不定义json具体字段格式,并且不会报错没序列化错误
// 同时要设置`typeHandler`,这样获取的时候就是json,只不过存的时候需要同样生成该类型json入库
@TableField(value = "json_info", typeHandler = JacksonTypeHandler.class)
private JSONObject jsonInfo;
- 创建mapper接口,注入entity
@Mapper
public interface ReportMapper extends IBaseMapper<Report>{
}
- 控制器读写
package com.example.springbootstudy.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.springbootstudy.entity.Report;
import com.example.springbootstudy.mapper.ReportMapper;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/seven")
public class SevenController {
private final ReportMapper reportMapper;
public SevenController (ReportMapper reportMapper) {
this.reportMapper = reportMapper;
}
@GetMapping(value = "/s1")
public Report s1() {
return this.reportMapper.selectById(1);
}
@Operation(summary = "测试json字段或者json格式的字符串读写问题")
@GetMapping(value = "/s2")
public Report s2() {
// 使用同样的json格式化类型
JSONObject jsonInfo = JSON.parseObject("{"a":3222}");
Report report = new Report().setUserId(2L)
.setResourceId(23L).setType("post")
.setJsonInfo(jsonInfo)
.setReason(String.valueOf(jsonInfo));
this.reportMapper.insert(report);
return report;
}
}
- 想自动更新某些字段,比如创建时间、更新时间、删除时间等
首先在entity字段中添加@TableField(fill = FieldFill.INSERT)
// 创建时自动填充
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
// 创建与修改时自动填充
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
添加了fill注解并不会字段维护,而是需要写个handle处理,比如写个组件类专门设置/component/MybatisPlusAutoFill.java
package com.example.springbootstudy.component;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.example.springbootstudy.constant.MybatisPlusAutoFillProperties;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义实现类
* @Author zhenhuaixiu
* @Date 2023/10/16 10:55
* @Version 1.0
*/
@Component
public class MybatisPlusAutoFill implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// String format = "yyyy-MM-dd HH:mm:ss";
// String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern(format));
this.strictInsertFill(metaObject, MybatisPlusAutoFillProperties.CREATED_AT, LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, MybatisPlusAutoFillProperties.UPDATED_AT, LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
System.out.println("updateFill");
this.strictUpdateFill(metaObject, MybatisPlusAutoFillProperties.UPDATED_AT, LocalDateTime::now, LocalDateTime.class);
}
}
上面的自动填充类调用了MybatisPlusAutoFillProperties
,是我们写的所有需要自动更新的字段常量类,比如/constant/MybatisPlusAutoFillProperties.java
package com.example.springbootstudy.constant;
public class MybatisPlusAutoFillProperties {
// 字段要与entity对应上,咱们开启了自动转驼峰,所以是驼峰形势的
public static final String CREATED_AT = "createdAt";
public static final String UPDATED_AT = "updatedAt";
}
这样在我们再次调用insert或者update的时候,就能看到更新了。
mybatis-plus就写到这,其他的去官网看,特别详细 https://baomidou.com/
项目代码分层解读
更多参考 https://www.jianshu.com/p/18c4418e9b99
- Entity: 实体层,一个表对应一个,相当于php的model
- Dao:持久层,用来封装操作数据的方法,相当于php的repositories层,会调用entity。(使用mybatis的mapper层其实就相当于Dao层)
- Service:业务层,与php一致,调用dao层接口,接收dao层返回的数据,完成项目的基本功能设计
- Controller:控制层,与php一致,前后端交互,接受前端请求,调用service层并接收返回的数据
- 总体流程:
Controller-->service接口-->serviceImpl-->dao接口-->daoImpl-->mapper-->db
Dao VS Mapper Dao通常被认为是传统的Java数据访问层,它将数据访问的逻辑封装在一组DAO接口和实现类中。这些接口和实现类主要用于将Java对象映射到数据库表,并执行一些数据操作,例如插入、更新、删除和查询。DAO通常使用JDBC和SQL语句来实现数据操作。在MyBatis中,DAO可以使用MyBatis的SqlSession和SqlSessionFactory来管理数据库连接和事务,并且可以使用MyBatis的动态SQL功能执行高度灵活的查询。 Mapper是MyBatis中的另一种数据访问层实现方式,它基于XML或注解的方式来描述SQL语句和参数映射,提供了更灵活、更简洁的数据访问方式。Mapper使用XML或注解来描述SQL语句和参数映射,并将它们映射到Java方法上。在执行数据操作时,Mapper会将Java方法转换为对应的SQL语句,并使用SqlSession执行该SQL语句。相对于Dao,Mapper更加灵活,并且在编写SQL语句时提供了更多的可读性和可维护性。 在实际开发中,选择使用Dao还是Mapper取决于具体的需求和个人偏好。如果您更喜欢面向接口的编程模型,并且需要使用Java代码来定义和执行数据操作,那么Dao可能是一个更好的选择。如果您需要更灵活、更简洁的方式来描述SQL语句,并且不介意使用XML或注解来描述它们,那么Mapper可能更适合您的需求。 这里要注意区分
Dto
,Dto是数据传输对象,类似response返回类,用来封装返回对象格式
Bean的生命周期理解
Spring Cloud
概念理解
- Spring:是JavaEE的一个轻量级开发框架,主营IoC和AOP,集成JDBC、ORM、MVC等功能便于开发
- Spring Boot:是基于Spring,提供开箱即用的积木式组件,目的是提升开发效率
- Spring Cloud:分布式应用程序,为了让分布式应用程序编写更方便,更容易而提供的一组基础设施,它的核心是Spring框架,利用Spring Boot的自动配置,力图实现最简化的分布式应用程序开发
初始化创建一个spring cloud微服务项目
- 首先通过IDEA创建一个maven项目,比如命名为scd(spring cloud demo),然后一般会删除src目录,顶级目录不做编程。
文件-新建-项目-Maven-下一步
- 在刚刚创建的项目右键新建一个模块,都选择maven项目,然后选择父项scd,创建。 IDEA会自动在顶级scd的pom文件中生成模块,并标记packaging为pom类型。这里我们先创建三个模块,分别是parent、config、common。
<packaging>pom</packaging>的理解
- 记得设置文件编码全都改成UTF-8
在
setting - File Encodings
中设置,全局的、项目的、属性文件的都改成UTF-8.
#QR{padding-top:20px;} #QR a{border:0} #QR img{width:180px;max-width:100%;display:inline-block;margin:.8em 2em 0 2em} #rewardButton { border: 1px solid #ccc; /*width: 20%;*/ line-height: 53px; text-align: center; height: 70px; display: block; border-radius: 10px; -webkit-transition-duration: .4s; transition-duration: .4s; background-color: #f77b83; color: #f7f7f7; margin: 20px auto; padding: 8px 25px; } #rewardButton:hover { color: #eb5055; border-color: #f77b83; outline-style: none; background-color: floralwhite; }