【苍穹外卖】总结
1 pom 依赖
1.1 MyBatis Spring
用于简化 MyBatis 与 Spring Boot 的集成,提供了对 MyBatis 框架的自动配置支持,简化了数据访问层的开发
1.2 Lombok
Lombok 是一个 Java 库,能够通过注解自动生成常见的代码(如 getter
、setter
、toString
等),减少了代码冗余,提升了开发效率
1. @Data
- 作用: 这是一个综合性注解,包含了
@Getter
、@Setter
、@ToString
、@EqualsAndHashCode
和@RequiredArgsConstructor
,也就是说,@Data
会为所有字段自动生成getter
和setter
,同时生成toString()
、equals()
、hashCode()
方法,并且为final
字段生成构造函数。
2. @Builder
- 作用: 生成建造者模式(Builder Pattern)代码,可以通过流式调用来构建对象。它允许更灵活、清晰地创建对象,尤其适合包含多个属性的复杂对象创建。
3. @NoArgsConstructor
- 作用: 生成无参数的构造函数。适用于需要创建空对象的情况,比如从数据库反序列化时,或者某些框架(如 JPA)需要一个无参构造函数。
4. @AllArgsConstructor
- 作用: 生成包含所有字段的全参数构造函数。适用于在创建对象时直接通过构造函数初始化所有字段。
1.3 FastJSON
FastJSON 是阿里巴巴开源的一个高性能 JSON 序列化/反序列化库,主要用于将 Java 对象转换为 JSON 字符串或将 JSON 字符串转换为 Java 对象
1.4 Commons Lang
提供了一些常用的 Java 工具类,扩展了 Java 核心库中的 java.lang
包,提供了字符串操作、数字处理等实用功能
1.5 Druid
Druid 是阿里巴巴开源的高效数据库连接池,集成了数据库监控和性能优化功能。druid-spring-boot-starter
提供了与 Spring Boot 的自动集成
1.6 PageHelper
PageHelper 是一个 MyBatis 分页插件,简化了分页操作,支持多种数据库并能自动分页
1.7 Knife4j
Knife4j 是基于 Swagger 的增强 UI,提供了更丰富的文档功能,便于 API 文档的生成与查看
1.8 AspectJ
AspectJ 是一个面向切面编程(AOP)框架,aspectjrt
是其运行时库,用于在运行时处理切面相关的逻辑
AspectJjweaver
是用于支持 AspectJ 切面的织入器(weaver),允许在编译期、类加载期或运行期插入切面代码
1.9 JSON Web Token (JJWT)
JJWT 是用于生成和验证 JSON Web Tokens (JWT) 的库,主要用于处理身份验证和安全
1.10 Aliyun OSS
阿里云对象存储(OSS)SDK,用于与阿里云 OSS 进行集成,实现文件的上传、下载和管理
1.11 JAXB API
JAXB(Java Architecture for XML Binding)是用于将 Java 对象转换为 XML 以及将 XML 转换为 Java 对象的 API
2 项目结构
common:相当于 util,放置自定义的方法
pojo:Plain Old Java Object,放置对象
server:服务端代码
2.1 common
2.1.1 constant
常量类
如
public class JwtClaimsConstant {
public static final String EMP_ID = "empId";
public static final String USER_ID = "userId";
public static final String PHONE = "phone";
public static final String USERNAME = "username";
public static final String NAME = "name";
}
2.1.2 context
上下文
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
管理当前线程中的上下文信息,特别是与线程相关的 Long
类型的 id
。通过使用 ThreadLocal
,它允许每个线程独立存储和访问自己的 id
,从而确保在多线程环境中不会出现线程间数据冲突的问题
这样可以 在任何时候获取用户 ID
2.1.3 exception
异常类
/**
* 登录失败
*/
public class LoginFailedException extends BaseException {
public LoginFailedException(String msg) {
super(msg);
}
}
2.1.4 json
将 Java 对象与 JSON 之间进行序列化和反序列化
通过这个类,可以将 Java
对象转换为 JSON
,或者将 JSON
转换为 Java
对象
/**
* 对象映射器:基于 jackson 将 Java 对象转为 json,或者将 json 转为 Java 对象
* 将 JSON 解析为 Java 对象的过程称为 [从 JSON 反序列化 Java 对象]
* 从 Java 对象生成 JSON 的过程称为 [序列化 Java 对象到 JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
2.1.5 properties
属性类
@Component
@ConfigurationProperties(prefix = "sky.jwt") // 配置属性类
@Data
public class JwtProperties {
/**
* 管理端员工生成jwt令牌相关配置
*/
private String adminSecretKey;
private long adminTtl;
private String adminTokenName;
/**
* 用户端微信用户生成jwt令牌相关配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
}
这里有个 配置属性类 的注解 @ConfigurationProperties(prefix = "sky.jwt"),将配置文件 application.yml 中的配置属性 映射 到 Java 类,前缀 prefix 指定了配置属性:
# application.yml
sky:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
admin-ttl: 72000000000
# 设置前端传递过来的令牌名称
admin-token-name: token
# 设置jwt签名加密时使用的秘钥
user-secret-key: userKey
# 设置jwt过期时间
user-ttl: 72000000000
# 设置前端传递过来的令牌名称 与 前端 一致,不可更改
user-token-name: authentication
2.1.6 result
结果类
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合
}
/**
* 后端统一返回结果
*
* @param <T>
*/
@Data
public class Result<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
2.1.7 utils
实用类,包括AliOss对象文件上传,jwt令牌
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
/**
* 文件上传
*
* @param bytes
* @param objectName
* @return
*/
public String upload(byte[] bytes, String objectName) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder
.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(objectName);
log.info("文件上传到:{}", stringBuilder.toString());
return stringBuilder.toString();
}
}
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
2.2 pojo
分为
entity(实体对象)
vo(View Object,视图对象)
dto(Data Transfer Object,数据传输对象)
2.2.1 entity
- 以 Entity 结尾
- 数据对象名 与 数据库表名 一致
- 字段 与 数据库字段 一致
2.2.2 vo
用于 展示层,作用是把 某个指定页面(或组件)的所有数据封装
- 不可继承自 Entity
- vo 可以继承、组合其他 DTO、VO、BO 等对象
- vo 只能用于返回前端
2.2.3 dto
用于从数据库中检索数据
2.3 server
除了 controller、service、mapper 三个层,还有其他一些类
2.3.1 config
配置类
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("开始创建redis模板类");
// RedisTemplate redisTemplate = new RedisTemplate();
// redisTemplate.setConnectionFactory(redisConnectionFactory);
//
redisTemplate.setKeySerializer(new StringRedisSerializer());
//
// return redisTemplate;
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置Key的序列化器为String
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置Value的序列化器为String
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// // 设置Hash Key的序列化器为String
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//
// // 设置Hash Value的序列化器为String
// redisTemplate.setHashValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
redis 数据库
web 层配置
/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**") // 添加拦截路径
.excludePathPatterns("/admin/employee/login"); // 排除拦截路径(登录不需要拦截)
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
/**
* 通过 knife4j 生成接口文档
*
* @return
*/
@Bean
public Docket docket1() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("管理端接口")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
.paths(PathSelectors.any())
.build();
return docket;
}
@Bean
public Docket docket2() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("用户端接口")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 设置静态资源映射
*
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 扩展 Spring MVC 的消息转换器
*
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 创建一个 消息转换器 对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 为 消息转换器 设置一个 对象转换器,对象转换器可以将 Java 对象序列化为 Json 数据
converter.setObjectMapper(new JacksonObjectMapper());
// 将自己的 消息转换器 加入到 容器 中,放置在第一位
converters.add(0, converter);
}
}
2.3.2 handler
@RestControllerAdvice 可以捕获并处理控制器层抛出的异常,从而统一管理异常处理逻辑,发生异常时,sping自动调用 handler 方法
/**
* 全局异常处理器,处理项目中抛出的业务异常
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获业务异常
*
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(BaseException ex) {
log.error("异常信息:{}", ex.getMessage());
return Result.error(ex.getMessage());
}
/**
* 处理 SQL 异常
*
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
// Duplicate entry 'ada' for key 'employee.idx_username'
if (ex.getMessage().contains("Duplicate entry")) {
String[] s = ex.getMessage().split(" ");
String username = s[2]; // 拿到用户名
String msg = username + MessageConstant.ALREADY_EXIST;
return Result.error(msg);
} else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
}
2.3.3 interceptor
拦截器
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是 Controller 的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt 校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
BaseContext.setCurrentId(empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应 401 状态码
response.setStatus(401);
return false;
}
}
}
2.3.3 启动类
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching // 开启缓存注解
@EnableScheduling // 开启任务调度
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
2.3.4 配置
服务器端口号
server:
port: 8080
dev:开发环境
spring:
profiles:
active: dev
main:
allow-circular-references: true
datasource:
druid:
driver-class-name: ${sky.datasource.driver-class-name}
url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: ${sky.datasource.username}
password: ${sky.datasource.password}
redis:
host: localhost
port: 6379
password: 123456
mybatis 配置
type-aliases-package: MyBatis 的类型别名包路径。MyBatis 提供了类型别名功能,可以为 Java 类型指定简短的别名,便于在 Mapper XML 文件中使用
开发环境下
数据库连接配置 和 阿里云oss
2.3.5 Controller & Service & Mapper
Controller 层是应用程序的最上层,负责处理来自客户端的请求(如 HTTP 请求),调用服务层的业务逻辑,并将响应返回给客户端。这个层是 MVC 模式中的控制器部分
常用注解:
@RestController
类注解,是 @Controller 和 @ResponseBody 的组合注解
@RequestMapping
类注解,Spring MVC 中用于映射 HTTP 请求到处理方法(控制器方法)或类上的重要注解。它定义了一个 URL 路径和 HTTP 方法的映射,使得应用程序可以响应用户发起的不同请求
指定了默认的路径
@Autowired
类字段注解,用于 自动注入依赖
@PostMapping / @GetMapping / @PutMapping / @DeleteMapping
方法注解,是 HTTP 请求方法的映射注解,它们简化了对特定 HTTP 请求(POST、GET、PUT、DELETE)类型的映射,后面加上路径 url
@RequestBody
将 HTTP 请求体中的数据解析为 Java 对象
@PathVariable
用于获取 URL 路径中的占位符,并将该占位符的值绑定到控制器方法的参数上
三种不同方式的请求:
- 请求体:@RequestBody
- 路径参数:/path/{data} @PathVariable("data") Data data
- Query 请求:不需要加注解,由Spring自动解析到对象
以两个例子:员工登录 和 新增员工 为例:
员工登录:
在方法前面加上统一的 service 层注入
@Autowired
private EmployeeService employeeService;
这里是 spring 两大特性(依赖注入(Dependency Injection, DI)和面向切面编程(Aspect-Oriented Programming, AOP))之一的 依赖注入
依赖注入是 Spring 的核心功能之一。它是一种设计模式,旨在减少类之间的耦合,使得对象不需要手动创建和管理依赖项,而是由 Spring 容器自动注入它们所需要的依赖
如果不这样做,
手动创建对象意味着必须在代码中自己决定什么时候以及如何创建对象,这会导致:
- 耦合性增强:如果
EmployeeService
的实现发生变化(如需要添加依赖),你必须手动修改控制器代码。Spring 的依赖注入允许你解耦对象的创建和使用,使得代码更具灵活性- 代码重复:你可能需要在多个地方重复创建
Service
对象。使用依赖注入,可以通过 Spring 容器在整个应用中管理和复用相同的Service
对象
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation(value = "员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成 jwt 令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
// 前端页面使用的 视图对象
// 通过 builder 构建器构造对象,而不是 new
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
登录接口的描述:
路径 Path:/login
方法 Method:POST
请求参数格式:JSON
请求参数:封装在一个数据传输对象 DTO 中
新增员工:
/**
* 新增员工
*
* @param employeeDTO
* @return
*/
@ApiOperation("新增员工")
@PostMapping("/admin/employee")
public Result addEmployee(@RequestBody EmployeeDTO employeeDTO) {
employeeService.addEmployee(employeeDTO);
return Result.success();
}
Service 层:
注解 @Service
实现 Service 接口
注入 Mapper
处理业务逻辑,封装的dto少了一些属性,添加属性变成数据库对应的entity才传入到Mapper
/**
* 新增员工
*
* @param employeeDTO
*/
@Override
public void addEmployee(EmployeeDTO employeeDTO) {
// 往下传递到 Mapper 层前,不要传 DTO,传的是实体对象 employee
// 而且 DTO 相比 employee 少了几个属性,分别是:
// password status createTime updateTime createUser updateUser
Employee employee = new Employee();
// 对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee);
// 设置 密码,默认是 123456,并且使用 MD5 加密,往数据库存的是加密后的密文,这里使用常量类,便于维护
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
// 设置 状态,默认为 1,这里使用常量类,便于维护
employee.setStatus(StatusConstant.ENABLE);
// 设置 创建时间 和 更新时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 设置 创建人 和 修改人
// 就是当前登录的用户 id
Long id = BaseContext.getCurrentId();
employee.setCreateUser(id);
employee.setUpdateUser(id);
employeeMapper.addEmployee(employee);
}
Mapper 层
使用占位符传参(当传参只有一个时,不用指定名字,直接输入字段,否则要#{employee.name})
/**
* 新增员工
*
* @param employee
*/
@Insert("insert into employee (name, username, password, phone, sex, " +
"id_number, create_time, update_time, create_user, update_user) " +
"values (#{name},#{username},#{password},#{phone},#{sex},#{idNumber}," +
"#{createTime},#{updateTime},#{createUser},#{updateUser})")
void addEmployee(Employee employee);