整合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可以是业务方传入,也可以是达到某个条件进行自由切换。