Java中實現(xiàn)訂單超時自動取消功能(最新推薦)
在開發(fā)中,我們會遇到需要延時任務(wù)的業(yè)務(wù)場景,例如:用戶下單之后未在規(guī)定的時間內(nèi)支付成功,該訂單會自動取消; 用戶注冊成功15分鐘后,發(fā)消息通知用戶;還有比如到期自動收貨,超時自動退款等都是類似的延時任務(wù)的業(yè)務(wù)問題。
這里主要介紹一下幾種方法:
- 1、定時任務(wù)
- 2、JDK延遲隊列 DelayQueue
- 3、redis過期監(jiān)聽
- 4、Redisson分布式延遲隊列
- 5、RocketMQ延遲消息
- 6、RabbitMQ死信隊列
1、定時任務(wù)
寫一個定時任務(wù),定期掃描數(shù)據(jù)庫中的訂單,如果時間過期,就取消這個訂單。這種實現(xiàn)方法成本低、實現(xiàn)容易。這里使用@Scheduled注解實現(xiàn),也可以用Quartz框架實現(xiàn)定時任務(wù)。
@Scheduled(cron = "30 * * * * ?")
public void scanOrder(){
orderService.scanOrder(); //每30秒掃描數(shù)據(jù)庫 找出過期未支付的訂單,取消該訂單
}優(yōu)點:實現(xiàn)容易,成本低,不依賴其他組件。
缺點:
- 時間不夠精確。因為掃描是有間隔的,但卻隨時會產(chǎn)生過期的訂單,所以可能會導(dǎo)致有些訂單已經(jīng)過期了一段時間后才被掃描到。
- 增加了數(shù)據(jù)庫的壓力。頻繁的訪問數(shù)據(jù)庫,當(dāng)數(shù)據(jù)越來越多時,訪問數(shù)據(jù)庫的成本也會增加。
2、JDK延遲隊列 DelayQueue
DelayQueue是JDK提供的一個無界隊列,它的本質(zhì)是封裝了一個PriorityQueue(優(yōu)先隊列), PriorityQueue內(nèi)部使用完全二叉堆來實現(xiàn)隊列排序,在向隊列中插入元素時,需要給出這個元素的Delay時間,也就是過期時間,隊列中最小的元素會被放在隊首,隊列中的元素只有到了Delay時間才允許從隊列中取出。
具體的實現(xiàn)思路就是:首先創(chuàng)建一個實體類實現(xiàn)Delay接口,然后將它放入DelayQueue隊列中。
(1)定義實現(xiàn)Delayed接口的實體類
需要實現(xiàn)Delayed接口的兩個方法:getDelay()和compareTo()
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyDelay implements Delayed {
private String orderNumber; //訂單編號
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Long time; //過期時間
@Override
public long getDelay(TimeUnit timeUnit) {
return time - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed delayed) {
MyDelay myDelay = (MyDelay)delayed;
return this.time.compareTo(myDelay.getTime());
}
}(2)將延時任務(wù)放入隊列
package com.demo;
import com.demo.config.MyDelay;
import java.util.concurrent.DelayQueue;
public class demo {
public static void main(String[] args) throws InterruptedException {
MyDelay myDelay1 = new MyDelay("0001", 5L);
MyDelay myDelay2 = new MyDelay("0002", 10L);
MyDelay myDelay3 = new MyDelay("0003", 15L);
DelayQueue<MyDelay> delayDelayQueue = new DelayQueue<MyDelay>();
delayDelayQueue.add(myDelay1);
delayDelayQueue.add(myDelay2);
delayDelayQueue.add(myDelay3);
while (delayDelayQueue.size()!=0) {
/**
* 取隊列頭部元素是否過期
*/
//DelayQueue的put/add方法是線程安全的,因為put/add方法內(nèi)部使用了ReentrantLock鎖進行線程同步。
// DelayQueue還提供了兩種出隊的方法 poll() 和 take() ,
// poll() 為非阻塞獲取,沒有到期的元素直接返回null;
// take() 阻塞方式獲取,沒有到期的元素線程將會等待。
MyDelay order = delayDelayQueue.poll();
if(order!=null) {
System.out.println("訂單編號:"+order.getOrderNumber()+",超時取消!");
}
Thread.sleep(1000);
}
}
}優(yōu)點:不依賴任何第三方組件,實現(xiàn)方便。
缺點:因為DelayQueue是基于JVM的,如果放入的訂單過多,會造成JVM溢出。如果JVM重啟了,那所有的數(shù)據(jù)就丟失了。
3、redis過期監(jiān)聽
redis是一個高性能的key,value數(shù)據(jù)庫,除了用作緩存之外,它還提供了過期監(jiān)聽的功能。
在redis.conf中配置
配置notify-keyspace-events "Ex" 即可開啟此功能。

