目录
公共字段填充
问题分析
先前新增员工和菜品时,列如创建时间,创建人id,修改时间等重复的字段代码比较冗余,不便于后期维护。
实现思路
产生契机:在Insert和update这两个操作时产生
技术点:枚举、注解、AOP、反射
楼主这一块不是很熟练,再总结一下
枚举
在Java中,枚举是一种具有固定的可能值集的类型。
enum Size {
SMALL, MEDIUM, LARGE, EXTRALARGE
}
花括号内的值称为枚举值(常量)。这些是枚举类型可以保留的唯一值。
使用案例:
class Test {
Size pizzaSize;
public Test(Size pizzaSize) {
this.pizzaSize = pizzaSize;
}
public void orderPizza() {
switch(pizzaSize) {
case SMALL:
System.out.println("我点了一个小号比萨饼。");
break;
case MEDIUM:
System.out.println("我点了一个中号的披萨。");
break;
default:
System.out.println("我不知道该点哪一种。");
break;
}
}
}
注解(Anotation)
任何声明都可以通过将其放在声明上面来标记注解。从Java 8开始,注释也可以放在类型之前。
//内置注解
@Override
@Deprecated
@SuppressWarnings
//元注解
@Retention//指定了该注解可用的最高级别。
@Documented//默认情况下,自定义注解不包含在官方Java文档中。要将注解包含在Javadoc文档中,请使用@Documented注解。
@Target(ElementType)//我们可以使用@Target注解将注解限制为应用于特定目标。
@Inherited//注解类型不能从超类继承。但是,如果需要将注解从超类继承到子类,则可以使用@Inherited注解。
@Repeatable
//带有@Repeatable标记的注解可以多次应用于同一声明。
//自定义注解
@interface MyCustomAnnotation {
String value() default "default value";
}
class Main {
@MyCustomAnnotation(value = "nhooo")//这里传入的value就是自定义注解里的"default value"
public void method1() {
System.out.println("测试方法1");
}
public static void main(String[] args) throws Exception {
Main obj = new Main();
obj.method1();
}
}
如果需要检索注解数据,可以使用反射。
反射
在学习Java反射之前,我们需要了解一个名为Class的Java类。
Java中有一个名为Class的类,该类在运行时保留有关对象和类的所有信息。
Class对象描述了特定类的属性。该对象用于执行反射。
创建名为class的对象
//使用forName()方法
Class Dog { }
Class c1 = Class.forName("Dog");
//使用getClass()方法
Dog d1 = new Dog()
Class c1 = d1.getClass();
//使用.class
Class c1 = Dog.class;
自动填充公共字段
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() { }
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException {
log.info("开始进行公共字段自动填充。。。");
//获取当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operation = autoFill.value();
//获得实体对象
Object[] args = joinPoint.getArgs();
if(args != null && args.length > 0) {
return;
}
Object entity = args[0];
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operation == OperationType.INSERT) {
try {
Method setCreateTiME = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
setCreateTiME.invoke(entity, now);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
新增菜品
需求分析
文件上传接口
阿里云OSS
介绍
阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
阿里云OSS将数据文件以对象(object)的形式上传到存储空间(bucket)中。
楼主花了四块多买了一个oss,教程网上很多,就不赘述了
接下来是配置。在application.yml文件中
alioss:
endpoint: ${sky.alioss.endpoint}
access-key-id: ${sky.alioss.access-key-id}
access-key-secret: ${sky.alioss.access-key-secret}
bucket-name: ${sky.alioss.bucket-name}
再在application-dev.xml文件中附上具体的值
OssConfiguration类
@Configuration
@Slf4j
public class OssConfiguration {
@Bean
@ConditionalOnMissingBean
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) {
log.info("开始创建阿里云文件上传工具类对象{}", aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint(),
aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(),
aliOssProperties.getBucketName());
}
}
| 声明该类是一个 Spring 配置类,用于替代传统的 XML 配置文件,通过 Java 代码定义 Bean 及其依赖关系 |
| 通过 Lombok 自动生成一个名为 log 的日志对象 |
| 在配置类中声明一个 Bean,告诉 Spring 容器如何创建该 Bean 的实例。 |
| 条件化创建 Bean,仅当 Spring 容器中 不存在 该类型(或指定名称)的 Bean 时,才会执行当前 @Bean 方法。 |
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("upload")
public Result<String> upload(MultipartFile file) {
log.info("文件上传:{}",file);
try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀
String extention = originalFilename.substring(originalFilename.lastIndexOf("."));
//构造新文件名称
String objectName = UUID.randomUUID().toString() + extention;
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败", e);
}
return Result.error("文件上传失败");
}
}
@Autowired 的核心作用
自动装配 Bean
- Spring 会在容器中查找与被注入字段 / 方法参数类型匹配的 Bean,并将其注入。
- 例如,在
CommonController
中
@Autowired
private AliOssUtil aliOssUtil; // Spring 会自动查找 AliOssUtil 类型的 Bean 并注入
成功!
可以看到阿里OSS里也用相应的图片
新增菜品接口
Controller类
@RestController
@RequestMapping("/admin/dish")
@Api(tags="菜品管理")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
@ApiOperation("新增菜品")
public Result addDish(@RequestBody DishDTO dishDTO) {
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
return Result.success();
}
}
serviceImpl类
public void saveWithFlavor(DishDTO dish) {
Dish dishEntity = new Dish();
BeanUtils.copyProperties(dish, dishEntity);
//向菜品表插入一条数据
dishMapper.insert(dishEntity);
Long id =dishEntity.getId();
//向口味表插入n条数据
List<DishFlavor> flavors = dish.getFlavors();
if(flavors!=null&&flavors.size()>0){
flavors.forEach(flavor -> {
flavor.setDishId(id);
});
//批量插入
dishFlavorMapper.insertBatch(flavors);
}
}
DishMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish(name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)
values
(#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
</insert>
</mapper>
注意:useGeneratedKeys="true"和 keyProperty="id"配合,能够指定主键值要映射到的实体类属性(或字段)
DishFlavorMapper.xml
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor(dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
</mapper>
注意:在 MyBatis 中,<foreach>
标签是 动态 SQL 的核心工具之一,主要用于 遍历集合数据并生成批量 SQL 语句
- 集合参数的处理:
- 若方法参数是 单个集合(如
List<Flavor> flavors
),collection
直接写集合变量名(flavors
)。 - 若参数是 多个集合或对象,需通过
@Param
注解为参数命名,避免 MyBatis 无法识别集合名。
- 若方法参数是 单个集合(如
- SQL 拼接安全:
- 避免在
separator
中使用OR
、AND
等可能导致 SQL 注入的符号(需结合业务逻辑严格校验)。
- 避免在
- 数据库批量操作优化:
- 部分数据库(如 MySQL)支持
batch insert
,配合 MyBatis 的ExecutorType.BATCH
可进一步提升性能。
- 部分数据库(如 MySQL)支持
菜品分页查询
这个功能需要注意的时SQL中的联合查询语句
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*,c.name from dish d left outer join category c on d.category_id =c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id like concat('%',#{categoryId},'%')
</if>
<if test="status != null">
and d.status like concat('%',#{status},'%')
</if>
</where>
order by d.create_time desc
</select>
删除菜品
业务逻辑
@Transactional
public void deleteDish(List<Long> ids){
//判断是否能被删除
for(Long id:ids){
Dish dish=dishMapper.getById(id);
if(dish.getStatus() == StatusConstant.ENABLE){
//不允许删除
throw new RuntimeException(MessageConstant.DISH_ON_SALE);
}
}
//判断是否在套餐中
List<Long> setmealIds=setmealDishMapper.getSetmealIdByIds(ids);
if(setmealIds!=null && setmealIds.size()>0){
//不允许删除
throw new RuntimeException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//删除菜品表中的数据
for(Long id:ids){
dishMapper.deleteById(id);
//删除关联的口味表中的数据
dishFlavorMapper.deleteByDishId(id);
}
}
修改菜品
楼主这里犯了一个错误,结果找bug找了半天,而且IDEA控制台也没有任何信息
<update id="update">
update dish
<set>
<if test="name!=null">name = #{name}, </if>
</set>
where id = #{id}
</update>
这里没写where语句导致找bug找了半个小时,气死了