百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis高效实现点赞、取消点赞只需这四步

mhr18 2024-11-02 11:58 25 浏览 0 评论

来自:掘金,作者:solocoder
本文链接:https://mp.weixin.qq.com/s/v4R6PuMjDJxlqyakw1QgLg

本文基于 SpringCloud, 用户发起点赞、取消点赞后先存入 Redis 中,再每隔两小时从 Redis 读取点赞数据写入数据库中做持久化存储。

点赞功能在很多系统中都有,但别看功能小,想要做好需要考虑的东西还挺多的。

点赞、取消点赞是高频次的操作,若每次都读写数据库,大量的操作会影响数据库性能,所以需要做缓存。

至于多久从 Redis 取一次数据存到数据库中,根据项目的实际情况定吧,我是暂时设了两个小时。

项目需求需要查看都谁点赞了,所以要存储每个点赞的点赞人、被点赞人,不能简单的做计数。

文章分四部分介绍:

  • Redis 缓存设计及实现
  • 数据库设计
  • 数据库操作
  • 开启定时任务持久化存储到数据库


一、Redis 缓存设计及实现


1.1 Redis 安装及运行

Redis 安装请自行查阅相关教程。

说下Docker 安装运行 Redis

docker?run?-d?-p?6379:6379?redis:4.0.8

如果已经安装了 Redis,打开命令行,输入启动 Redis 的命令

redis-server

1.2 Redis 与 SpringBoot 项目的整合

1.在 pom.xml 中引入依赖

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

2.在启动类上添加注释 @EnableCaching

@SpringBootApplication
@EnableDiscoveryClient
@EnableSwagger2
@EnableFeignClients(basePackages?=?"com.solo.coderiver.project.client")
@EnableCaching
public?class?UserApplication?{

????public?static?void?main(String[]?args)?{
????????SpringApplication.run(UserApplication.class,?args);
????}
}

3.编写 Redis 配置类 RedisConfig

import?com.fasterxml.jackson.annotation.JsonAutoDetect;
import?com.fasterxml.jackson.annotation.PropertyAccessor;
import?com.fasterxml.jackson.databind.ObjectMapper;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.data.redis.connection.RedisConnectionFactory;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.data.redis.core.StringRedisTemplate;
import?org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import?java.net.UnknownHostException;


@Configuration
public?class?RedisConfig?{

????@Bean
????@ConditionalOnMissingBean(name?=?"redisTemplate")
????public?RedisTemplate<String,?Object>?redisTemplate(
????????????RedisConnectionFactory?redisConnectionFactory)
????????????throws?UnknownHostException?{

????????Jackson2JsonRedisSerializer<Object>?jackson2JsonRedisSerializer?=?new?Jackson2JsonRedisSerializer<Object>(Object.class);
????????ObjectMapper?om?=?new?ObjectMapper();
????????om.setVisibility(PropertyAccessor.ALL,?JsonAutoDetect.Visibility.ANY);
????????om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
????????jackson2JsonRedisSerializer.setObjectMapper(om);

????????RedisTemplate<String,?Object>?template?=?new?RedisTemplate<String,?Object>();
????????template.setConnectionFactory(redisConnectionFactory);
????????template.setKeySerializer(jackson2JsonRedisSerializer);
????????template.setValueSerializer(jackson2JsonRedisSerializer);
????????template.setHashKeySerializer(jackson2JsonRedisSerializer);
????????template.setHashValueSerializer(jackson2JsonRedisSerializer);
????????template.afterPropertiesSet();
????????return?template;
????}


????@Bean
????@ConditionalOnMissingBean(StringRedisTemplate.class)
????public?StringRedisTemplate?stringRedisTemplate(
????????????RedisConnectionFactory?redisConnectionFactory)
????????????throws?UnknownHostException?{
????????StringRedisTemplate?template?=?new?StringRedisTemplate();
????????template.setConnectionFactory(redisConnectionFactory);
????????return?template;
????}
}

