数据表创建、Mybatis、三层架构
前置
经过后端项目初始化后,有以下两个步骤:
- 我们需要对数据表进行设计
- 并且以三层架构的形式整理后端目录
- 生成数据表对应的实体对象、mapper和service
- 实现一个简单的注册逻辑,包括账号密码的校验、密码加密和写表
数据表设计
方法
- IEAD中数据库创建
- sql语句创建
知识点
- 主键用primary key表示
- 数据类型一般有int、tinyint、bigint、datatime、varchar
- 其中tinyint为1字节、int为4字节、bigint为8字节
- varchar以实际字符大小来存储,相比text节省空间
- not null 表示非空、default设置缺省值、comment设置字段含义
三层架构
一次后端的请求可以分成三个部分:提取数据、数据处理和响应请求。后端三层架构将其解耦成三层,分别是Dao层(Mapper层)、service层和controller层,方便代码的解耦、复用和维护。
知识点
- 对应的注解比较多:@RestController、@RequestMapping、@Autowired、@Resource等
实现基本数据库操作
步骤
实现数据库操作,分成几个步骤:
- 生成实体对象,即一个表对应一个实体类,表的字段对应类成员
- 生成mapper层接口和xml,对应数据库的访问接口,xml中写具体的sql
- 生成service层接口和实现,对应增上改查和数据处理
方法:Mybatis
-
在IDEA中连接数据库,指定一个表可以用MybatisX-Generator,自动生成对应的代码框架,然后将generator中的文件到包中,如图:
-
对Mybatis生成的代码进行测试,在UserService接口用alt+enter自动创建测试:
javapackage com.lzj.usercenter.service; import java.util.Date; import com.lzj.usercenter.domain.User; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * 用户服务测试 * * @author:lzj */ import javax.annotation.Resource; @SpringBootTest class UserServiceTest { @Resource private UserService userService; @Test void testAddUser(){ User user = new User(); user.setUsername("lzj"); user.setUserAccount("lzj"); user.setAvatarUrl("https://portrait.gitee.com/uploads/avatars/user/3031/9094632_DXdaxia_1620607641.png!avatar30"); user.setGender(0); user.setUserPassword(""); user.setPhone("111111"); user.setEmail("222222@qq.com"); user.setUserStatus(0); user.setCreateTime(new Date()); user.setUpdateTime(new Date()); user.setIsDelete(0); user.setUserRole(0); user.setPlanetCode(""); boolean result = userService.save(user); System.out.println(user.getId()); Assertions.assertEquals(true, result); } }
遇到的坑
-
驼峰命名自动转换问题
javaorg.springframework.jdbc.BadSqlGrammarException: ### Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'user_account' in 'field list' ### The error may exist in com/lzj/usercenter/mapper/UserMapper.java (best guess) ### The error may involve com.lzj.usercenter.mapper.UserMapper.insert-Inline ### The error occurred while setting parameters ### SQL: INSERT INTO user ( username, user_account, avatar_url, gender, user_password, phone, email, user_status, create_time, update_time, is_delete, user_role, planet_code ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'user_account' in 'field list' ; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'user_account' in 'field list'
原因:mybatis-plus会自动将驼峰字段转换成下划线,如果字段是驼峰命名,实体类也是驼峰,则需要关闭自动转换,在application.yml加入配置:
ymlmybatis-plus: configuration: map-underscore-to-camel-case: false
-
yml配置问题
javajava.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:98) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:313) at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743) at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:742) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:362) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:283) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:282) at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
原因:这个报错表示依赖注入有问题,产生的原因有好几种,最难找到的就是yml中缩进问题(发现1.中的缩进少了一格),需要检查刚刚修改的yml配置的缩进有没有问题。
实现简单的后端注册校验接口
注册校验在前后端都需要做,后端根据规则去校验,并将密码加密存入数据库。
- 在UserService接口加入一个账户校验接口
java
public interface UserService extends IService<User> {
/**
*
* @param userAccount 用户账号
* @param userPassword 用户密码
* @param checkPassword 校验码
* @return 新用户id
*/
long userRegister(String userAccount, String userPassword, String checkPassword);
}
- 在UserServiceImpl实现该方法
java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{
private final String SALT = "lzj";
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
//校验
if(StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)){
return -1;
}
if(userAccount.length() < 4){
return -1;
}
if(userPassword.length() < 8 || checkPassword.length() < 8){
return -1;
}
//账号不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
long count = this.count(queryWrapper);
if(count > 0 ){
return -1;
}
//账户不能包含特殊字符
String validRule = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%...... &*()------+|{}【】';:""'。,、?]";
Matcher matcher = Pattern.compile(validRule).matcher(userAccount);
if(matcher.find()){
return -1;
}
//密码需要与校验密码相同
if(!userPassword.equals(checkPassword)){
return -1;
}
// 2.对密码进行加密(密码千万不要直接以明文存储到数据库中)
String verifyPassword = DigestUtils.md5DigestAsHex((SALT +userPassword).getBytes(StandardCharsets.UTF_8));
//保存
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(verifyPassword);
boolean saveResult = this.save(user);
if(!saveResult){
return -1;
}
return user.getId();
}
}
- 生成该方法的测试样例
java
@Test
void userRegister() {
String userAccount = "lzj33";
String userPassword = "123456789";
String checkPassword = "12345";
long result = userService.userRegister(userAccount, userPassword, checkPassword);
Assertions.assertEquals(-1,result);
userAccount = "lzj33";
userPassword = "123456789";
checkPassword = "123456789";
result = userService.userRegister(userAccount, userPassword, checkPassword);
System.out.println(result);
User user = userService.getById(result);
System.out.println(user);
}
Tips
- 加Apache Common Lang的库,它实现的StringUtils.isAnyBlank可以放入多个参数,方便同时对多个字符串校验
- 加密可以用md5,加SALT更随机
- 特殊字符用正则表达式过滤