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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值