至此 Redis 在 SpringBoot 项目中的配置已经完成,可以愉快的使用了。

1.3 Redis 的数据结构类型

Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。

下面来对这5种数据结构类型作简单的介绍:

1.4 点赞数据在 Redis 中的存储格式

用 Redis 存储两种数据,一种是记录点赞人、被点赞人、点赞状态的数据,另一种是每个用户被点赞了多少次,做个简单的计数。

由于需要记录点赞人和被点赞人,还有点赞状态(点赞、取消点赞),还要固定时间间隔取出 Redis 中所有点赞数据,分析了下 Redis 数据格式中 Hash 最合适。

因为 Hash 里的数据都是存在一个键里,可以通过这个键很方便的把所有的点赞数据都取出。这个键里面的数据还可以存成键值对的形式,方便存入点赞人、被点赞人和点赞状态。

设点赞人的 id 为 likedPostId,被点赞人的 id 为 likedUserId ,点赞时状态为 1,取消点赞状态为 0。将点赞人 id 和被点赞人 id 作为键,两个 id 中间用 :: 隔开,点赞状态作为值。

所以如果用户点赞,存储的键为:likedUserId::likedPostId,对应的值为 1 。取消点赞,存储的键为:likedUserId::likedPostId,对应的值为 0 。取数据时把键用 :: 切开就得到了两个id,也很方便。

在可视化工具 RDM 中看到的是这样子

1.5 操作 Redis

将具体操作方法封装到了 RedisService 接口里

RedisService.java

import?com.solo.coderiver.user.dataobject.UserLike;
import?com.solo.coderiver.user.dto.LikedCountDTO;

import?java.util.List;

public?interface?RedisService?{

????/**
?????*?点赞。状态为1
?????*?@param?likedUserId
?????*?@param?likedPostId
?????*/
????void?saveLiked2Redis(String?likedUserId,?String?likedPostId);

????/**
?????*?取消点赞。将状态改变为0
?????*?@param?likedUserId
?????*?@param?likedPostId
?????*/
????void?unlikeFromRedis(String?likedUserId,?String?likedPostId);

????/**
?????*?从Redis中删除一条点赞数据
?????*?@param?likedUserId
?????*?@param?likedPostId
?????*/
????void?deleteLikedFromRedis(String?likedUserId,?String?likedPostId);

????/**
?????*?该用户的点赞数加1
?????*?@param?likedUserId
?????*/
????void?incrementLikedCount(String?likedUserId);

????/**
?????*?该用户的点赞数减1
?????*?@param?likedUserId
?????*/
????void?decrementLikedCount(String?likedUserId);

????/**
?????*?获取Redis中存储的所有点赞数据
?????*?@return
?????*/
????List<UserLike>?getLikedDataFromRedis();

????/**
?????*?获取Redis中存储的所有点赞数量
?????*?@return
?????*/
????List<LikedCountDTO>?getLikedCountFromRedis();

}

实现类 RedisServiceImpl.java

import?com.solo.coderiver.user.dataobject.UserLike;
import?com.solo.coderiver.user.dto.LikedCountDTO;
import?com.solo.coderiver.user.enums.LikedStatusEnum;
import?com.solo.coderiver.user.service.LikedService;
import?com.solo.coderiver.user.service.RedisService;
import?com.solo.coderiver.user.utils.RedisKeyUtils;
import?lombok.extern.slf4j.Slf4j;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.redis.core.Cursor;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.data.redis.core.ScanOptions;
import?org.springframework.stereotype.Service;

import?java.util.ArrayList;
import?java.util.List;
import?java.util.Map;

@Service
@Slf4j
public?class?RedisServiceImpl?implements?RedisService?{

????@Autowired
????RedisTemplate?redisTemplate;

????@Autowired
????LikedService?likedService;

????@Override
????public?void?saveLiked2Redis(String?likedUserId,?String?likedPostId)?{
????????String?key?=?RedisKeyUtils.getLikedKey(likedUserId,?likedPostId);
????????redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED,?key,?LikedStatusEnum.LIKE.getCode());
????}

