SpringBoot+SSM项目实战 苍穹外卖(7)(Spring Cache)

继续上一节的内容,本节实现缓存菜品、缓存套餐、添加购物车、查看购物车和清空购物车功能。

目录

  • 缓存菜品
  • 缓存套餐(基于Spring Cache)
    • @EnableCaching、@Cacheable、@CachePut和@CacheEvict
    • Spring Cache实现缓存套餐
  • 添加购物车
  • 查看购物车
  • 清空购物车





缓存菜品

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。结果:系统响应慢、用户体验差

在这里插入图片描述

可以通过Redis来缓存菜品数据,减少数据库查询操作。缓存逻辑分析:每个分类下的菜品保存一份缓存数据;数据库中菜品数据有变更时清理缓存数据。

在这里插入图片描述

修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:

@Autowired
private RedisTemplate redisTemplate;
/*** 根据分类id查询菜品** @param categoryId* @return*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {//构造redis中的key,规则:dish_分类idString key = "dish_" + categoryId;//查询redis中是否存在菜品数据List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);if(list != null && list.size() > 0){//如果存在,直接返回,无须查询数据库return Result.success(list);}Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品//如果不存在,查询数据库,将查询到的数据放入redis中list = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key, list);return Result.success(list);
}

为了保证数据库和Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。

需要改造的方法:新增菜品、修改菜品、批量删除菜品和起售、停售菜品。

在管理端DishController中添加清理缓存的方法,保证数据一致性:

/*** 清理缓存数据* @param pattern*/
private void cleanCache(String pattern){Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);
}

然后优化以前的方法:
1). 新增菜品优化

@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO) {log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavor(dishDTO);//清理缓存数据String key = "dish_" + dishDTO.getCategoryId();cleanCache(key);return Result.success();
}

2). 菜品批量删除优化

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBatch(ids);// 可能影响多个菜品分类,要知道影响哪些分类还需要等查询之后查数据库才知道比较麻烦// 所以直接将redis里所有的菜品缓存数据清理掉,删掉所有以dish_开头的keycleanCache("dish_*");return Result.success();
}

3). 修改菜品优化

@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.updateWithFlavor(dishDTO);// 因为如果只是修改基础属性还好 但如果是修改菜品的分类那要影响俩个key// 也干脆将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();
}

4). 菜品起售停售优化

@PostMapping("/status/{status}")
@ApiOperation("修改菜品销售状态")
public Result updateStatus(@PathVariable Integer status,Long id){log.info("根据分类id修改菜品销售状态:{}", status);dishService.updateStatusById(status,id);// 还需要去根据id查询分类id 比较麻烦// 也是将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();
}

个人觉得老师这部分的代码写得有些粗暴,大部分都选择了直接删除,但是可能这样在某种程度上也好,不用增加代码逻辑显得过于臃肿,因为本身修改菜品的情况就比较少。


但是新增菜品和删除菜品时感觉还是无需删除,因为新增菜品默认是停售状态,删除菜品时这些菜品必须是停售状态才能删除,只有修改这个菜品的销售状态为起售之后才会在用户端显示该菜品,所以这些地方根本不需要删除redis缓存,在起售停售里删除缓存就行。


然后感觉需要使用缓存的地方还是挺多的,如果大部分都是直接清理掉所有的缓存数据,那这部分的代码是不是用AOP来实现,在需要使用缓存清理的地方加上响应注解更加整洁呢?

下面进行功能测试,可以通过如下方式进行测试:查看控制台sql、前后端联调、查看Redis中的缓存数据

1). 加入缓存

当第一次查询某个分类的菜品时,会从数据为中进行查询,同时将查询的结果存储到Redis中,在后续的访问,若查询相同分类的菜品时,直接从Redis缓存中查询,不再查询数据库。登录小程序:选择蜀味牛蛙(id=17)

在这里插入图片描述

查看控制台sql:有查询语句,说明是从数据库中进行查询

在这里插入图片描述

查看Redis中的缓存数据:说明缓存成功

在这里插入图片描述