springboot 項目集成redis配置過期監(jiān)聽
在pom中引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在yml中配置redis源
redis:
#數(shù)據(jù)庫索引
database: 0
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
#最大連接數(shù)
max-active: 15
#最大阻塞等待時間(負數(shù)表示沒限制)
max-wait: -1
#最大空閑
max-idle: 15
#最小空閑
min-idle: 0
#連接超時時間
timeout: 10000編寫redis配置類
package com.example.study_demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
/**
* Redis配置
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
return redisMessageListenerContainer;
}
@Bean
public KeyExpiredListener keyExpiredListener() {
return new KeyExpiredListener(this.redisMessageListenerContainer());
}
}編寫redis工具類
package com.example.study_demo.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
* @param timeout 時間
* @param timeUnit 時間顆粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 設(shè)置有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @return true=設(shè)置成功;false=設(shè)置失敗
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 設(shè)置有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @param unit 時間單位
* @return true=設(shè)置成功;false=設(shè)置失敗
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 獲得緩存的基本對象。
*
* @param key 緩存鍵值
* @return 緩存鍵值對應(yīng)的數(shù)據(jù)
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 刪除單個對象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 刪除集合對象
*
* @param collection 多個對象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 緩存List數(shù)據(jù)
*
* @param key 緩存的鍵值
* @param dataList 待緩存的List數(shù)據(jù)
* @return 緩存的對象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 獲得緩存的list對象
*
* @param key 緩存的鍵值
* @return 緩存鍵值對應(yīng)的數(shù)據(jù)
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 緩存Set
*
* @param key 緩存鍵值
* @param dataSet 緩存的數(shù)據(jù)
* @return 緩存數(shù)據(jù)的對象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 獲得緩存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 緩存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 獲得緩存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 獲取Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @return Hash中的對象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 刪除Hash中的數(shù)據(jù)
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 獲取多個Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKeys Hash鍵集合
* @return Hash對象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 獲得緩存的基本對象列表
*
* @param pattern 字符串前綴
* @return 對象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}編寫監(jiān)控類
在代碼中繼承KeyspaceEventMessageListener ,實現(xiàn)onMessage就可以監(jiān)聽過期的數(shù)據(jù)量
package com.example.study_demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Slf4j
public class KeyExpiredListener extends KeyExpirationEventMessageListener {
public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
log.info("訂單{}過期了", expiredKey);
}
}測試
package com.demo;
import com.demo.config.MyDelay;
import java.util.concurrent.DelayQueue;
public class demo {
public static void main(String[] args) throws InterruptedException {
long expire = 5L; //設(shè)置過期時間
String key = "0001";
RedisCache redisCache = new RedisCache();
redisCache.setCacheObject(key,"訂單過期了");
redisCache.expire(key,expire);
}
}優(yōu)點:由于redis的高性能,所以在設(shè)置以及消費key時的速度可以保證。
缺點: 由于redis的key過期策略的原因,當(dāng)一個key過期時,無法立刻保證將其刪除,自然我們監(jiān)聽事件也無法第一時間消費到這個key,所以會存在一定的延遲。 此外,在redis5.0之前,訂閱發(fā)布消息并沒有被持久化,自然也沒有所謂的確認機制,所以一旦消費信息過程中我們的客戶端發(fā)生了宕機,這條消息就徹底丟失了。
4、Redisson分布式延遲隊列
Redisson是一個基于redis實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)絡(luò),它不僅提供了一系列的分布式Java常用對象,還提供了許多分布式服務(wù)。Redisson除了提供我們常用的分布式鎖外,還提供了一個分布式延遲隊列RDelayedQueue ,它是一種基于zset結(jié)構(gòu)實現(xiàn)的延遲隊列,其實現(xiàn)類是RedissonDelayedQueue,在springboot中整合使用Redisson分布式延遲隊列的步驟如下:
引入pom依賴,yml中配置redis連接
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.5</version>
</dependency>創(chuàng)建延時隊列生產(chǎn)者
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 延遲隊列生產(chǎn)者
*/
@Service
public class RDelayQueueProducer {
@Autowired
private RedissonClient redissonClient;
public void addTask(String taskId, long delayTime){
//創(chuàng)建一個延遲隊列
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(redissonClient.getQueue("my_delayQueue"));
//將任務(wù)添加到延遲隊列,指定延遲時間
delayedQueue.offer(taskId,delayTime,java.util.concurrent.TimeUnit.SECONDS);
}
}創(chuàng)建延時隊列消費者
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 延遲隊列消費者
*/
@Service
public class RDelayQueueConsumer {
@Autowired
private RedissonClient redissonClient;
public void consumeTask(){
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(redissonClient.getQueue("my_delayQueue"));
while (true){
String poll = delayedQueue.poll();
if(poll!=null){
//收到消息進行處理
System.out.println("收到消息:"+poll);
}
}
}
}測試
@PostMapping("/test")
public void test(){
rDelayQueueProducer.addTask("0001",5);
rDelayQueueProducer.addTask("0002",10);
rDelayQueueProducer.addTask("0003",15);
}優(yōu)點:使用簡單,并且其實現(xiàn)類中大量使用lua腳本保證其原子性,不會有并發(fā)重復(fù)問題。
缺點:需要依賴redis
5、RocketMQ延遲消息
RocketMQ是阿里巴巴開源的一款分布式消息中間件,基于高可用分布式集群技術(shù),提供低延遲的、可靠的消息發(fā)布與訂閱服務(wù)。下面是在springboot中集成RocketMQ延遲消息的步驟:
安裝并啟動 RocketMQ 服務(wù)
可參考RocketMQ 官方文檔進行安裝和啟動
引入依賴
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>配置RocketMQ
spring:
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ NameServer地址
producer:
group: my-group # 生產(chǎn)者組名創(chuàng)建消息生產(chǎn)者
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RocketMQProducerService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendMessage(String topic, String message,long delay) {
// 發(fā)送延遲消息,延遲級別為16,對應(yīng)延遲時間為delay
rocketMQTemplate.syncSend(topic, message, delay, 16);
}
}創(chuàng)建消息消費者
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
@Service
@RocketMQMessageListener(topic = "test-topic", consumerGroup = "my-consumer-group")
public class RocketMQConsumerService implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("接收到消息: " + message);
//檢查訂單是否支付
}
}測試
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RocketMQTestController {
@Autowired
private RocketMQProducerService producerService;
@GetMapping("/sendMessage")
public String sendMessage() {
String topic = "test-topic";
String message = "0001"; //發(fā)送訂單編號到rocketMQ
long delay = 3000;
producerService.sendMessage(topic, message, delay);
return "消息發(fā)送成功";
}
}優(yōu)點:系統(tǒng)之間完全解耦,只需要關(guān)注生產(chǎn)及消費即可。其吞吐量極高。
缺點:RocketMQ是重量級的組件,引入后,隨之而來的消息丟失等問題都增加了系統(tǒng)的復(fù)雜度。
6、RabbitMQ死信隊列
當(dāng)RabbitMQ中的一條正常信息,因為過了存活時間(ttl過期)、隊列長度超限等原因無法被消費時,就會被當(dāng)成一條死信消息,投遞到死信隊列。基于這樣的機制,我們可以給消息設(shè)置一個ttl ,等消息過期就會進入死信隊列,我們再消費死信隊列即可,這樣,就可以達到和RocketMQ一樣的效果。springboot集成rabbitMQ的步驟如下:
安裝并啟動 RabbitMQ 服務(wù)
可參考RabbitMQ官方文檔進行安裝和啟動
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>配置RabbitMQ
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest配置 RabbitMQ 隊列和交換機
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
public static final String ORDER_EXCHANGE = "order.exchange";
public static final String ORDER_QUEUE = "order.queue";
public static final String ORDER_ROUTING_KEY = "order.routing.key";
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
public static final String DEAD_LETTER_QUEUE = "dead.letter.queue";
public static final String DEAD_LETTER_ROUTING_KEY = "dead.letter.routing.key";
// 死信交換機
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange(DEAD_LETTER_EXCHANGE);
}
// 死信隊列
@Bean
public Queue deadLetterQueue() {
return new Queue(DEAD_LETTER_QUEUE);
}
// 綁定死信隊列和死信交換機
@Bean
public Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(DEAD_LETTER_ROUTING_KEY);
}
// 正常交換機
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(ORDER_EXCHANGE);
}
// 正常隊列,設(shè)置死信交換機和路由鍵,以及消息TTL為30分鐘(1800000毫秒)
@Bean
public Queue orderQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
args.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY);
args.put("x-message-ttl", 1800000);
return new Queue(ORDER_QUEUE, true, false, false, args);
}
// 綁定正常隊列和正常交換機
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(ORDER_ROUTING_KEY);
}
}創(chuàng)建消息生產(chǎn)者
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderMessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrderMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.ORDER_EXCHANGE, RabbitMQConfig.ORDER_ROUTING_KEY, message);
}
}創(chuàng)建消息消費者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class OrderMessageConsumer {
@RabbitListener(queues = RabbitMQConfig.DEAD_LETTER_QUEUE)
public void receiveOrderMessage(String message) {
System.out.println("收到訂單: " + message);
// 模擬檢查訂單支付狀態(tài)
}
}測試
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderMessageController {
@Autowired
private OrderMessageProducer orderMessageProducer;
@GetMapping("/sendOrderMessage")
public String sendOrderMessage() {
String message = "0001"; //訂單編號
orderMessageProducer.sendOrderMessage(message);
return "訂單消息已發(fā)送,30分鐘后處理";
}
}優(yōu)點:同RocketMQ一樣可以使業(yè)務(wù)解耦。
缺點:RabbitMQ 的 TTL 是基于隊列的,而不是基于單個消息的精確時間控制。當(dāng)隊列中有多個消息時,即使某個消息的 TTL 已經(jīng)過期,也需要等待前面的消息被處理完才能進入死信隊列,導(dǎo)致消息的實際處理時間可能會有一定的延遲,無法保證精確的延遲時間。
到此這篇關(guān)于Java中如何實現(xiàn)訂單超時自動取消功能的文章就介紹到這了,更多相關(guān)Java訂單超時自動取消內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)批量查找與替換Excel文本的思路詳解
在 Java 中,可以通過find和replace的方法來查找和替換單元格的數(shù)據(jù),下面小編將以Excel文件為例為大家介紹如何實現(xiàn)Excel文件內(nèi)容的批量替換,感興趣的朋友跟隨小編一起看看吧2023-10-10
SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法
Spring Boot Actuator 是開發(fā)和管理生產(chǎn)級 Spring Boot 應(yīng)用程序的重要工具,它可以幫助你確保應(yīng)用程序的穩(wěn)定性和性能,本文給大家介紹了SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法,需要的朋友可以參考下2024-05-05
Spring + Spring Boot + MyBatis + MongoDB的整合教程
這篇文章主要給大家介紹了關(guān)于Spring + Spring Boot + MyBatis + MongoDB的整合教程,文中通過圖文以及示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-12-12
Mybatis通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換
這篇文章主要為大家詳細介紹了Mybatis如何通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12