????@Override
????public?void?unlikeFromRedis(String?likedUserId,?String?likedPostId)?{
????????String?key?=?RedisKeyUtils.getLikedKey(likedUserId,?likedPostId);
????????redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED,?key,?LikedStatusEnum.UNLIKE.getCode());
????}

????@Override
????public?void?deleteLikedFromRedis(String?likedUserId,?String?likedPostId)?{
????????String?key?=?RedisKeyUtils.getLikedKey(likedUserId,?likedPostId);
????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED,?key);
????}

????@Override
????public?void?incrementLikedCount(String?likedUserId)?{
????????redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,?likedUserId,?1);
????}

????@Override
????public?void?decrementLikedCount(String?likedUserId)?{
????????redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,?likedUserId,?-1);
????}

????@Override
????public?List<UserLike>?getLikedDataFromRedis()?{
????????Cursor<Map.Entry<Object,?Object>>?cursor?=?redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED,?ScanOptions.NONE);
????????List<UserLike>?list?=?new?ArrayList<>();
????????while?(cursor.hasNext()){
????????????Map.Entry<Object,?Object>?entry?=?cursor.next();
????????????String?key?=?(String)?entry.getKey();
????????????//分离出?likedUserId,likedPostId
????????????String[]?split?=?key.split("::");
????????????String?likedUserId?=?split[0];
????????????String?likedPostId?=?split[1];
????????????Integer?value?=?(Integer)?entry.getValue();

????????????//组装成?UserLike?对象
????????????UserLike?userLike?=?new?UserLike(likedUserId,?likedPostId,?value);
????????????list.add(userLike);

????????????//存到?list?后从?Redis?中删除
????????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED,?key);
????????}

????????return?list;
????}

????@Override
????public?List<LikedCountDTO>?getLikedCountFromRedis()?{
????????Cursor<Map.Entry<Object,?Object>>?cursor?=?redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,?ScanOptions.NONE);
????????List<LikedCountDTO>?list?=?new?ArrayList<>();
????????while?(cursor.hasNext()){
????????????Map.Entry<Object,?Object>?map?=?cursor.next();
????????????//将点赞数量存储在?LikedCountDT
????????????String?key?=?(String)map.getKey();
????????????LikedCountDTO?dto?=?new?LikedCountDTO(key,?(Integer)?map.getValue());
????????????list.add(dto);
????????????//从Redis中删除这条记录
????????????redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,?key);
????????}
????????return?list;
????}
}

用到的工具类和枚举类

RedisKeyUtils, 用于根据一定规则生成 key

public?class?RedisKeyUtils?{

????//保存用户点赞数据的key
????public?static?final?String?MAP_KEY_USER_LIKED?=?"MAP_USER_LIKED";
????//保存用户被点赞数量的key
????public?static?final?String?MAP_KEY_USER_LIKED_COUNT?=?"MAP_USER_LIKED_COUNT";

????/**
?????*?拼接被点赞的用户id和点赞的人的id作为key。格式 222222::333333
?????*?@param?likedUserId?被点赞的人id
?????*?@param?likedPostId?点赞的人的id
?????*?@return
?????*/
????public?static?String?getLikedKey(String?likedUserId,?String?likedPostId){
????????StringBuilder?builder?=?new?StringBuilder();
????????builder.append(likedUserId);
????????builder.append("::");
????????builder.append(likedPostId);
????????return?builder.toString();
????}
}

LikedStatusEnum 用户点赞状态的枚举类

package?com.solo.coderiver.user.enums;

import?lombok.Getter;

/**
?*?用户点赞的状态
?*/
@Getter
public?enum?LikedStatusEnum?{
????LIKE(1,?"点赞"),
????UNLIKE(0,?"取消点赞/未点赞"),
????;

????private?Integer?code;

????private?String?msg;

????LikedStatusEnum(Integer?code,?String?msg)?{
????????this.code?=?code;
????????this.msg?=?msg;
????}
}


