整合MyBatis進行批量操作
一個批量插入,一個批量更新,此擴展Mapper繼承原Mapper,這樣注入的時候就不用注入2個Mapper接口。
Tips:
- 進行批量操作,要設置參數allowMultiQueries=true,不然無法進行批量更新。
spring.datasource.url=jdbc:mysql://${ip}:${port}/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
- 傳入的List一定要做限制,更新操作最好不要超過500條(據說超過有問題,待驗證)
樣例代碼:
public interface IVoiceMsgDetailExtMapper extends VoiceMsgDetailMapper {
@Insert("<script>" +
" insert into voice_msg_detail ( voice_ext_msg_id, voice_msg_id, phone," +
" status, call_start_time, call_answer_time," +
" template_id, result_code, result_desc," +
" request_param, response_param" +
" )" +
" values" +
" <foreach collection=\"list\" item=\"item\" index=\"index\" separator=\",\">" +
" (" +
" #{item.voiceExtMsgId,jdbcType=VARCHAR}, #{item.voiceMsgId,jdbcType=VARCHAR}, #{item.phone,jdbcType=VARCHAR}," +
" #{item.status,jdbcType=SMALLINT}, #{item.callStartTime,jdbcType=VARCHAR}, #{item.callAnswerTime,jdbcType=VARCHAR}," +
" #{item.requestParam,jdbcType=VARCHAR}, #{item.responseParam,jdbcType=VARCHAR}" +
" )" +
" </foreach>" +
"</script>")
@Options(useGeneratedKeys = true)
int insertByBatch(List<VoiceMsgDetail> list);
@Update("<script>" +
"<foreach collection=\"list\" item=\"record\" index=\"index\" open=\"\" close=\"\" separator=\";\">" +
" update voice_msg_detail" +
" <set>" +
" <if test=\"record.phone != null\">" +
" phone = #{record.phone,jdbcType=VARCHAR}," +
" </if>" +
" <if test=\"record.status != null\">" +
" status = #{record.status,jdbcType=SMALLINT}," +
" </if>" +
" </set>" +
" WHERE id = #{record.id,jdbcType=BIGINT}" +
" </foreach>" +
"</script>")
int updateByBatch(List<VoiceMsgDetail> list);
}
基於Redis的分佈式鎖
基於Redis做的分佈式鎖,由於加鎖和設置過期時間是兩部操作,因此每次獲取鎖會檢查鎖的有效期,防止異常情況下過期時間沒有設置上導致死鎖發生。
樣例代碼同時有重試次數概念,默認6次,獲取鎖最大等待時間爲3秒。
樣例代碼
/**
* 基於Redis的分佈式鎖
* @author chengjz
* @version 1.0
* @date 2018-12-19 17:11
*/
@Component
@Slf4j
public class RedisLockUtils {
/**
* 服務標識
*/
private static String APP_NAME = "msg-service";
/**
* 默認過期時間
*/
private static int DEFAULT_EXPIRE_TIME = 3;
/**
* 默認重試次數
*/
private static int DEFAULT_RETRIES_TIMES = 6;
/**
* 只執1次
*/
private static int ONLY_ONCE = 1;
/**
* KEY前綴
*/
private static final String PREFIX = "common:redisLock:{0}:{1}";
private static StringRedisTemplate stringRedisTemplate;
@Autowired
public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
RedisLockUtils.stringRedisTemplate = stringRedisTemplate;
}
/**
* 默認鎖三小時,且獲取鎖最大等待時間爲3秒,value默認爲時間戳
* @see #DEFAULT_EXPIRE_TIME
* @see #DEFAULT_RETRIES_TIMES
* @param lockKey 加鎖的KEY
* @return
*/
public static Boolean getLock(String lockKey) {
return getCustomLock(lockKey, System.currentTimeMillis() + "", DEFAULT_EXPIRE_TIME, TimeUnit.HOURS, DEFAULT_RETRIES_TIMES);
}
/**
* 默認鎖三小時,且獲取鎖最大等待時間爲3秒
* @see #DEFAULT_EXPIRE_TIME
* @see #DEFAULT_RETRIES_TIMES
* @param lockKey 加鎖的KEY
* @param lockVal 加鎖的Val
* @return
*/
public static Boolean getLock(String lockKey, String lockVal) {
return getCustomLock(lockKey, lockVal, DEFAULT_EXPIRE_TIME, TimeUnit.HOURS, DEFAULT_RETRIES_TIMES);
}
/**
* 不進行重試,執行1次
* @see #ONLY_ONCE
* @param lockKey 加鎖的KEY
* @return
*/
public static Boolean getLockOnce(String lockKey) {
return getLockOnce(lockKey, System.currentTimeMillis() + "");
}
/**
* 不進行重試,只執行1次
* @see #ONLY_ONCE
* @param lockKey 加鎖的KEY
* @param lockVal 加鎖的Val
* @return
*/
public static Boolean getLockOnce(String lockKey, String lockVal) {
return getCustomLock(lockKey, lockVal, DEFAULT_EXPIRE_TIME, TimeUnit.HOURS, ONLY_ONCE);
}
/**
* 自定義鎖
* @param lockKey 加鎖的KEY
* @param lockVal 加鎖的Val
* @param expireTime 失效時間
* @param timeUnit 失效單位
* @param retries 獲取鎖失敗時最多可以重試的次數,每次間隔500毫秒
* @return
*/
public static Boolean getCustomLock(String lockKey, String lockVal, int expireTime, TimeUnit timeUnit, int retries) {
String fullKey = getKeyStr(lockKey);
for (int i = 0; i < retries; i++) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(fullKey, lockVal);
if (flag) {
stringRedisTemplate.expire(fullKey, expireTime, timeUnit);
log.info("{} get lock success ...", fullKey);
return true;
} else {
Long expire = stringRedisTemplate.getExpire(fullKey);
if (expire == -1) {
log.warn("Exception key : {}, Reset expiration time !!", fullKey);
stringRedisTemplate.expire(fullKey, expireTime, timeUnit);
}
}
if (retries > ONLY_ONCE) {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
log.warn("Redis lock sleep failed!");
}
}
}
log.info("{} get lock failed ...", fullKey);
return false;
}
/**
* 釋放鎖
* @param lockKey 要解鎖的KEY
* @return
*/
public static Boolean releaseLock(String lockKey) {
String fullKey = getKeyStr(lockKey);
String s = stringRedisTemplate.opsForValue().get(fullKey);
if (StringUtils.isEmpty(s)) {
log.info("{} doesn't exist, release the lock failed ...", fullKey);
return false;
}
stringRedisTemplate.delete(fullKey);
log.info("{} release lock success ...", fullKey);
return true;
}
/**
* 生成組裝的Key
* @param lockKey 要鎖的Key
* @return
*/
public static String getKeyStr(String lockKey) {
return MessageFormat.format(PREFIX, APP_NAME, lockKey);
}
/**
* 多Key組合
* @param args 要組合的Key列表
* @return
*/
public static String assemblyMultKey(String ... args) {
if (args.length == 1) {
return args[0];
} else {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.length; i++) {
sb.append(args[i]).append(":");
}
String assemblyMultKey = sb.toString();
assemblyMultKey = assemblyMultKey.substring(0, assemblyMultKey.length() - 1);
log.info("assemblyMultKey : {}", assemblyMultKey);
return assemblyMultKey;
}
}
}
基於Spring的策略模式
當一個接口有多個實現時,通過傳入參數,進行動態調用指定服務實現。典型場景:一個渠道接口,現在有多個渠道商,那麼就可以用此方式進行處理。
首先準備,一個接口三個實現:
public interface IChannelService {
void process();
}
@Service("aChannel")
public class AChannelService implements IChannelService {
@Override
public void process() {
System.out.println("A 業務開始執行...");
}
}
@Service("bChannel")
public class BChannelService implements IChannelService {
@Override
public void process() {
System.out.println("B 業務開始執行...");
}
}
@Service("cChannel")
public class CChannelService implements IChannelService {
@Override
public void process() {
System.out.println("C 業務開始執行...");
}
}
然後寫一個基於Spring管理對象的工廠類,並對取出的對象進行緩存:
@Component
@Slf4j
public class IChannelFactory implements ApplicationContextAware {
public static ApplicationContext APPLICATIONCONTEXT;
public static Map<String, IChannelService> CHANNEL_MAPS;
@PostConstruct
public void initChanelMaps() {
CHANNEL_MAPS = APPLICATIONCONTEXT.getBeansOfType(IChannelService.class);
CHANNEL_MAPS.forEach( (k, v) -> log.info("loading ... {} : {}", k, v.getClass().getSimpleName()));
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
APPLICATIONCONTEXT = context;
}
}
啓動時會把此接口所有的實現bean放入緩存中,下圖是日誌:
2019-01-07 16:57:59.266 |-INFO [main] IChannelFactory [29] -| loading ... aChannel : AChannelService
2019-01-07 16:57:59.267 |-INFO [main] IChannelFactory [29] -| loading ... bChannel : BChannelService
2019-01-07 16:57:59.267 |-INFO [main] IChannelFactory [29] -| loading ... cChannel : CChannelService
那麼測試一下吧:
String channel = null;
channel = "aChannel";
IChannelService a = IChannelFactory.CHANNEL_MAPS.get(channel);
a.process();
channel = "bChannel";
IChannelService b = IChannelFactory.CHANNEL_MAPS.get(channel);
b.process();
channel = "cChannel";
IChannelService c = IChannelFactory.CHANNEL_MAPS.get(channel);
c.process();
根據實際需要進行更改,channel可以是業務方傳入,也可以是達到某個條件進行自由切換。