JAVA資料庫

整合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可以是業務方傳入,也可以是達到某個條件進行自由切換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章