二、数据库设计


数据库表中至少要包含三个字段:被点赞用户id,点赞用户id,点赞状态。再加上主键id,创建时间,修改时间就行了。

建表语句

create?table?`user_like`(
????`id`?int?not?null?auto_increment,
????`liked_user_id`?varchar(32)?not?null?comment?'被点赞的用户id',
????`liked_post_id`?varchar(32)?not?null?comment?'点赞的用户id',
????`status`?tinyint(1)?default?'1'?comment?'点赞状态,0取消,1点赞',
????`create_time`?timestamp?not?null?default?current_timestamp?comment?'创建时间',
??`update_time`?timestamp?not?null?default?current_timestamp?on?update?current_timestamp?comment?'修改时间',
????primary?key(`id`),
????INDEX?`liked_user_id`(`liked_user_id`),
????INDEX?`liked_post_id`(`liked_post_id`)
)?comment?'用户点赞表';

对应的对象 UserLike

import?com.solo.coderiver.user.enums.LikedStatusEnum;
import?lombok.Data;

import?javax.persistence.Entity;
import?javax.persistence.GeneratedValue;
import?javax.persistence.GenerationType;
import?javax.persistence.Id;

/**
?*?用户点赞表
?*/
@Entity
@Data
public?class?UserLike?{

????//主键id
????@Id
????@GeneratedValue(strategy?=?GenerationType.IDENTITY)
????private?Integer?id;

????//被点赞的用户的id
????private?String?likedUserId;

????//点赞的用户的id
????private?String?likedPostId;

????//点赞的状态.默认未点赞
????private?Integer?status?=?LikedStatusEnum.UNLIKE.getCode();

????public?UserLike()?{
????}

????public?UserLike(String?likedUserId,?String?likedPostId,?Integer?status)?{
????????this.likedUserId?=?likedUserId;
????????this.likedPostId?=?likedPostId;
????????this.status?=?status;
????}
}


三、数据库操作


操作数据库同样封装在接口中

LikedService

import?com.solo.coderiver.user.dataobject.UserLike;
import?org.springframework.data.domain.Page;
import?org.springframework.data.domain.Pageable;

import?java.util.List;

public?interface?LikedService?{

????/**
?????*?保存点赞记录
?????*?@param?userLike
?????*?@return
?????*/
????UserLike?save(UserLike?userLike);

????/**
?????*?批量保存或修改
?????*?@param?list
?????*/
????List<UserLike>?saveAll(List<UserLike>?list);


????/**
?????*?根据被点赞人的id查询点赞列表(即查询都谁给这个人点赞过)
?????*?@param?likedUserId?被点赞人的id
?????*?@param?pageable
?????*?@return
?????*/
????Page<UserLike>?getLikedListByLikedUserId(String?likedUserId,?Pageable?pageable);

????/**
?????*?根据点赞人的id查询点赞列表(即查询这个人都给谁点赞过)
?????*?@param?likedPostId
?????*?@param?pageable
?????*?@return
?????*/
????Page<UserLike>?getLikedListByLikedPostId(String?likedPostId,?Pageable?pageable);

????/**
?????*?通过被点赞人和点赞人id查询是否存在点赞记录
?????*?@param?likedUserId
?????*?@param?likedPostId
?????*?@return
?????*/
????UserLike?getByLikedUserIdAndLikedPostId(String?likedUserId,?String?likedPostId);

????/**
?????*?将Redis里的点赞数据存入数据库中
?????*/
????void?transLikedFromRedis2DB();

????/**
?????*?将Redis中的点赞数量数据存入数据库
?????*/
????void?transLikedCountFromRedis2DB();

}

LikedServiceImpl 实现类

