day66(SpringBoot2:YAML配置,使用Druid数据库连接池,编写持久层(数据访问层)代码,关于业务逻辑层(service层)
day66(SpringBoot2:YAML配置,使用Druid数据库连接池,编写持久层(数据访问层)代码,关于业务逻辑层(service层)
1.YAML配置
-
YAML配置就是把原有的
.properties
配置的扩展改为yml
-
AML配置原本并不是Spring系列框架内置的配置语法,如果在项目中需要使用这种语法进行配置,解析这类文件需要添加相关依赖,在Spring Boot中默认已添加此依赖
-
在YAML配置中,原本在
.properties
的配置表现为使用多个小数点分隔的配置将改为换行并使用2个空格缩进的语法,换行前的部分使用冒号表示结束,最后的属性名与值之间使用冒号和1个空格进行分隔,如果有多条属性在.properties
文件中属性名有重复的前缀,在yml
中不必也不能重复写 -
例如:
-
原本在
.properties
中配置为:spring.datasource.username=root spring.datasource.password=123456
-
则在
yml
文件中配置为:spring: datasource: username: root password: 123456
-
-
提示:在IntelliJ IDEA中编写
yml
时,当需要缩进2个空格时,仍可以使用键盘上的TAB键进行缩进,IntelliJ IDEA会自动将其转换为2个空格。 -
无论是
.properties
还是yml
,只是配置文件的扩展名和文件内部的配置语法有区别,对于Spring Boot最终的执行其实没有任何表现上的不同
2.使用Druid数据库连接池
-
Druid数据库连接是阿里巴巴团队研发的,在Spring Boot项目中,如果需要显式的指定使用此连接池,首先,需要在项目中添加依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency>
-
当添加了此依赖,在项目中需要应用时,需要在配置文件中指定
spring.datasource.type
属性,取值为以上依赖项的jar包中的DruidDataSource
类型的全限定名。 -
例如: 在
yml
中配置为:# Spring系列框架的配置 spring: # 连接数据库的相关配置 datasource: # 使用的数据库连接池类型 type: com.alibaba.druid.pool.DruidDataSource
3.编写持久层(数据访问层)代码
-
数据持久化:
- 在开发领域中,讨论数据时,通常指定是正在执行或处理的数据,这些数据都是在内存中的,而内存(RAM)的特征包含”一旦断电,数据将全部丢失“,为了让数据永久保存下来,通常会将数据存储到能够永久存储数据的介质中,通常是计算机的硬盘,硬盘上的数据都是以文件的形式存在的,所以,当需要永久保存数据时,可以将数据存储到文本文件中,或存储到XML文件中,或存储到数据库中,这些保存的做法就是数据持久化,而文本文件、XML文件都不利于实现增删改查中的所有数据访问操作,而数据库是实现增删改查这4种操作都比较便利的,所以,一般在讨论数据持久化时,默认指的都是使用数据库存储数据。
-
在项目中,会将代码(各类、接口)划分一些层次,各层用于解决不同的问题,其中,持久层就是用于解决数据持久化问题的,甚至,简单来说,持久层对应的就是数据库编程的相关文件或代码。
-
目前,使用Mybatis技术实现持久层编程,需要:
- 编写一次性的基础配置
- 使用
@MapperScan
指定接口所在的Base Package - 指定配置SQL语句的XML文件的位置
- 使用
- 编写每个数据访问功能的代码
- 在接口中添加必须的抽象方法
- 可能需要创建相关的POJO类
- 在XML文件中配置抽象方法映射的SQL语句
- 在接口中添加必须的抽象方法
- 编写一次性的基础配置
-
关于一次性的配置,
@MapperScan
注解需要添加在配置类上,有2种做法:-
直接将此注解添加在启动类上,因为启动类本身也是配置类
-
自行创建配置类,在此配置类上添加
@MapperScan
-
如果采用以上的第2种做法,则应该在
src\main\java
的根包下,创建config.MybatisConfig
类,并在此类使用@MapperScan
注解package cn.tedu.boot.demo.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("cn.tedu.boot.demo.mapper") public class MybatisConfig { }
-
-
-
另外,关于指定配置SQL语句的XML文件的位置,需要在
application.yml
(或application.properties
)中配置mybatis.mapper-locations
属性,例如:# Mybatis相关配置 mybatis: # 用于配置SQL语句的XML文件的位置 mapper-locations: classpath:mapper/*.xml
-
基于以上的配置值,还应该在
src/main/resources
下自行创建名为mapper
的文件夹。至此,关于使用Mybatis实现数据库编程的一次性配置结束!
接下来,可以使用任何你已知的Mybatis使用方式实现所需的数据访问。
目前,设定目标为:最终实现”添加管理员账号“的功能。则在数据访问层需要做到:
- 插入管理员数据
- 创建
cn.tedu.boot.demo.entity.Admin
类 - 在
cn.tedu.boot.demo.mapper
包(不存在,则创建)下创建AdminMapper
接口,并在接口中声明int insert(Admin admin);
方法 - 在
src/main/resources/mapper
文件夹下通过粘贴得到AdminMapper.xml
文件,在此文件中配置与以上抽象方法映射的SQL语句 - 编写完成后,应该及时测试,测试时,推荐在
src/test/java
的根包下创建mapper.AdminMapperTests
测试类,并在此类中编写测试方法
- 创建
- 根据用户名查询管理员数据
- 后续,在每次插入数据之前,会调用此功能进行查询,以此保证”重复的用户名不会被添加到数据库中“
- 即便在数据表中用户名已经添加了
unique
,但是,不应该让程序执行到此处
- 即便在数据表中用户名已经添加了
- 在
AdminMapper
接口中添加Admin getByUsername(String username);
方法 - 在
AdminMapper.xml
文件中添加与以上抽象方法映射的SQL语句 - 编写完成后,应该及时测试
- 后续,在每次插入数据之前,会调用此功能进行查询,以此保证”重复的用户名不会被添加到数据库中“
- 其它问题暂不考虑,例如在
ams_admin
中,其实phone
和email
也是设置了unique
的,如果完整的实现,则还需要添加根据phone
查询管理员的功能,和根据email
查询管理员的功能,在不实现这2个功能的情况下,后续进行测试和使用时,应该不使用重复的phone
和email
值来测试或执行
- 插入管理员数据
4.关于业务逻辑层(service层)
-
业务逻辑层是被Controller直接调用的层(Controller不允许直接调用持久层),通常,在业务逻辑层中编写的代码是为了保证数据的完整性和安全性,使得数据是随着我们设定的规则而产生或发生变化。
-
通常,在业务逻辑层的代码会由接口和实现类组件,其中,接口被视为是必须的
- 推荐使用基于接口的编程方式
- 部分框架在处理某些功能时,会使用基于接口的代理模式,例如Spring JDBC框架在处理事务时
-
在接口中,声明抽象方法时,仅以操作成功为前提来设计返回值类型(不考虑失败),如果业务在执行过程可能出现某些失败(不符合所设定的规则),可以通过抛出异常来表示!
-
关于抛出的异常,通常是自定义的异常,并且,自定义异常通常是
RuntimeException
的子类,主要原因:
- 不必显式的抛出或捕获,因为业务逻辑层的异常永远是抛出的,而控制器层会调用业务逻辑层,在控制器层的Controller中其实也是永远抛出异常的,这些异常会通过Spring MVC统一处理异常的机制进行处理,关于异常的整个过程都是固定流程,所以,没有必要显式抛出或捕获
- 部分框架在处理某些事情时,默认只对
RuntimeException
的子孙类进行识别并处理,例如Spring JDBC框架在处理事务时
- 所以,在实际编写业务逻辑层之前,应该先规划异常,例如先创建
ServiceException
类:
package cn.tedu.boot.demo.ex;
public class ServiceException extends RuntimeException {
}
- 接下来,再创建具体的对应某种“失败”的异常,例如,在添加管理员时,可能因为“用户名已经存在”而失败,则创建对应的
UsernameDuplicateException
异常:
package cn.tedu.boot.demo.ex;
public class UsernameDuplicateException extends ServiceException {
}
- 关于抽象方法的参数,应该设计为客户端提交的数据类型或对应的封装类型,不可以是数据表对应的实体类型!如果使用封装的类型,这种类型在类名上应该添加某种后缀,例如
DTO
或其它后缀,例如:
package cn.tedu.boot.demo.pojo.dto;
public class AdminAddNewDTO implements Serializable {
private String username;
private String password;
private String nickname;
private String avatar;
private String phone;
private String email;
private String description;
// Setters & Getters
// hashCode(), equals()
// toString()
}
然后,在cn.tedu.boot.demo.service
包下声明接口及抽象方法:
package cn.tedu.boot.demo.service;
public interface IAdminService {
void addNew(AdminAddNewDTO adminAddNewDTO);
}
并在以上service
包下创建impl
子包,再创建AdminServiceImpl
类:
package cn.tedu.boot.demo.service.impl;
@Service // @Component, @Controller, @Repository
public class AdminServiceImpl implements IAdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
// 通过参数获取用户名
// 调用adminMapper的Admin getByUsername(String username)方法执行查询
// 判断查询结果是否不为null
// -- 是:表示用户名已经被占用,则抛出UsernameDuplicateException
// 通过参数获取原密码
// 通过加密方式,得到加密后的密码encodedPassword
// 暂时不加密,写为String encodedPassword = adminAddNewDTO.getPassword();
// 创建当前时间对象now > LocalDateTime.now()
// 创建Admin对象
// 补全Admin对象的属性值:通过参数获取username,nickname……
// 补全Admin对象的属性值:password > encodedPassword
// 补全Admin对象的属性值:isEnable > 1
// 补全Admin对象的属性值:lastLoginIp > null
// 补全Admin对象的属性值:loginCount > 0
// 补全Admin对象的属性值:gmtLastLogin > null
// 补全Admin对象的属性值:gmtCreate > now
// 补全Admin对象的属性值:gmtModified > now
// 调用adminMapper的insert(Admin admin)方法插入管理员数据,获取返回值
// 判断以上返回的结果是否不为1,抛出InsertException异常
}
}
以上业务代码的实现为:
package cn.tedu.boot.demo.service.impl;
import cn.tedu.boot.demo.entity.Admin;
import cn.tedu.boot.demo.ex.InsertException;
import cn.tedu.boot.demo.ex.UsernameDuplicateException;
import cn.tedu.boot.demo.mapper.AdminMapper;
import cn.tedu.boot.demo.pojo.dto.AdminAddNewDTO;
import cn.tedu.boot.demo.service.IAdminService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class AdminServiceImpl implements IAdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
// 通过参数获取用户名
String username = adminAddNewDTO.getUsername();
// 调用adminMapper的Admin getByUsername(String username)方法执行查询
Admin queryResult = adminMapper.getByUsername(username);
// 判断查询结果是否不为null
if (queryResult != null) {
// 是:表示用户名已经被占用,则抛出UsernameDuplicateException
throw new UsernameDuplicateException();
}
// 通过参数获取原密码
String password = adminAddNewDTO.getPassword();
// 通过加密方式,得到加密后的密码encodedPassword
String encodedPassword = password;
// 创建当前时间对象now > LocalDateTime.now()
LocalDateTime now = LocalDateTime.now();
// 创建Admin对象
Admin admin = new Admin();
// 补全Admin对象的属性值:通过参数获取username,nickname……
admin.setUsername(username);
admin.setNickname(adminAddNewDTO.getNickname());
admin.setAvatar(adminAddNewDTO.getAvatar());
admin.setPhone(adminAddNewDTO.getPhone());
admin.setEmail(adminAddNewDTO.getEmail());
admin.setDescription(adminAddNewDTO.getDescription());
// 以上这些从一个对象中把属性赋到另一个对象中,还可以使用:
// BeanUtils.copyProperties(adminAddNewDTO, admin);
// 补全Admin对象的属性值:password > encodedPassword
admin.setPassword(encodedPassword);
// 补全Admin对象的属性值:isEnable > 1
admin.setIsEnable(1);
// 补全Admin对象的属性值:lastLoginIp > null
// 补全Admin对象的属性值:loginCount > 0
admin.setLoginCount(0);
// 补全Admin对象的属性值:gmtLastLogin > null
// 补全Admin对象的属性值:gmtCreate > now
admin.setGmtCreate(now);
// 补全Admin对象的属性值:gmtModified > now
admin.setGmtModified(now);
// 调用adminMapper的insert(Admin admin)方法插入管理员数据,获取返回值
int rows = adminMapper.insert(admin);
// 判断以上返回的结果是否不为1,抛出InsertException异常
if (rows != 1) {
throw new InsertException();
}
}
}
以上代码未实现对密码的加密处理!关于密码加密,相关的代码应该定义在别的某个类中,不应该直接将加密过程编写在以上代码中,因为加密的代码需要在多处应用(添加用户、用户登录、修改密码等),并且,从分工的角度上来看,也不应该是业务逻辑层的任务!所以,在cn.tedu.boot.demo.util
(包不存在,则创建)下创建PasswordEncoder
类,用于处理密码加密:
package cn.tedu.boot.demo.util;
@Component
public class PasswordEncoder {
public String encode(String rawPassword) {
return "aaa" + rawPassword + "aaa";
}
}
完成后,需要在AdminServiceImpl
中自动装配以上PasswordEncoder
,并在需要加密时调用
PasswordEncoder对象的encode()方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具