学习目标:
掌握实战项目各种开发过程
学习内容:
某某项目的源码实操,记录常用的“姿势”,
例如:
- 架构设计
- 掌握 Java 基本语法
- 分析各种常用的类以及方法
- 掌握开发必备技巧
- 分析源码
学习时间:
`7.13
项目涉及的注解类
@EqualsAndHashCode(callSuper = true)
Lombok 提供的一个注解,用于自动生成 Java 类的 equals() 和 hashCode() 方法。它的作用是在生成的方法中,包含父类中的属性。
子类需要使用父类的 equals() 和 hashCode() 方法,那么就可以使用 @EqualsAndHashCode(callSuper = true) 注解。
这样,子类的 equals() 和 hashCode() 方法就会包含父类的属性,从而正确地比较两个对象是否相等。
import lombok.EqualsAndHashCode;
public class Person {
private String name;
private int age;
}
@EqualsAndHashCode(callSuper = true)
public class Student extends Person {
private String id;
}
@Accessors(chain = true):
Lombok的注解,为类字段生成流畅的setter方法,允许多个setter调用在单个语句中连接在一起。
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class Person {
private String firstName;
private String lastName;
private int age;
}
使用 @Accessors(chain = true)
我们可以链式调用 setter 方法,如下所示:
Person person = new Person()
.setFirstName("John")
.setLastName("Doe")
.setAge(30);
@ApiModelProperty(hidden = true)
@ApiModelProperty(hidden = true) 是 Swagger 注解中的一种,用于在文档中隐藏 API 的某些属性。
@ApiModelProperty(value = "Password.", example = "password123", required = true, hidden = true)
private String password;
@RestController
它用于指示一个类是一个 RESTful web 服务控制器。
当一个类被注解为 @RestController,它告诉 Spring 这个类将处理传入的 HTTP 请求并生成 HTTP 响应。响应通常是 JSON 或 XML 格式,尽管它可以是由 Spring 支持的任何格式。
@RestController 是两个其他注解的组合:@Controller 和 @ResponseBody。@Controller 用于指示一个类是一个控制器,而 @ResponseBody 用于指示方法的返回值应该被序列化并作为响应主体发送回去。
Dao层中常用注解(xxDao)
@Repository
@Repository 是 Spring 框架中的注解,用于标识一个类是数据访问层(DAO 层)的组件,可以让Spring自动扫描并创建它的实例
@Resource (@Resource 注解有两种用法,分别为名称匹配和类型匹配。)
@Resource是 Java 中的注解,用于在代码中引用外部资源或依赖。
需要注意的是,@Resource 注解不是 Spring 框架中的注解,它是 Java EE 标准中的注解。
在 Spring 中,通常使用 @Autowired 或 @Inject 注解进行依赖注入。这两个注解与 @Resource 注解的作用类似,但具有更强的类型匹配能力和更多的选项。
例如:在xxDao中利用@Resource引入UserMapper
@Resource
private UserMapper userMapper;
名称匹配(要配合配置文件):
@Resource(name = "myDataSource")
private DataSource dataSource;
指定了名称为 myDataSource 的资源,它将被注入到 dataSource 字段中。
在 Spring 配置文件中,可以通过 id 或者 name 属性为 Bean 指定一个名称
<bean id="userDaoImpl" class="com.example.dao.UserDaoImpl">
<!-- ... -->
</bean>
类型匹配:
@Resource
private UserService userService;
如果没有指定名称,它将尝试根据类型进行匹配。如果存在类型为 UserService 的 Bean,则它将被注入到 userService 字段中。
接口实现类中常用到的注解
@Service
@Service 注解通常用于标识一个类是服务层的组件,它与 @Component 注解类似,但更加具体化,可以让Spring自动扫描并创建它的实例,然后可以使用@Autowired注解将它注入到需要它的地方
@Slf4j
Lombok库提供的注解,用于自动生成日志记录器,自动为类生成一个名为log的静态日志记录器,可以直接调用它的方法进行日志记录,比如log.info(xxx)
log.info("开始执行方法");
@Autowired
Spring 框架中的注解,用于自动装配 Spring 容器中的 Bean。
Bean 是指通过 Spring 容器管理的对象实例。当一个 Bean 被创建时,它可以在 Spring 容器中注册和存储。
另一个 Bean 中需要使用该 Bean 时,可以通过 @Autowired 注解将它注入到需要它的地方。
比如:
1.在Controller中注入需要用到的接口
2.在xxImpl(接口实现类)中注入需要使用的DAO 对象(DAO 层是指数据访问对象层Data Access Object)
Mapper接口常用注解
@Select
是 MyBatis 框架中的注解,用于声明一个查询语句。
@Select("select * from user where third_account_id = #{account_id} limit 1")
UserDO getByThirdAccountId(@Param("account_id") String accountId);
定义了一个查询语句,它从 user 表中查询 third_account_id 字段等于 account_id 参数的记录,并返回查询结果中的第一条记录。#{account_id} 是 MyBatis 中的占位符,表示一个参数。
方法的参数 accountId 使用了 @Param 注解,指定了参数名称为 account_id。这个注解告诉 MyBatis 在生成 SQL 语句时使用这个名称作为参数名,从而与 SQL 语句中的占位符 #{account_id} 对应。
项目涉及的对象
HttpServletRequest request
HttpServletRequest 是客户端发送到服务器的HTTP请求,
里边包含了客户端请求的所有信息,如**请求的URL、请求方法、请求头、请求参数**等,一般用于读取客户端发送的数据,并根据请求内容生成响应
举例子:如果我们要验证用户的账号和密码,那么客户端发送到服务器的HTTP请求中一定要携带这两个参数,然后在服务器中,利用这个类的方法来获取客户端发送过来的参数,解析账号和密码,最后进行业务操作即可
以下这个接口内部的部分方法:
String getAuthType():获取请求的认证类型。
Cookie[] getCookies():获取请求中的所有 Cookie。
long getDateHeader(String name):获取请求头中指定名称的日期值。
String getHeader(String name):获取请求头中指定名称的值。
Enumeration<String> getHeaders(String name):获取请求头中指定名称的所有值。
Enumeration<String> getHeaderNames():获取请求头中所有名称的枚举。
int getIntHeader(String name):获取请求头中指定名称的整数值。
HttpServletMapping getHttpServletMapping():获取请求对应的 Servlet 映射信息。
String getMethod():获取请求的 HTTP 方法。
String getPathInfo():获取请求的路径信息。
HttpServletResponse
表示服务器返回客户端的HTTP响应,包含了服务器响应的所有信息,如响应状态码、响应头、响应正文等。常被开发人员用来设置响应状态码、响应头、响应正文等。
public void setHttpServletResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应状态码为 200
response.setStatus(HttpServletResponse.OK);
// 设置响应头的 Content-Type 为 text/html
response.setContentType("text/html");
// 添加一个响应头
response.addHeader("X-Header", "HeaderValue");
// 获取写入响应正文的 PrintWriter 对象
PrintWriter out = response.getWriter();
// 写入响应正文
out.println("xxx");
// 关闭 PrintWriter 对象
out.close();
}
Dao层的对象
举例子:
public UserDO getUserByUserName(String userName) {
LambdaQueryWrapper<UserDO> queryUser = Wrappers.lambdaQuery();
queryUser.eq(UserDO::getUserName, userName)
.eq(UserDO::getDeleted, YesOrNoEnum.NO.getCode());
//UserDO::getUserName 是 Java 8 中的方法引用语法,用于引用 UserDO 类中的 getUserName() 方法。
return userMapper.selectOne(queryUser);
}
用到了MyBatis-Plus 实现的查询用户信息的方法,MyBatis-Plus 是 MyBatis 的增强工具库,提供了更加便捷的数据访问操作和查询构建方式。
- 使用 Wrappers.lambdaQuery() 方法创建一个 LambdaQueryWrapper 对象
- eq() 方法用于设置等于条件
- userMapper.selectOne(queryUser) 方法查询符合条件的一条记录
项目涉及的技巧
写接口
接口定义了一组方法签名,但是不用提供方法的具体实现
一般搭配着接口实现类(xxxImpl),在接口实现类中需要使用@Override(在 Java 中,当一个类继承自另一个类或实现了一个接口时,它可以重写父类或接口的方法,以实现自己的行为。)
接口实现类的技巧
接口实现类通常要去调用Dao层,所以通常要 用@Autowired引入xxDao,然后再在xxDao里 用@Resource引入xxMapper(一般就是用来写sql语句的接口)
Mapper接口的方法
BaseMapper(一般我们自己写的mapper接口都要继承这个接口)
BaseMapper 是 MyBatis-Plus 框架中的一个接口,提供了一系列基本的数据访问操作,包括插入、删除、更新和查询等。
我们自己的Mapper接口一般要继承BaseMapper接口例如:
public interface UserMapper extends BaseMapper<UserDO>
然后BaseMapper接口内部如下:
public interface BaseMapper<T> extends Mapper<T>
原始的Mapper接口本身是为空的:
package com.baomidou.mybatisplus.core.mapper;
public interface Mapper<T> {
}
以下是 BaseMapper 中常用的方法:
insert(T entity):插入一条记录;
deleteById(Serializable id):根据 ID 删除一条记录;
deleteByMap(Map<String, Object> columnMap):根据指定的列名和值删除记录;
delete(Wrapper<T> wrapper):根据条件删除记录;
deleteBatchIds(Collection<? extends Serializable> idList):根据 ID 批量删除记录;
updateById(T entity):根据 ID 更新一条记录;
update(T entity, Wrapper<T> wrapper):根据条件更新记录;
selectById(Serializable id):根据 ID 查询一条记录;
selectBatchIds(Collection<? extends Serializable> idList):根据 ID 批量查询记录;
selectByMap(Map<String, Object> columnMap):根据指定的列名和值查询记录;
selectOne(Wrapper<T> wrapper):根据条件查询一条记录;
selectCount(Wrapper<T> wrapper):根据条件查询记录数;
selectList(Wrapper<T> wrapper):根据条件查询多条记录;
selectMaps(Wrapper<T> wrapper):根据条件查询多条记录,并将结果集封装成 Map;
selectObjs(Wrapper<T> wrapper):根据条件查询多条记录,并将结果集封装成 Object 列表;
selectPage(Page<T> page, Wrapper<T> wrapper):根据条件分页查询记录;
selectMapsPage(Page<T> page, Wrapper<T> wrapper):根据条件分页查询记录,并将结果集封装成 Map。
注意:
Wrapper 是 MyBatis-Plus 框架中提供的一个查询条件构造器,用于构建复杂的查询条件。使用 Wrapper 可以在不编写 SQL 语句的情况下构建查询条件,提高查询的可读性和可维护性。
除了 Wrapper,MyBatis-Plus 还提供了其他的查询条件构造器,例如 QueryWrapper、LambdaQueryWrapper 等,它们都可以用于构建查询条件。这些查询条件构造器具有不同的优点和用途,可以根据实际情况选择合适的方式来构建查询条件。
条件构造器
QueryWrapper
QueryWrapper:基于实体对象的属性进行查询,支持链式调用和条件组合。
QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", "admin")
.ne("deleted", YesOrNoEnum.YES.getCode())
.likeRight("email", "example.com")
.orderByAsc("user_id")
.last("limit 10");
List<UserDO> userList = userMapper.selectList(queryWrapper);
//构建了一个查询条件,包括用户名等于 admin、删除标记不等于 YES、邮箱以 example.com 结尾、按照 user_id 升序排序、限制查询结果为前 10 条记录等多个条件。使用 selectList() 方法执行查询,并返回符合条件的用户列表。
LambdaQueryWrapper
基于 Lambda 表达式进行查询,支持类型安全的属性引用和条件组合。
LambdaQueryWrapper<UserDO> lambdaQueryWrapper = Wrappers.lambdaQuery();
//这里需要传入要检测的对象UserDo,用来和数据库的数据进行对比
lambdaQueryWrapper.eq(UserDO::getUserName, "admin")
.ne(UserDO::getDeleted, YesOrNoEnum.YES.getCode())
.likeRight(UserDO::getEmail, "example.com")
.orderByAsc(UserDO::getUserId)
.last("limit 10");
List<UserDO> userList = userMapper.selectList(lambdaQueryWrapper);
//UserDO::getUserName 是 Java 8 中的方法引用语法,用于引用 UserDO 类中的 getUserName() 方法。
//将() -> userDO.getUserName()替换为UserDO::getUserName
在xxxImpl层(接口实现类)捕获异常的技巧
我们通常在进行Dao层操作后会得到一个数据对象,然后进行这个对象的判断,进行if语句后,可以调用一个通用的异常类的方法,比如
if (user == null) {
throw ExceptionUtil.of(StatusEnum.USER_NOT_EXISTS, "userName=" + username);
}
这个异常工具类内部如下:
public class ExceptionUtil {
public static ForumException of(StatusEnum status, Object... args) {
return new ForumException(status, args);
}
}
这个异常工具类会再次返回一个基础异常类
基础异常类如下:
public class ForumException extends RuntimeException
//继承自 RuntimeException,表示这是一个运行时异常。
与普通异常不同,运行时异常(例如空指针异常、数组越界异常等。)在代码中可以不被显式地捕获和处理。
可以使代码更加简洁,因为不需要对这些异常进行显式的捕获和处理。
这样,经过三层封装,就实现了从xxxImpl实现类->封装的异常工具类->自定义的异常类
项目必备方法
String.format()
String.format() 是 Java 中一个常用的字符串格式化方法,用于将指定字符串中的占位符替换为指定的值,生成新的字符串
String.format(String format, Object... args)
%s:表示字符串类型;
%d:表示整数类型;
%f:表示浮点数类型;
%c:表示字符类型;
%b:表示布尔类型;
%n:表示换行符。
String name = "Alice";
int age = 20;
String message = String.format("My name is %s and my age is %d", name, age);
System.out.println(message);
Objects.equals()
是一个条件表达式,用于比较两个值是否相等。
Objects.equals(encPwd(plainPwd), encPwd)
encPwd(plainPwd) 表示将 plainPwd 明文密码加密后得到的密文密码,encPwd 表示从数据库中查询出来的已加密的密码。
这个表达式的含义是,将用户输入的明文密码加密后得到的密文密码(即 encPwd(plainPwd)),与从数据库中查询出来的已加密的密码(即 encPwd)进行比较,如果相等则返回 true,否则返回 false。
substring()
substring() 方法接收两个参数:第一个参数是一个整数 beginIndex,表示要截取的子串的起始位置(包含);第二个参数是一个整数 endIndex,表示要截取的子串的结束位置(不包含)。如果省略第二个参数,则默认截取到字符串的末尾。
String str = "Hello, world!";
String subStr1 = str.substring(0, 5); // "Hello"
String subStr2 = str.substring(7, 12); // "world"
String subStr3 = str.substring(7); // "world!"
为什么要实现Serializable 接口
- Serializable 接口是 Java 中的一个标记接口,它没有任何方法或属性,只是用于标记一个类的实例可以被序列化和反序列化。
- 当一个类实现了 Serializable 接口后,这个类的实例就可以被序列化为字节流,从而方便地在网络中传输、存储到文件中或者在不同应用程序之间共享(存储在持久化存储中(如数据库、磁盘文件等)
为什么要重写父类的hashcode和equal
每个对象都有一个默认的 hashCode() 和 equals() 方法。hashCode() 方法根据对象的内部状态计算出一个 hash 值,用于在哈希表等数据结构中快速查找对象;equals() 方法用于比较两个对象是否相等。
默认的 hashCode() 和 equals() 方法是根据对象的内存地址来计算的,而不是根据对象的属性值。因此,如果我们需要根据对象的属性值来比较两个对象是否相等,就需要重写 hashCode() 和 equals() 方法。
重写 hashCode() 和 equals() 方法是为了比较对象是否相等,以及为了让对象能够被正确地存储在哈希表等数据结构中。在实现时需要遵循规范和注意一致性问题。
return a==b
经常用来判断某个条件是否等于某个条件。
如果相等,则返回 true,表示匹配成功;否则返回 false,表示匹配失败。