import?com.solo.coderiver.user.dataobject.UserInfo;
import?com.solo.coderiver.user.dataobject.UserLike;
import?com.solo.coderiver.user.dto.LikedCountDTO;
import?com.solo.coderiver.user.enums.LikedStatusEnum;
import?com.solo.coderiver.user.repository.UserLikeRepository;
import?com.solo.coderiver.user.service.LikedService;
import?com.solo.coderiver.user.service.RedisService;
import?com.solo.coderiver.user.service.UserService;
import?lombok.extern.slf4j.Slf4j;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.domain.Page;
import?org.springframework.data.domain.Pageable;
import?org.springframework.stereotype.Service;
import?org.springframework.transaction.annotation.Transactional;

import?java.util.List;

@Service
@Slf4j
public?class?LikedServiceImpl?implements?LikedService?{

????@Autowired
????UserLikeRepository?likeRepository;

????@Autowired
????RedisService?redisService;

????@Autowired
????UserService?userService;

????@Override
????@Transactional
????public?UserLike?save(UserLike?userLike)?{
????????return?likeRepository.save(userLike);
????}

????@Override
????@Transactional
????public?List<UserLike>?saveAll(List<UserLike>?list)?{
????????return?likeRepository.saveAll(list);
????}

????@Override
????public?Page<UserLike>?getLikedListByLikedUserId(String?likedUserId,?Pageable?pageable)?{
????????return?likeRepository.findByLikedUserIdAndStatus(likedUserId,?LikedStatusEnum.LIKE.getCode(),?pageable);
????}

????@Override
????public?Page<UserLike>?getLikedListByLikedPostId(String?likedPostId,?Pageable?pageable)?{
????????return?likeRepository.findByLikedPostIdAndStatus(likedPostId,?LikedStatusEnum.LIKE.getCode(),?pageable);
????}

????@Override
????public?UserLike?getByLikedUserIdAndLikedPostId(String?likedUserId,?String?likedPostId)?{
????????return?likeRepository.findByLikedUserIdAndLikedPostId(likedUserId,?likedPostId);
????}

????@Override
????@Transactional
????public?void?transLikedFromRedis2DB()?{
????????List<UserLike>?list?=?redisService.getLikedDataFromRedis();
????????for?(UserLike?like?:?list)?{
????????????UserLike?ul?=?getByLikedUserIdAndLikedPostId(like.getLikedUserId(),?like.getLikedPostId());
????????????if?(ul?==?null){
????????????????//没有记录,直接存入
????????????????save(like);
????????????}else{
????????????????//有记录,需要更新
????????????????ul.setStatus(like.getStatus());
????????????????save(ul);
????????????}
????????}
????}

????@Override
????@Transactional
????public?void?transLikedCountFromRedis2DB()?{
????????List<LikedCountDTO>?list?=?redisService.getLikedCountFromRedis();
????????for?(LikedCountDTO?dto?:?list)?{
????????????UserInfo?user?=?userService.findById(dto.getId());
????????????//点赞数量属于无关紧要的操作,出错无需抛异常
????????????if?(user?!=?null){
????????????????Integer?likeNum?=?user.getLikeNum()?+?dto.getCount();
????????????????user.setLikeNum(likeNum);
????????????????//更新点赞数量
????????????????userService.updateInfo(user);
????????????}
????????}
????}
}

数据库的操作就这些,主要还是增删改查。

四、开启定时任务持久化存储到数据库


定时任务 Quartz 很强大,就用它了。

Quartz 使用步骤:

1.添加依赖

<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.编写配置文件

package?com.solo.coderiver.user.config;

import?com.solo.coderiver.user.task.LikeTask;
import?org.quartz.*;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;

