黑马瑞吉外卖项目开发笔记
目录
软件开发整体介绍
开发流程
角色分工
软件环境
瑞吉外卖项目介绍
项目介绍
产品原型展示
技术选型
功能架构
角色
开发环境搭建
数据库环境搭建
Maven环境搭建
1.直接创建maven项目(这里是没有用springboot快捷方式创建)
2.导入pom文件
3.添加核心配置文件application.yml
4.编写项目启动类
5.添加前端资源
6.springboot项目基本结构
功能开发总结
1.功能接口开发总结
编辑 需求分析编辑
代码开发
2.通用结果集
3.请求方式及springboot相关的注解(常用)
4.Session
5.过滤器
6.异常处理
7.md5加密
8.前端后端交互流程示例
9.分页查询
需求分析及代码开发
图示示例
10.js对long型数据进行处理导致精度丢失问题
11.公共字段自动填充
需求开发
代码开发
12.文件上传及文件下载
简要介绍
编辑编辑
编辑
代码开发
13.Dto
13.短信发送
简介
操作步骤
14. 手机号实现用户登录
15.事务控制
软件开发整体介绍
开发流程
角色分工
软件环境
瑞吉外卖项目介绍
项目介绍
产品原型展示
技术选型
功能架构
角色
开发环境搭建
数据库环境搭建
我这里用的是Navicat图形化工具创建的,或者通过命令行创建,不过就是比较麻烦,这里就不介绍了。
然后导入我们的sql脚本,我们就可以看到下面的数据库文件了。
Maven环境搭建
1.直接创建maven项目(这里是没有用springboot快捷方式创建)
2.导入pom文件
这里是我们用到的依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>compile</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.23</version></dependency></dependencies>
3.添加核心配置文件application.yml
server:port: 8080
spring:application:name: reggie_take_outdatasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: root
mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: ASSIGN_ID
4.编写项目启动类
@Slf4j//日志
@SpringBootApplication//项目启动类注解
public class ReggieApplication {public static void main(String[] args) {SpringApplication.run(ReggieApplication.class,args);log.info("项目启动成功...");}
}
5.添加前端资源
默认情况下我们只能访问static或template下的静态资源
所以我们要写配置类
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {/*** 设置静态资源映射* @param registry*/@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("开始进行静态资源映射...");registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}
}
6.springboot项目基本结构
图中的img是我需要的,正常情况下应该放在resource下。大致结构是这样的。
common包基本放置一些通用的类。config是配置类包 。controller是控制类包。dto是数据传输对象的意思,他的类跟实体类类似,但又有区别,我们放到下面再去专门讲解。entry包是实体类包,一般与数据表相对应。filter包是过滤器包。mapper是映射类包,负责实体映射。service是业务类包,包括业务接口和对应实现类。utils包是工具类包。
功能开发总结
1.功能接口开发总结
开发一个功能接口,我们基本需要几个简单步骤,首先对功能进行需求分析,然后我们编写需要操作的实体类(对应我们的数据表),然后创建对应的mapper,然后接着创建对应的service接口和实现类,最后创建我们的controller控制类。接着我们按需求开发即可。下面是一个登录接口开发过程
下面是对应要开发的前端静态页面,我们通过F12进行调试观察。
需求分析
对应的需要操作的是employee表。下面是html中的javascript相关登录代码。我们作为后端开发人员,我们只需要看懂即可。例如handleLogin()是我们的登录方法,loginApi()是我们的发送请求接口。我们进入接口,即可查看我们要访问的地址。(从网页上用f12查看也是一样的)
我们在写后端代码时应当返回上面几个信息,然后相应逻辑如下。
代码开发
首先创建了mapper,service,controller,common,entity包(我们这里用的是mybatis框架)
先创建实体类
/*** 员工实体类*/
@Data
public class Employee implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String username;private String name;private String password;private String phone;private String sex;private String idNumber;//身份证号码private Integer status;private LocalDateTime createTime;private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;}
再创建Mapper接口
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
接着创建业务接口和实现类
public interface EmployeeService extends IService<Employee> {
}@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
然后创建controller类
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 员工登录* @param request* @param employee* @return*///@RequestBody返回json数据,并封装成employee对象,// HttpServletRequest将员工id存入session@PostMapping("/login")public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){//将页面提交的密码进行md5加密处理String password = employee.getPassword();password= DigestUtils.md5DigestAsHex(password.getBytes());//根据页面提交用户名来查询数据库LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Employee::getUsername,employee.getUsername());Employee emp = employeeService.getOne(queryWrapper);//因为用户名是唯一的//如果没有查到,则返回失败结果if(emp==null){return R.error("登陆失败");}//比对密码if(!emp.getPassword().equals(password)){return R.error("登陆失败");}//查看员工状态,是否为禁用状态if(emp.getStatus()==0){return R.error("账号已禁用");}//登陆成功,将员工id存入sessionrequest.getSession().setAttribute("employee",emp.getId());return R.success(emp);}
}
2.通用结果集
我们这里封装了一个通用的结果集,我们返回前端的信息都是用它来封装的,方便且实用。
里面的code=0或1,我们在前端都有对应的处理。我们需要注意的是在不同的前端对应的处理不同。下面是登录页面的处理。1是成功,0是失败.
if (String(res.code) === '1') {localStorage.setItem('userInfo',JSON.stringify(res.data))window.location.href= '/backend/index.html' } else {this.$message.error(res.msg)this.loading = false }
@Data
public class R<T> {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据private Map map = new HashMap(); //动态数据public static <T> R<T> success(T object) {R<T> r = new R<T>();r.data = object;r.code = 1;return r;}public static <T> R<T> error(String msg) {R r = new R();r.msg = msg;r.code = 0;return r;}public R<T> add(String key, Object value) {this.map.put(key, value);return this;}}
3.请求方式及springboot相关的注解(常用)
@RequestMapping("/employee")//这个我们是在类上加的(必须加)。 参数代表该类的请求路径,可有可无。下面的是在方法上加的,注意区分。
查找
get:@GetMapping
post: @PostMapping
删除
delete: @DeleteMapping
插入,修改
put: @PutMapping
他们也可加参数,例如 @PostMapping("/login"),下面这三种是我们比较常用的。
这个是@RequestBody的一个例子,他表示我们返回的是Employee的json形式的数据。
@PostMapping("/login")public R<Employee> login(@RequestBody Employee employee){
}这个是@PathVariable的例子,他是映射 URL 绑定的占位符。
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。
这种形式也可称之为REST风格。
@GetMapping("/{id}")public R<Employee> getById(@PathVariable Long id){
}这个是@RequestParam,主要用于将请求参数区域的数据映射到控制层方法的参数上。
首先我们需要知道@RequestParam注解主要有哪些参数value:请求中传入参数的名称,如果不设置后台接口的value值,则会默认为该变量名。否则在后台接口中ids将接收不到对应的数据required:该参数是否为必传项。默认是true,表示请求中一定要传入对应的参数,否则会报404错误,如果设置为false时,当请求中没有此参数,将会默认为null,而对于基本数据类型的变量,则必须有值,这时会抛出空指针异常。如果允许空值,则接口中变量需要使用包装类来声明。defaultValue:参数的默认值,如果请求中没有同名的参数时,该变量默认为此值。注意默认值可以使用SpEL表达式,如"#{systemProperties[‘java.vm.version’]}"
@DeleteMappingpublic R<String> delete(@RequestParam List<Long> ids){
}@PostMapping("/status/{code}")public R<String> stopSale(@RequestParam List<Long> ids,@PathVariable int code){
}
4.Session
我们在编程中常常需要在页面间传值。这时候我们经常需要用到session。(用来存储数据)
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){//登陆成功,将员工id存入sessionrequest.getSession().setAttribute("employee",emp.getId());//清除session中的idrequest.getSession().removeAttribute("employee");
}
5.过滤器
由于用户需要登录才能访问内部页面等需求,我们在开发中我们经常需要用到过滤器。
下面是一个例子,如果未登录就访问其他页面,自动跳转至登录页面
/*** 检查用户是否登录*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {//路径匹配器,支持通配符public static final AntPathMatcher PATH_MATCHER=new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request=(HttpServletRequest) servletRequest;HttpServletResponse response=(HttpServletResponse) servletResponse;//获取本次请求uriString requestURI = request.getRequestURI();//定义不需要处理的请求路径String[] urls=new String[]{"/employee/login","/employee/logout","/backend/**","/front/**"};//判断本次请求是否需要处理boolean check = check(urls, requestURI);//放行if (check){filterChain.doFilter(request,response);return;}//判断登录状态if (request.getSession().getAttribute("employee")!=null){filterChain.doFilter(request,response);return;}//未登录,通过输出流方式向客户端响应数据response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/*** 路径匹配* @param urls* @param requestURI* @return*/public boolean check(String[] urls,String requestURI){for (String url : urls) {boolean match = PATH_MATCHER.match(url, requestURI);if (match){return true;}}return false;}
然后在启动类添加注解@ServletComponentScan,这样我们就开启了过滤器功能。
6.异常处理
我们在开发中会经常遇到各种各样的异常,这时我们经常需要设置全局异常处理和自定义异常。
/*** 全局异常处理*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法* @return*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){log.error(ex.getMessage());if (ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String msg=s[2]+"已存在";return R.error(msg);}return R.error("未知错误");}/*** 异常处理方法* @return*/@ExceptionHandler(CustomerException.class)public R<String> exceptionHandler(CustomerException ex){log.error(ex.getMessage());return R.error(ex.getMessage());}
}
/*** 自定义业务异常类*/
public class CustomerException extends RuntimeException{public CustomerException(String message){super(message);}
}
7.md5加密
我们在登录过程中处理数据时我们需要对密码进行加密,以保证账户的安全。
我们经常会用到md5加密,这个是比较常用的。下面就来简要介绍一下。
MD5加密是一种不可逆的加密算法,不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。
下面是设置初始密码的例子。//设置初始密码,需要md5加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));employeeService.save(employee);
8.前端后端交互流程示例
9.分页查询
当我们需要显示的数据量庞大的时候,我们想要简洁明了的展示我们的数据,这时候分页就是我们的首选。以下是相关流程示例。(这里配置的mybatisPlus的分页插件)
需求分析及代码开发
/*** 配置MP的分页插件*/
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}/*** 员工信息分页查询* @param page* @param pageSize* @param name* @return*/@GetMapping("/page")public R<Page> page(int page,int pageSize,String name){log.info("page={},pageSize={},name={}",page,pageSize,name);//构造分页构造器Page pageInfo=new Page(page,pageSize);//构造条件构造器LambdaQueryWrapper<Employee> queryWrapper=new LambdaQueryWrapper();//添加过滤条件queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//添加排序条件queryWrapper.orderByAsc(Employee::getUpdateTime);//执行查询employeeService.page(pageInfo,queryWrapper);return R.success(pageInfo);}
图示示例
10.js对long型数据进行处理导致精度丢失问题
在开发中,我们发现js对long型数据处理会导致精度丢失。
处理思想:在服务端给页面响应数据时进行处理,将long型数据转成String类型。
下面是具体实现步骤。
@PutMapping//@RequestBody转换成json格式public R<String> update(HttpServletRequest request,@RequestBody Employee employee){log.info(employee.toString());Long empId = (Long)request.getSession().getAttribute("employee");employee.setUpdateUser(empId);employee.setUpdateTime(LocalDateTime.now());employeeService.updateById(employee);return R.success("员工信息修改成功");}
/*** 对象映射器:基于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_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(BigInteger.class, ToStringSerializer.instance).addSerializer(Long.class, ToStringSerializer.instance).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);}
}/*** 扩展mvc框架的消息转换器* @param converters*/@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {log.info("扩展消息转换器");//创建消息转换器对象MappingJackson2HttpMessageConverter messageConverter=new MappingJackson2HttpMessageConverter();//设置对象转换器messageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的消息转换器对象追加到消息转换器容器中converters.add(0,messageConverter);}
11.公共字段自动填充
需求开发
代码开发
由于要实现公共字段自动填充,我们就需要从session拿到一些数据,这里我们用ThreadLocal.
/*** 自定义元数据对象处理器*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {/*** 插入操作自动填充* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {log.info("insert...");log.info(metaObject.toString());metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime", LocalDateTime.now());metaObject.setValue("createUser", BaseContext.getCurrentId());metaObject.setValue("updateUser", BaseContext.getCurrentId());}/*** 更新操作自动填充* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {log.info("update...");log.info(metaObject.toString());long id = Thread.currentThread().getId();log.info("线程id为:{}",id);metaObject.setValue("updateTime", LocalDateTime.now());metaObject.setValue("updateUser", BaseContext.getCurrentId());}
}//实体类中部分代码@TableField(fill = FieldFill.INSERT)//插入时自动填充private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时自动填充private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;
/*** 基于ThreadLocal封装工具类,用于保存和获取当前登录用户id* 在每个线程内单独起作用*/
public class BaseContext {private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();/*** 设置值* @param id*/public static void setCurrentId(Long id){threadLocal.set(id);}/*** 获取值* @return*/public static Long getCurrentId(){return threadLocal.get();}
}
12.文件上传及文件下载
简要介绍
代码开发
/*** 文件上传和下载*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {@Value("${reggie.path}")private String basePath;/*** 文件上传* @param file* @return*/@PostMapping("/upload")//名字file必须与前端一致public R<String> upload(MultipartFile file){//file是一个临时文件,需要转存到指定位置log.info(file.toString());//获取原始文件名String originalFilename = file.getOriginalFilename();String suffix=originalFilename.substring(originalFilename.lastIndexOf("."));//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖String fileName= UUID.randomUUID().toString()+suffix;//创建目录对象File dir=new File(basePath);//判断当前目录是否存在if (!dir.exists()){dir.mkdirs();}try {//将临时文件转存到指定位置file.transferTo(new File(basePath+fileName));} catch (IOException e) {e.printStackTrace();}return R.success(fileName);}/*** 文件下载* @param name* @param response*/@GetMapping("/download")public void download(String name, HttpServletResponse response){try {//输入流读取文件内容FileInputStream fileInputStream=new FileInputStream(new File(basePath+name));//输出流,将文件写回浏览器,在浏览器展示文件内容ServletOutputStream outputStream = response.getOutputStream();response.setContentType("image/jpeg");int len=0;byte[] bytes=new byte[1024];while ((len=fileInputStream.read(bytes))!=-1){outputStream.write(bytes,0,len);outputStream.flush();}//关闭资源outputStream.close();fileInputStream.close();} catch (Exception e) {e.printStackTrace();}}
}
13.Dto
数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器)。
由于我们在实际开发中实体类有时候满足不了我们的需求,比如本项目开发菜品,我们在菜品中需要口味的集合。
所以这里我们既用到了口味实体,也用到了菜品实体,所以这里我们用到了dto,我们封装实体类,以得到我们想要的类。(Dish和DishFlavor分别对应一张数据表)
@Data
public class DishDto extends Dish {private List<DishFlavor> flavors = new ArrayList<>();private String categoryName;private Integer copies;
}
13.短信发送
简介
阿里云
操作步骤
1.添加签名
2.添加模板
3.获取Assesskey
4.查看帮助文档,用原始java sdk测试
5.用测试短信功能测试,需充值至少一块钱。(在首页我们可以找到测试接口)
14. 手机号实现用户登录
这里我们通过session可以很简单的实现手机号登录。
/**如果这里页面点击没反应,我们需要及时情理浏览器缓存* 发送手机短信验证码* @param user* @return*/@PostMapping("/sendMsg")//json提交的要加requestbodypublic R<String> sendMsg(@RequestBody User user,HttpSession httpSession){//获取手机号String phone=user.getPhone();if (StringUtils.isNotEmpty(phone)){//生成随机6位验证码String code = ValidateCodeUtils.generateValidateCode(6).toString();log.info("code={}",code);//调用apiSMSUtils.sendMessage(signName,templateCode,phone,code);//将生成的验证码保存到session中httpSession.setAttribute(phone,code);return R.success("验证码发送成功");}return R.error("短信发送失败");}/*** 移动端用户登录,后期设置验证码定时器* @param map* @param httpSession* @return*/@PostMapping("/login")//json提交的要加requestbodypublic R<User> login(@RequestBody Map map, HttpSession httpSession){log.info(map.toString());//获取手机号和验证码String phone = map.get("phone").toString();String code = map.get("code").toString();//获取session验证码Object codeInSession = httpSession.getAttribute(phone);//比较if (codeInSession!=null && codeInSession.equals(code)){LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper();queryWrapper.eq(User::getPhone,phone);User user = userService.getOne(queryWrapper);if (user==null){//判断当前手机号是否为新用户,若为新用户则自动完成注册user=new User();user.setPhone(phone);user.setStatus(1);userService.save(user);}httpSession.setAttribute("user",user.getId());return R.success(user);}else if(codeInSession!=null && !codeInSession.equals(code)){return R.error("验证码错误");}return R.error("登录失败");}
15.事务控制
我们只需要在业务层的方法上加上一个注解即可。他可保证事务的一致性。
@Override@Transactional//因为操作多张表,我们这里加入事务public void saveWithFlavor(DishDto dishDto) {//保存菜品的基本信息到菜品表dishthis.save(dishDto);Long id = dishDto.getId();//菜品idList<DishFlavor> flavors = dishDto.getFlavors();flavors=flavors.stream().map((item)->{item.setDishId(id);return item;}).collect(Collectors.toList());//保存菜品口味数据dishFlavorService.saveBatch(flavors);}
然后在启动类上加入@EnableTransactionManagement注解即可。
黑马瑞吉外卖项目开发笔记相关推荐
- 黑马瑞吉外卖项目之订单明细的查询,分页,派送功能
黑马瑞吉外卖项目之订单明细的查询,分页,派送功能 /*** 后台管理订单信息分页查询* @param page* @param pageSize* @return*/@GetMapping(" ...
- 瑞吉外卖项目学习笔记:P1-项目介绍
瑞吉外卖项目介绍 1.项目介绍 1.1开发步骤 1.2产品原型 1.3技术选型 1.4项目功能架构 1.5角色 1.项目介绍 1.1开发步骤 实现基本要求,移动端应用通过H5实现,用户可通过浏览器访问 ...
- 瑞吉外卖项目学习笔记01
项目背景 在开发瑞吉外卖这个项目之前,我们需要全方位的来介绍一下当前我们学习的这个项目.接下来,我们将从以下的五个方面, 来介绍瑞吉外卖这个项目. 项目介绍 本项目(瑞吉外卖)是专门为餐饮企业(餐厅. ...
- 瑞吉外卖项目学习笔记-P20-Linux系统环境下项目部署
P20-Linux系统环境下项目部署 1.Linux系统环境软件安装 2.项目部署-手工部署项目(繁琐,效率低下) 2.1在IDEA中开发SpringBoot项目,并打包成jar包 2.2将jar包上 ...
- 瑞吉外卖项目学习笔记-P25-项目优化-读写分离
P25-项目优化-读写分离 1. 读写分离问题说明 1.1单台数据库 1.2 多台数据库 2.MySQL主从复制(二进制日志) 2.1介绍 2.2配置 2.2.1配置-主库maser(CentOS 数 ...
- 黑马瑞吉外卖项目之套餐删除、起售、停售和批量删除、起售、停售菜品功能
观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址和请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理. 批量起售.停售 /*** 根据id(批量)停售 ...
- 黑马瑞吉外卖项目之用户订单信息分页查询功能
这里导入OrderDto并添加sumNum package com.itheima.reggie.dto;import com.itheima.reggie.entity.OrderDetail; i ...
- 瑞吉外卖项目剩余功能补充
目录 菜品启售和停售 菜品批量启售和批量停售 菜品的批量删除 菜品删除逻辑优化 套餐管理的启售,停售 套餐管理的修改 后台按条件查看和展示客户订单 手机端减少购物车中的菜品或者套餐数量(前端展示有一点 ...
- 瑞吉外卖项目 基于spring Boot+mybatis-plus开发 超详细笔记,有源码链接
本项目是基于自学b站中 黑马程序员 的瑞吉外卖项目:视频链接: 黑马程序员Java项目实战<瑞吉外卖>,轻松掌握springboot + mybatis plus开发核心技术的真java实 ...
最新文章
- Linux下svn搭建配置流程
- druid 多数据源_Spring Boot + Mybatis 中 配置Druid多数据源并实现自由切换
- aws 性能_AWS上的应用程序自动扩展–选项和对性能的影响
- php同时删除两个列表数据库,PHP 处理 数据库多表,既能高效又能思路清晰如何处理的?...
- android语音识别和合成第三方
- c语言 字符串字符反向储存_C ++中的反向字符串
- spark原理和spark与mapreduce的最大区别
- 第5章 深度学习和卷积神经网络
- ae导出json_AE脚本导出json格式的Web动画工具 Bodymovin v5.7.0 + 使用教程【资源分享1171】...
- ORA-00091错误的解决方式
- php引用()详解及注意事项
- MyBatisplus字段名与表名的映射
- python读excel两列为字典_python交换数据的两列,python处理excel数据, python交换...
- 极品婆媳龙争虎斗---终极PK王者之战(10)
- UE4 坐标系 坐标轴旋转轴
- LaTex Verbatim 环境下使用数学符号
- 【C语言】求最小公倍数三种方法
- 什么是模态分析?什么是振型?
- 树莓派GPIO控制/使用的教程
- android组件化单独运行
热门文章
- Unity 2D光照(2D Light)和阴影(Shadow Caster 2D)
- iphone4s更换电池_如果更换了iPhone电池后仍然出现问题该怎么办
- 用python画几个东西怎么画_一步一步教你如何用Python画一个滑稽
- 2020主流室内定位技术对比
- nuscenes instance 调研笔记
- S4HANA 2020输入会计凭证提示需要输入税码的配置
- 使用SSH服务管理远程主机(RHEL8)
- 无法运行宏,可能是因为该宏在此工作簿中不可用,或者所有的宏都被禁用的解决方法...
- 规划云:GIS相关模块
- 汉诺塔模拟器java