再次访问:选择蜀味牛蛙(id=17)

在这里插入图片描述

说明是从Redis中查询的数据。

2). 菜品修改

比如当在后台修改菜品数据时,为了保证Redis缓存中的数据和数据库中的数据时刻保持一致,当修改后,需要清空对应的缓存数据。用户再次访问时,还是先从数据库中查询,同时再把查询的结果存储到Redis中,这样,就能保证缓存和数据库的数据保持一致。
进入后台:修改蜀味牛蛙分类下的任意一个菜品,当前分类的菜品数据已在Redis中缓存。然后修改菜品,查看Redis中的缓存数据发现修改时,已清空缓存。

在这里插入图片描述

测试完毕提交代码到github。





缓存套餐(基于Spring Cache)

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。其提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCache、Caffeine、Redis(常用)

在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:

注解说明
@EnableCaching开启缓存注解功能,通常加在启动类上
@Cacheable在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除

在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.7.3</version> 
</dependency>


@EnableCaching、@Cacheable、@CachePut和@CacheEvict

下面用一个入门案例来学习一下SpringCache注解的使用,导入老师提供的基础工程:底层已使用Redis缓存实现:

在这里插入图片描述

创建名为spring_cache_demo数据库,将springcachedemo.sql脚本直接导入数据库中。然后在引导类上加@EnableCaching:

@Slf4j
@SpringBootApplication
@EnableCaching//开启缓存注解功能
public class CacheDemoApplication {public static void main(String[] args) {SpringApplication.run(CacheDemoApplication.class,args);log.info("项目启动成功...");}
}


@CachePut注解

@CachePut 说明:作用: 将方法返回值,放入缓存。
​value: 缓存的名称, 每个缓存名称下面可以有很多key
key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

/*** CachePut:将方法返回值放入缓存* value:缓存的名称,每个缓存名称下面可以有多个key* key:缓存的key*/@PostMapping@CachePut(value = "userCache", key = "#user.id")public User save(@RequestBody User user){userMapper.insert(user);return user;}

上面这样写,假如方法运行完,生成的主键为1,那么在redis中存储的key是userCache::1,存储的value是方法返回值,存储的key是这个格式是因为指定了注解的两个属性
userCache为注解的value属性,然后自动加上::,后面跟着的key属性是可变参数,这个也是自己指定。key的写法如下:

#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

后面三种其实是同一个意思,推荐使用第一种,因为比较直观。
启动服务,通过swagger接口文档测试,访问UserController的save()方法。因为id是自增,所以不需要设置id属性。访问完之后:

在这里插入图片描述

在这里插入图片描述

在这个工程里使用的是1号数据库所以在db1。然后我们发现redis种的key也是支持这种树形结构的,在userCache文件夹下有一个Empty文件夹,然后是userCache::1。这个层次是怎么划分出来的呢?为什么中间有个Empty呢?因为userCache::1中间有两个冒号,一个冒号代表一级。可以自己去redis命令行里试一下命令:set a:b:c:d test 就能看到四级的key



@Cacheable注解

​ 作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中
value: 缓存的名称,每个缓存名称下面可以有多个key
​key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

在getById上加注解@Cacheable

/**
* Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中
* value:缓存的名称,每个缓存名称下面可以有多个key
* key:缓存的key
*/
@GetMapping
@Cacheable(cacheNames = "userCache",key="#id")
public User getById(Long id){User user = userMapper.getById(id);return user;
}

重启服务,通过swagger接口文档测试,访问UserController的getById()方法。第一次访问,会请求我们controller的方法,查询数据库。后面再查询相同的id,就直接从Redis中查询数据,不用再查询数据库了,就说明缓存生效了。

在这里插入图片描述



@CacheEvict注解注解

​ 作用: 清理指定缓存
value: 缓存的名称,每个缓存名称下面可以有多个key
​key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

在 delete 方法上加注解@CacheEvict

@DeleteMapping
@CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
public void deleteById(Long id){userMapper.deleteById(id);
}@DeleteMapping("/delAll")
@CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据
public void deleteAll(){userMapper.deleteAll();
}