@Configuration
public?class?QuartzConfig?{

????private?static?final?String?LIKE_TASK_IDENTITY?=?"LikeTaskQuartz";

????@Bean
????public?JobDetail?quartzDetail(){
????????return?JobBuilder.newJob(LikeTask.class).withIdentity(LIKE_TASK_IDENTITY).storeDurably().build();
????}

????@Bean
????public?Trigger?quartzTrigger(){
????????SimpleScheduleBuilder?scheduleBuilder?=?SimpleScheduleBuilder.simpleSchedule()
//????????????????.withIntervalInSeconds(10)??//设置时间周期单位秒
????????????????.withIntervalInHours(2)??//两个小时执行一次
????????????????.repeatForever();
????????return?TriggerBuilder.newTrigger().forJob(quartzDetail())
????????????????.withIdentity(LIKE_TASK_IDENTITY)
????????????????.withSchedule(scheduleBuilder)
????????????????.build();
????}
}

3.编写执行任务的类继承自 QuartzJobBean

package?com.solo.coderiver.user.task;

import?com.solo.coderiver.user.service.LikedService;
import?lombok.extern.slf4j.Slf4j;
import?org.apache.commons.lang.time.DateUtils;
import?org.quartz.JobExecutionContext;
import?org.quartz.JobExecutionException;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.scheduling.quartz.QuartzJobBean;

import?java.text.SimpleDateFormat;
import?java.util.Date;

/**
?*?点赞的定时任务
?*/
@Slf4j
public?class?LikeTask?extends?QuartzJobBean?{

????@Autowired
????LikedService?likedService;

????private?SimpleDateFormat?sdf?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss");

????@Override
????protected?void?executeInternal(JobExecutionContext?jobExecutionContext)?throws?JobExecutionException?{

????????log.info("LikeTask--------?{}",?sdf.format(new?Date()));

????????//将?Redis?里的点赞信息同步到数据库里
????????likedService.transLikedFromRedis2DB();
????????likedService.transLikedCountFromRedis2DB();
????}
}

在定时任务中直接调用 LikedService 封装的方法完成数据同步。

以上就是点赞功能的设计与实现,不足之处还请各位大佬多多指教。

另外,点赞/取消点赞 跟 点赞数 +1/ -1 应该保证是原子操作 , 不然出现并发问题就会有两条重复的点赞记录 , 所以要给整个原子操作加锁 . 同时需要在Spring Boot 的系统关闭钩子函数中补充同步redis中点赞数据到mysql中的过程 . 不然有可能出现距离上一次同步1小时59分的时候服务器更新 , 把整整两小时的点赞数据都给清空了 . 如果点赞设计到比较重要活动业务的话这就很尴尬了 .

如有更好的实现方案欢迎在评论区交流…

链接:https://juejin.im/post/5bdc257e6fb9a049ba410098


想了解更多关于数据库、云技术的内容吗?

快来关注“数据和云"、"云和恩墨,"公众号及"云和恩墨"官方网站,我们期待大家一同学习与进步!

小程序”DBASK“在线问答,随时解惑,欢迎了解和关注!

相关推荐

如何检查 Linux 服务器是物理服务器还是虚拟服务器?

在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...

第四节 Windows 系统 Docker 安装全指南

一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...

C++ std:shared_ptr自定义allocator引入内存池

当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...

Activiti 8.0.0 发布,业务流程管理与工作流系统

Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...

MyBatis动态SQL的5种高级玩法,90%的人只用过3种

MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...

Springboot数据访问(整合Mybatis Plus)

Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...

盘点金州勇士在奥克兰13年的13大球星 满满的全是...

见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...

Mybatis入门看这一篇就够了(mybatis快速入门)

什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...

Springboot数据访问(整合druid数据源)

Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...

Linux 中的 &quot;/etc/profile.d&quot; 目录有什么作用 ?

什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...

企业数据库安全管理规范(企业数据库安全管理规范最新版)

1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...

Oracle 伪列!这些隐藏用法你都知道吗?

在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...

Oracle 高效处理数据的隐藏神器:临时表妙用

各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...

Oracle 数据库查询:多表查询(oracle多表关联查询)

一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...

一文掌握怎么利用Shell+Python实现多数据源的异地备份程序

简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...

取消回复欢迎 发表评论: