SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題
前言
項目中經(jīng)常會出現(xiàn)重復(fù)提交的問題,而接口冪等性也一直以來是做任何項目都要關(guān)注的疑難點,網(wǎng)上可以查到非常多的方案,我歸納了幾點如下:
1)、數(shù)據(jù)庫層面,對責任字段設(shè)置唯一索引,這是最直接有效的方式,不好的地方就是一旦觸發(fā)就會在服務(wù)端拋數(shù)據(jù)庫相關(guān)異常;
2)、代碼層面,增加業(yè)務(wù)邏輯判斷,先查詢一遍若沒有才插入,這也是最容易想到的方式,反正寫上就對了,不好的地方就是分布式場景下依然避免不了問題;
3)、前端層面,對于觸發(fā)事件的操作比如按鈕等,最好點擊過后都設(shè)置幾秒的置灰時間,能很大程度上解決惡意提交的問題。
以上幾點經(jīng)常在項目中結(jié)合使用,不過有一種更通用的方案,就是自定義注解,寫一個專門處理這類問題的注解,之后在有需要用到的接口上直接加上這個注解即可,十分方便。
實現(xiàn)步驟
1、引入依賴
完整依賴如下:
使用的SpringBoot版本3.0之前最新的發(fā)布版本2.6.3,3.0版本要求JDK17,所以短時間內(nèi)不會在行業(yè)中普遍的,企業(yè)中成熟的版本依然是2.x。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>resubmit</artifactId> <version>0.0.1-SNAPSHOT</version> <name>resubmit</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- spring web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring aop 實現(xiàn)自定義注解用到 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- spring processor 加載項目配置使用,也可以不用,但IDEA配置類頂端會有警告 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- spring jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mysql 不填寫版本號默認是8.0以上 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- springData redis 不填寫版本號默認和springboot版本一致 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redission 注意版本號和springData-redis要對應(yīng) 這里26對應(yīng)springData-redis的2.6版本 --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-data-26</artifactId> <version>3.16.8</version> </dependency> <!-- fastjson 解析json用到,也可以換成自己喜歡用的 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <!-- mybatis-plus 參考官網(wǎng),目前是最新版 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <!-- 代碼生成器 mybatisPlus自帶的生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.1</version> </dependency> <!-- freemarker模板生成器 引入代碼生成器需要 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <!-- swagger 因為mybatisPlus代碼生成器會自帶swagger的注解 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> <!-- lombok 因為mybatisPlus代碼生成器會自帶lombok的注解 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2、配置application.yml
這里注意,生產(chǎn)環(huán)境項目是要區(qū)分application.yml、application-dev.yml、application-test.yml、application-prod.yml的,這里只是實現(xiàn)功能的小案例,就只寫了這一個。
# 端口,改成自己的。 server: port: 8888 # 數(shù)據(jù)源,使用hikari,現(xiàn)在一般項目都是默認的這個數(shù)據(jù)源了,更詳細的配置可以百度。 spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/resubmit_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false username: root # 換成自己數(shù)據(jù)庫的賬號 password: 123456 # 換成自己數(shù)據(jù)庫的密碼 # redis配置,換成自己的。 redis: database: 11 host: 192.168.1.197 port: 6379 password: 123456 jedis: pool: max-active: 1000 max-wait: -1ms max-idle: 50 min-idle: 1 # redission配置,這里直接讀取的redis變量. redisson: singleserverconfig: address: "redis://${spring.redis.host}:${spring.redis.port}" password: ${spring.redis.password} database: ${spring.redis.database}
3、編寫配置類
1)、redis配置類
package com.example.resubmit.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; 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.*; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * <p> * redis配置類 * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-02-08 */ @Configuration public class RedisConfig { @Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate redisTemplate = new StringRedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
2)、Redisson配置類
package com.example.resubmit.config; import com.fasterxml.jackson.core.JsonProcessingException; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; /** * <p> * redission配置類 * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-02-08 */ @Configuration @ConfigurationProperties(prefix = "redisson.singleserverconfig") public class RedissonSpringDataConfig { private static final Logger log = LoggerFactory.getLogger(RedissonSpringDataConfig.class); private String address; private int database; private String password; @Bean public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) { return new RedissonConnectionFactory(redisson); } @Bean(destroyMethod = "shutdown") public RedissonClient redisson() throws JsonProcessingException { log.debug("[RedissonSpringDataConfig][redisson]>>>> address: {}, database: {}, password: {}", address, database, password); Config config = new Config(); SingleServerConfig sconfig= config.useSingleServer() .setAddress(address) .setDatabase(database); // 如果redis設(shè)置了密碼,這里不設(shè)置密碼就會報“org.redisson.client.RedisAuthRequiredException: NOAUTH Authentication required”錯誤。 if(StringUtils.hasText(password)){ sconfig.setPassword(password); } return Redisson.create(config); } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
4、MybatisPlus代碼生成器
這個生成器我參考官網(wǎng)的做了優(yōu)化和注釋,一看就能明白。
package com.example.resubmit.generator; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.Collections; /** * @作者: 福隆苑居士,公眾號:【Java分享客?!? * @日期: 2022/2/8 20:51 * @描述: mybatis-plus代碼生成器 */ public class CodeGenerator { private static final String url = "jdbc:mysql://localhost:3306/resubmit_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false"; private static final String username = "root"; private static final String password = "123456"; private static final String outputDir = "D:\workspace\workspace_java\resubmit\src\main\java"; // entity、mapper、service、controller生成的目錄地址,換成自己項目的。 private static final String xmlOutputDir = "D:\workspace\workspace_java\resubmit\src\main\resources\mapper"; // xxMapper.xml生成的目錄地址,換成自己項目的。 public static void main(String[] args) { FastAutoGenerator.create(url, username, password) .globalConfig(builder -> { builder.author("福隆苑居士,公眾號:【9i分享客棧】") // 設(shè)置作者 .enableSwagger() // 開啟 swagger 模式 .fileOverride() // 覆蓋已生成文件 .outputDir(outputDir); // 指定輸出目錄 }) .packageConfig(builder -> { builder.parent("com.example.resubmit") // 設(shè)置父包名,和自己項目的父包名一致即可。 .moduleName("") // 設(shè)置父包模塊名,為空就會直接生成在父包名目錄下。 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, xmlOutputDir)); // 設(shè)置mapperXml生成路徑 }) .strategyConfig(builder -> { builder.addInclude("tb_user") // 設(shè)置需要生成的表名,多個用逗號隔開。 .addTablePrefix("t_", "tb_", "c_"); // 設(shè)置過濾表前綴,多個用逗號隔開。 }) .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板 .execute(); } }
5、定義響應(yīng)實體
這個因人而定,可以自己定義,也可以直接用我的。
package com.example.resubmit.util; import com.example.resubmit.enums.ResponseCodeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; /** * <p> * 自定義響應(yīng)結(jié)果 * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-02-08 */ public class ResultEntity<T> { private String code; private String msg; private T data; public ResultEntity(){} public ResultEntity(String code, String msg){ this.code = code; this.msg = msg; } public ResultEntity(String code, String msg, T data){ this.code = code; this.msg = msg; this.data = data; } @JsonIgnore public boolean isSuccess() { return ResponseCodeEnum.SUCCESS.getCode().equals(this.getCode()); } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static ResultEntity fail(String code, String msg) { return new ResultEntity(code, msg); } public static <T> ResultEntity fail(String code, String msg, T data) { return new ResultEntity(code, msg, data); } public static ResultEntity ok(String code, String msg) { return new ResultEntity(code, msg); } public static <T> ResultEntity ok(String code, String msg, T data) { return new ResultEntity(code, msg, data); } public static ResultEntity ok(String msg) { return new ResultEntity(ResponseCodeEnum.SUCCESS.getCode(), msg); } public static <T> ResultEntity ok(String msg, T data) { return new ResultEntity(ResponseCodeEnum.SUCCESS.getCode(), msg, data); } public static ResultEntity fail(String msg) { return new ResultEntity(ResponseCodeEnum.FAIL.getCode(), msg); } }
6、Redisson工具類
package com.example.resubmit.redission; import org.redisson.Redisson; import org.redisson.api.RLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * <p> * 加鎖解鎖工具類 * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-02-08 */ @Component public class RedisLock { private static final Logger log = LoggerFactory.getLogger(RedisLock.class); // todo 待優(yōu)化,最好使用自定義的線程池,自定義工作隊列和最大線程數(shù)。 private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(4); @Resource private Redisson redisson; /** * Redission獲取鎖 * * @param lockKey 鎖名 * @param uuid 唯一標識 * @param delaySeconds 過期時間 * @param unit 單位 * @return 是否獲取成功 */ public boolean Rlock(String lockKey, final String uuid, long delaySeconds, final TimeUnit unit) { RLock rLock = redisson.getLock(lockKey); boolean success = false; try { // log.debug("===lock thread id is :{}", Thread.currentThread().getId()); success = rLock.tryLock(0, delaySeconds, unit); } catch (InterruptedException e) { log.error("[RedisLock][Rlock]>>>> 加鎖異常: ", e); } return success; } /** * Redission釋放鎖 * * @param lockKey 鎖名 */ public void Runlock(String lockKey) { RLock rLock = redisson.getLock(lockKey); log.debug("[RedisLock][Rlock]>>>> {}, status: {} === unlock thread id is: {}", rLock.isHeldByCurrentThread(), rLock.isLocked(), Thread.currentThread().getId()); rLock.unlock(); } /** * Redission延遲釋放鎖 * * @param lockKey 鎖名 * @param delayTime 延遲時間 * @param unit 單位 */ public void delayUnlock(final String lockKey, long delayTime, TimeUnit unit) { if (!StringUtils.hasText(lockKey)) { return; } if (delayTime <= 0) { Runlock(lockKey); } else { EXECUTOR_SERVICE.schedule(() -> Runlock(lockKey), delayTime, unit); } } }
7、自定義注解
這里增加了延遲時間的屬性,默認8秒。
package com.example.resubmit.redission; import java.lang.annotation.*; /** * <p> * 防重復(fù)提交注解 * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-02-08 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NotResubmit { /** * 延時時間 在延時多久后可以再次提交,默認8秒 * @return 秒 */ int delaySeconds() default 8; }
8、防重注解的實現(xiàn)
這里是實現(xiàn)防重注解的核心代碼,這里append實體屬性時用到了toString()方法,所以要求我們生成的實體對象一定要有toString()方法。
package com.example.resubmit.redission; import com.example.resubmit.enums.ResponseCodeEnum; import com.example.resubmit.util.ResultEntity; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.DigestUtils; import javax.annotation.Resource; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; /** * <p> * 防重復(fù)提交注解的實現(xiàn),使用AOP。 * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-02-08 */ @Aspect @Component public class LockMethodAOP { private static final Logger log = LoggerFactory.getLogger(LockMethodAOP.class); @Resource private RedisLock redisLock; /** * 這里注意,我的注解寫在同一個包下所以沒有包名,如果換自己的目錄,要改為@annotation(com.xxx.NotResubmit)加上完整包名. */ @Around("execution(public * *(..)) && @annotation(NotResubmit)") public Object interceptor(ProceedingJoinPoint pjp) throws Throwable { // 獲取到這個注解 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); NotResubmit lock = method.getAnnotation(NotResubmit.class); final String lockKey = generateKey(pjp); // 上鎖 final boolean success = redisLock.Rlock(lockKey, null, lock.delaySeconds(), TimeUnit.SECONDS); if (!success) { // 這里也可以改為自己項目自定義的異常拋出 return ResponseEntity.badRequest().body(ResultEntity.fail(ResponseCodeEnum.FAIL.getCode(), "操作太頻繁")); } return pjp.proceed(); } private String generateKey(ProceedingJoinPoint pjp) { StringBuilder sb = new StringBuilder(); Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); sb.append(pjp.getTarget().getClass().getName())//類名 .append(method.getName());//方法名 for (Object o : pjp.getArgs()) { sb.append(o.toString()); } return DigestUtils.md5DigestAsHex(sb.toString().getBytes(Charset.defaultCharset())); } }
9、編寫控制器
這里在插入方法上加了@NotResubmit(delaySeconds = 10)注解,表示這個插入方法執(zhí)行時,10秒內(nèi)不允許重復(fù)提交,10秒后才可以插入成功,這個時間可以根據(jù)需要在不同的接口上自行修改。
package com.example.resubmit.controller; import com.example.resubmit.entity.User; import com.example.resubmit.redission.NotResubmit; import com.example.resubmit.service.IUserService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.stereotype.Controller; import java.time.LocalDateTime; import java.util.List; /** * <p> * 控制器 * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-02-08 */ @Controller @RequestMapping("/api/user") public class UserController { private final IUserService userService; public UserController(IUserService userService) { this.userService = userService; } /** * 查詢列表 * @return 結(jié)果 */ @GetMapping("/list") public ResponseEntity<List<User>> list() { return ResponseEntity.ok().body(userService.list()); } /** * 插入記錄 * @return 結(jié)果 */ @NotResubmit(delaySeconds = 10) @PostMapping("/insert") public ResponseEntity<List<User>> insert(@RequestBody User user) { // 插入 user.setCreatedAt(LocalDateTime.now()); user.setCreatedBy("冰敦敦"); user.setUpdatedAt(LocalDateTime.now()); user.setUpdatedBy("冰敦敦"); userService.save(user); // 返回列表 return ResponseEntity.ok().body(userService.list()); } }
10、效果
1)、執(zhí)行接口插入一條記錄
2)、連續(xù)點擊看注解是否生效
可以發(fā)現(xiàn),會返回操作太頻繁的提示,并沒有插入數(shù)據(jù)庫。
3)、等待10秒過后再執(zhí)行
發(fā)現(xiàn)又可以插入進去了
總結(jié)
通過最終效果可以發(fā)現(xiàn),自定義的防重注解實現(xiàn)起來并沒有那么難,核心思想如下:
把實體類的屬性append并進行簽名,作為redisson加鎖的key,在aop攔截到使用這個注解的接口方法時,就會根據(jù)傳入的對象和上一次提交時傳入的對象進行屬性簽名的匹配,只要完全一致,代表是重復(fù)提交,只有在超過延遲時間后才能成功通過攔截,最終執(zhí)行業(yè)務(wù)。
這也是選取整合mybatisPlus的原因,因為它的代碼生成器會自動生成帶有toString()方法的代碼,如果不想使用,直接用lombok注解也可以。
最后,你其實可以發(fā)現(xiàn),掌握了方法后,這個注解不僅可以拿來做防重,進行優(yōu)化改造后還可以用來做接口限流,是不是很有意思呢。
到此這篇關(guān)于SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題 的文章就介紹到這了,更多相關(guān)SpringBoot Redisson重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java畢業(yè)設(shè)計實戰(zhàn)之教室預(yù)訂管理系統(tǒng)的實現(xiàn)
這是一個使用了java+SpringBoot+Maven+Vue+mysql開發(fā)的教室預(yù)訂管理系統(tǒng),是一個畢業(yè)設(shè)計的實戰(zhàn)練習,具有教室預(yù)訂管理該有的所有功能,感興趣的朋友快來看看吧2022-02-02SpringBoot項目中@RestControllerAdvice全局異常失效問題的解決
@RestController注解是一個用于定義RESTful Web服務(wù)的控制器的特殊注解,它是@Controller和@ResponseBody注解的結(jié)合體,意味著你不需要在每個處理請求的方法上都添加@ResponseBody,本文給大家介紹了解決SpringBoot項目中@RestControllerAdvice全局異常失效問題2024-11-11Java中ShardingSphere分庫分表實戰(zhàn)
我們做項目的時候,數(shù)據(jù)量比較大,單表千萬級別的,需要分庫分表,本文主要介紹了Java中ShardingSphere分庫分表實戰(zhàn),感興趣的可以了解一下2021-09-09詳解Java?POI?excel自定義設(shè)置單元格格式
這篇文章主要介紹了Java?POI?excel設(shè)置單元格格式,自定義設(shè)置,設(shè)置單元格格式:來源_formats,更多數(shù)據(jù)類型從formats里面發(fā)現(xiàn),需要的朋友可以參考下2024-01-01JAVA(SpringBoot)集成Jasypt進行加密、解密功能
Jasypt是一個Java庫,專門用于簡化加密和解密操作,提供多種加密算法支持,集成到SpringBoot等框架中,通過使用Jasypt,可以有效保護配置文件中的敏感信息,如數(shù)據(jù)庫密碼等,避免被未授權(quán)訪問,Jasypt還支持自定義加密器,提高擴展性和安全性,適用于各種需要加密保護應(yīng)用場景2024-09-09