重启服务,通过swagger接口文档测试,访问UserController的deleteAll()方法。查看user表:数据清空。查询Redis缓存数据,userCache下所有的缓存数据被清空。

在这里插入图片描述

在这里插入图片描述





Spring Cache实现缓存套餐

1). 导入Spring Cache和Redis相关maven坐标(已实现)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching // 开启缓存注解功能
public class SkyApplication {...
}

3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

/*** 条件查询** @param categoryId* @return*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key可能为setmealCache::100
public Result<List<Setmeal>> list(Long categoryId) {Setmeal setmeal = new Setmeal();setmeal.setCategoryId(categoryId);setmeal.setStatus(StatusConstant.ENABLE);List<Setmeal> list = setmealService.list(setmeal);return Result.success(list);
}

4). 在管理端接口SetmealController的 save、delete、update、updateStatus等方法上加入CacheEvict注解

@PostMapping
@ApiOperation(value = "新增套餐")
@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100
public Result save(@RequestBody SetmealDTO setmealDTO){log.info("新增套餐:{}",setmealDTO);setmealServices.save(setmealDTO);return Result.success();
}@DeleteMapping
@ApiOperation("套餐批量删除")
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
public Result delete(@RequestParam List<Long> ids) {log.info("套餐批量删除:{}", ids);setmealServices.deleteBatch(ids);return Result.success();
}@PutMapping
@ApiOperation("修改套餐")
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
public Result update(@RequestBody SetmealVO setmealVO) {log.info("修改套餐:{}", setmealVO);setmealServices.updateWithDishes(setmealVO);return Result.success();
}@PostMapping("/status/{status}")
@ApiOperation("修改套餐销售状态")
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
public Result updateStatus(@PathVariable Integer status,Long id){log.info("根据套餐id修改套餐销售状态:{}", status);setmealServices.updateStatusById(status,id);return Result.success();
}

这里解释一下为什么在修改套餐的时候不是去像新增套餐那样精确的根据分类id来删,而是全部删除。因为修改套餐的时候很有可能改变了套餐的分类。像根据套餐id修改销售状态那就属于是没办法精确删除,参数里就没有分类id。
但是这里因为新增套餐默认是停售,删除套餐必须套餐是停售才能删除,所以倒是感觉也没必要在这里清缓存了,前端根本看不到这些数据,你新增的时候清一下,然后用户前端查套餐的时候又得去数据库读了,感觉在修改套餐销售状态里清就行。感觉这个技术使用起来还是比较抽象的。

我看视频弹幕也有人问为什么不在管理端的新增套餐里使用@CachePut注解。
要记住我们在用户端的根据分类id查询套餐函数上加上了@Cacheable注解,所以用户在查的时候如果缓存里没有数据是自动会将查询到的数据存储到缓存的。用户端查询数据,所以设置缓存,而管理端的增删改会导致数据库和缓存数据不一致,那当然是要在管理端进行缓存删除了,要理清这个思路。

功能测试通过前后端联调方式来进行测试,同时观察redis中缓存的套餐数据。和缓存菜品功能测试基本一致,不再赘述。

测试完毕提交代码到github。





添加购物车

用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击加号将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

请添加图片描述

通过上述原型图,设计出对应的添加购物车接口。添加购物车时,有可能添加菜品,也有可能添加套餐。故传入参数要么是菜品id,要么是套餐id。

请添加图片描述

用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为shopping_cart表,具体表结构如下:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)商品名称冗余字段
imagevarchar(255)商品图片路径冗余字段
user_idbigint用户id逻辑外键
dish_idbigint菜品id逻辑外键
setmeal_idbigint套餐id逻辑外键
dish_flavorvarchar(50)菜品口味
numberint商品数量
amountdecimal(10,2)商品单价冗余字段
create_timedatetime创建时间

购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些
菜品列表展示出来的既有套餐,又有菜品,如果用户选择的是套餐,就保存套餐ID(setmeal_id),如果用户选择的是菜品,就保存菜品ID(dish_id)
对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可
在sky-pojo模块,ShoppingCartDTO.java已定义



Controller层

创建controller.user.ShoppingCartController:

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端-购物车接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 添加购物车* @param shoppingCartDTO* @return*/@PostMapping("/add")@ApiOperation("添加购物车")public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车:{}", shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);//后绪步骤实现return Result.success();}
}

Service层实现类

ShoppingCartServiceImpl

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;/*** 添加购物车** @param shoppingCartDTO*/public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);//只能查询自己的购物车数据shoppingCart.setUserId(BaseContext.getCurrentId());//判断当前商品是否在购物车中 注意实现这个接口时如果是不同口味的同一个菜品 也需要单独插入 而不能在它基础上数量加1List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if (shoppingCartList != null && shoppingCartList.size() == 1) {//如果已经存在,就更新数量,数量加1shoppingCart = shoppingCartList.get(0);shoppingCart.setNumber(shoppingCart.getNumber() + 1);shoppingCartMapper.updateNumberById(shoppingCart);} else {//如果不存在,插入数据,数量就是1//判断当前添加到购物车的是菜品还是套餐Long dishId = shoppingCartDTO.getDishId();if (dishId != null) {//添加到购物车的是菜品Dish dish = dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());} else {//添加到购物车的是套餐Setmeal setmeal = setmealMapper.getById(shoppingCartDTO.getSetmealId());shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartMapper.insert(shoppingCart);}}
}

Mapper层

ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper {/*** 条件查询** @param shoppingCart* @return*/List<ShoppingCart> list(ShoppingCart shoppingCart);/*** 更新商品数量** @param shoppingCart*/@Update("update shopping_cart set number = #{number} where id = #{id}")void updateNumberById(ShoppingCart shoppingCart);/*** 插入购物车数据** @param shoppingCart*/@Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " +" values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")void insert(ShoppingCart shoppingCart);}

ShoppingCartMapper.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.ShoppingCartMapper"><select id="list" parameterType="ShoppingCart" resultType="ShoppingCart">select * from shopping_cart<where><if test="userId != null">and user_id = #{userId}</if><if test="dishId != null">and dish_id = #{dishId}</if><if test="setmealId != null">and setmeal_id = #{setmealId}</if><if test="dishFlavor != null">and dish_flavor = #{dishFlavor}</if></where>order by create_time desc</select>
</mapper>

注意这里动态sql将口味也视为同一条购物车数据的判断之中,即若是不同的口味需要单独在购物车数据库中插入一条数据。

功能测试进入小程序,添加菜品加入购物车,查询数据库。因为现在没有实现查看购物车功能,所以只能在表中进行查看。在前后联调时,后台可通断点方式启动,查看运行的每一步。

在这里插入图片描述

测试成功,提交代码。





查看购物车

当用户添加完菜品和套餐后,可进入到购物车中,查看购物中的菜品和套餐。

在这里插入图片描述

请添加图片描述

Controller层

在ShoppingCartController中创建查看购物车的方法:

/*** 查看购物车* @return*/
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result<List<ShoppingCart>> list(){return Result.success(shoppingCartService.showShoppingCart());
}

Service层实现类

在ShoppingCartServiceImpl中实现查看购物车的方法:

/*** 查看购物车* @return*/
public List<ShoppingCart> showShoppingCart() {return shoppingCartMapper.list(ShoppingCart.builder().userId(BaseContext.getCurrentId()).build());
}

功能测试,当进入小程序时,就会发起查看购物车的请求

在这里插入图片描述

在这里插入图片描述

测试成功,提交代码。





清空购物车

当点击清空按钮时,会把购物车中的数据全部清空。

在这里插入图片描述

在这里插入图片描述

Controller层

在ShoppingCartController中创建清空购物车的方法:

/*** 清空购物车商品* @return*/
@DeleteMapping("/clean")
@ApiOperation("清空购物车商品")
public Result<String> clean(){shoppingCartService.cleanShoppingCart();return Result.success();
}

Service层实现类

在ShoppingCartServiceImpl中实现清空购物车的方法:

/*** 清空购物车商品*/
public void cleanShoppingCart() {shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());
}

Mapper层

在ShoppingCartMapper接口中创建删除购物车数据的方法:

/*** 根据用户id删除购物车数据** @param userId*/
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUserId(Long userId);

功能测试,进入到购物车页面,点击清空。

在这里插入图片描述

在这里插入图片描述

数据库该用户的购物数据也被清空:

在这里插入图片描述

测试成功提交代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/223304.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JMeter】JMeter添加插件

一、前言 ​ 在我们的工作中&#xff0c;我们可以利用一些插件来帮助我们更好的进行性能测试。今天我们来介绍下Jmeter怎么添加插件&#xff1f; 二、插件管理器 ​ 首先我们需要下载插件管理器jar包 下载地址&#xff1a;Install :: JMeter-Plugins.org 然后我们将下载下来…

yocto系列讲解[实战篇]93 - 添加Qtwebengine和Browser实例

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述集成meta-qt5移植过程中的问题问题1:virtual/libgl set to mesa, not mesa-gl问题2:dmabuf-server-buffer tries to use undecl…

[Angular] 笔记 6:ngStyle

ngStyle 指令: 用于更新 HTML 元素的样式。设置一个或多个样式属性&#xff0c;用以冒号分隔的键值对指定。键是样式名称&#xff0c;带有可选的 .<unit> 后缀&#xff08;如 ‘top.px’、‘font-style.em’&#xff09;&#xff0c;值为待求值的表达式&#xff0c;得到…

java SSM家庭财务管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM家庭财务管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

达梦到达梦的外部链接dblink(DM-DM DBLINK)

一. 使用场景&#xff1a; 部链接对象&#xff08;LINK&#xff09;是 DM 中的一种特殊的数据库实体对象&#xff0c;它记录了远程数据库的连接和路径信息&#xff0c;用于建立与远程数据的联系。通过多台数据库主库间的相互通讯&#xff0c;用户可以透明地操作远程数据库的数…

关于游戏性能优化的技巧

关于游戏性能优化的技巧 游戏性能优化对象池Jobs、Burst、多线程间隔处理定时更新全局广播缓存组件缓存常用数据2D残影优化2D骨骼转GPU动画定时器优化DrawCall合批处理优化碰撞层优化粒子特效 游戏性能优化 好久没有在CSDN上面写文章了&#xff0c;今天突然看到鬼谷工作室技术…

一篇文章带你搞定CTFMice基本操作

CTF比赛是在最短时间内拿到最多的flag&#xff0c;mice必须要有人做&#xff0c;或者一支战队必须留出一块时间专门写一些mice&#xff0c;web&#xff0c;pwn最后的一两道基本都会有难度&#xff0c;这时候就看mice的解题速度了&#xff01; 说实话&#xff0c;这是很大一块&…

【C++】bind绑定包装器全解(代码演示,例题演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…

【视觉实践】使用Mediapipe进行目标检测:杯子检测和椅子检测实践

目录 1 Mediapipe 2 Solutions 3 安装mediapipe 4 实践 1 Mediapipe Mediapipe是google的一个开源项目,可以提供开源的、跨平台的常用机器学习(machine learning,ML)方案。MediaPipe是一个用于构建机器学习管道</

Spring IoCDI

文章目录 前言什么是Spring1. 什么是 IoC 容器1.1 什么是容器1.2 什么是 IoC 2. 什么是DI IoC & DI 的使用IoC详解Bean的存储Controller注解如何获取Bean1. 根据Bean的名称获取Bean2. 根据Bean类型获取Bean3. 根据Bean名和Bean类型获取Bean Service注解Repository注解Compo…

【深度学习】序列生成模型(六):评价方法计算实例:计算ROUGE-N得分【理论到程序】

文章目录 一、BLEU-N得分&#xff08;Bilingual Evaluation Understudy&#xff09;二、ROUGE-N得分&#xff08;Recall-Oriented Understudy for Gisting Evaluation&#xff09;1. 定义2. 计算N1N2 3. 程序 给定一个生成序列“The cat sat on the mat”和两个参考序列“The c…

阿里云林立翔:基于阿里云 GPU 的 AIGC 小规模训练优化方案

云布道师 本篇文章围绕生成式 AI 技术栈、生成式 AI 微调训练和性能分析、ECS GPU 实例为生成式 AI 提供算力保障、应用场景案例等相关话题展开。 生成式 AI 技术栈介绍 1、生成式 AI 爆发的历程 在 2022 年的下半年&#xff0c;业界迎来了生成式 AI 的全面爆发&#xff0c…

【Win10安装Qt6.3】安装教程_保姆级

前言 Windows系统安装Qt4及Qt5.12之前版本和安装Qt.12之后及Qt6方法是不同的 &#xff1b;因为之前的版本提供的有安装包&#xff0c;直接一路点击Next就Ok了。但Qt5.12版本之后&#xff0c;Qt公司就不再提供安装包了&#xff0c;不论是社区版&#xff0c;专业版等&#xff0c…

SpringMVC基础知识(持续更新中~)

笔记&#xff1a; https://gitee.com/zhengguangqq/ssm-md/blob/master/ssm%20md%E6%A0%BC%E5%BC%8F%E7%AC%94%E8%AE%B0/%E4%B8%89%E3%80%81SpringMVC.md 细节补充&#xff1a;

Mac版MySQL开启服务及终端进入MySQL的基本操作

Mac版MySQL开启服务及终端进入MySQL的基本操作 一、开启mysql服务 下载完成后&#xff0c;系统偏好设置->MySQL 如图显示&#xff0c;左边是绿色的&#xff0c;右边的按键显示是Stop MySQL Server&#xff0c;说明服务已经开启 二、终端进入mysql 1.输入下面语句并回车…

05. Springboot admin集成Actuator(一)

目录 1、前言 2、Actuator监控端点 2.1、健康检查 2.2、信息端点 2.3、环境信息 2.4、度量指标 2.5、日志文件查看 2.6、追踪信息 2.7、Beans信息 2.8、Mappings信息 3、快速使用 2.1、添加依赖 2.2、添加配置文件 2.3、启动程序 4、自定义端点Endpoint 5、自定…

【数据结构和算法】---栈和队列的互相实现

目录 一、用栈实现队列1.1初始化队列1.2模拟入队列1.3模拟出队列1.4取模拟的队列头元素1.5判断队列是否为空 二、用队列实现栈2.1初始化栈2.2模拟出栈2.3模拟入栈2.4取模拟的栈顶元素2.5判读栈是否为空 一、用栈实现队列 具体题目可以参考LeetCode232. 用栈实现队列 首先要想到…

开源 AI 新秀崛起:Bittensor 更像是真正的“OpenAI”

强大的人工智能正在飞速发展&#xff0c;而完全由 OpenAI、Midjourney、Google&#xff08;Bard&#xff09;这样的少数公司控制 AI 不免让人感到担忧。在这样的背景下&#xff0c;试图用创新性解决方案处理人工智能中心化问题、权力集中于少数公司的 Bittensor&#xff0c;可谓…

HackTheBox - Medium - Linux - Jupiter

Jupiter Jupiter 是一台中等难度的 Linux 机器&#xff0c;它有一个使用 PostgreSQL 数据库的 Grafana 实例&#xff0c;该数据库在权限上过度扩展&#xff0c;容易受到 SQL 注入的影响&#xff0c;因此容易受到远程代码执行的影响。一旦站稳脚跟&#xff0c;就会注意到一个名…

【机器学习】决策树

参考课程视频&#xff1a;https://www.icourse163.org/course/NEU-1462101162?tid1471214452 1 概述 样子&#xff1a; 2 分裂 2.1 分裂原则 信息增益 信息增益比 基尼指数 3 终止 & 剪枝 3.1 终止条件 无需分裂 当前节点内样本同属一类 无法分裂 当前节点内…