spring事务问题
- 如果想使 Propagation.REQUIRES_NEW 生效,则需要把testB这个方法单独放到别的类中,或者通过aop contxt 获取对象,进行调用
说明:同一个service中的两个方法互相调用都开启事务,被调用方的事务是不会生效的,是属于调用方的事务,不会开启新的事务
- 同一个service类中的两个方法互相调用,都开启事务,遇到错误两个都会回滚
- 同一个service类中的两个方法互相调用,调用方开启事务,被调用方不开启事务,出现异常都会回滚
- 同一个service类中的两个方法互相调用,调用方开启事务,被调用方开启事务,调用方catch被调用方异常,则两个都不会回滚,
- 同一个service类中的两个方法互相调用,调用方不开启事务,被调用方开启事务,出现异常不会回滚,通过aop contxt 获取对象,进行调用可以解决
备注: 主要是当同一个service类中的两个方法互相调用,被调用的service没有被aop增强代理,不会开启事务
私有方法不生效
方法用final修饰
其实,我们在使用
@Transactional
注解时,是可以指定propagation
参数的。该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:
REQUIRED
如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。SUPPORTS
如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。MANDATORY
如果当前上下文中存在事务,否则抛出异常。REQUIRES_NEW
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。NOT_SUPPORTED
如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。NEVER
如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。NESTED
如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
Transaction rolled back because it has been marked as rollback-only 问题:
rocketmq + 事务一起使用
rocketmq消费时开启事务,并且捕获异常,try的方法也开启事务注解,这样最终try的方法报错,rocketmq还是会重试5次,正确的应该是报错后就不再执行,消息消费完成,日志记录即可,但是没有,重试了5次
原因:在外层开启了事务,内部也开启了事务,如果没有指定其他参数,则也会加入已经开启的事务中,内部执行报错,会被记录下来,在Spring的REQUIRED中,只要异常被Spring捕获到过,那么Spring最终就会回滚整个事务,即使自己在业务中已经捕获!
为什么会影响到rocketmq进行重试?
事务处理时链路有一个异常则最终会抛出异常,mq监听异常后返回重试状态
解决方式:
- 调用方不开启事务,这样调用方捕获异常后,不会影响整个链路
- 被调用方整个方法trycatch 不抛出异常
具体结合需求处理
https://juejin.cn/post/7052607119606382600
compareTo
compareTo大于的话返回的是正整数,等于是0,小于的话就是负整数
lambda
分组功能 并且有序
List
for (Map.Entry<Boolean, List
}
Map<String, List<String>> collect = storePointRelList.stream().collect(Collectors.groupingBy(MapperStorePointRel::getStoreId,
Collectors.mapping(MapperStorePointRel::getStoreId, Collectors.toList())));
Map<String, LinkedBlockingQueue<String>> queueMap = storePointRelList.stream()
.collect(Collectors.groupingBy(MapperStorePointRel::getStoreId,
Collectors.mapping(MapperStorePointRel::getStoreId, Collectors.toCollection(LinkedBlockingQueue::new))));
Map<String, String> assetMap = astSgassetService.lambdaQuery().select(AstSgasset::getId, AstSgasset::getName).list().stream()
.collect(Collectors.toMap(AstSgasset::getId, AstSgasset::getName, (v1, v2) -> v2));
Map<String, TargetPointInfoDTO> targetPointInfoDTOMap = pointInfoDTOList.stream()
.collect(Collectors.toMap(TargetPointInfoDTO::getId, item -> item, (v1, v2) -> v2));
Map<String, AstSgassetDTO> assetMap = astSgassetManager.getAstSgassetListByIds()
.stream().collect(Collectors.groupingBy(AstSgassetDTO::getId, Collectors.collectingAndThen(Collectors.toList(), item -> item.get(0))));
filter会将false的数据过滤掉
optional
Optional.ofNullable(m1()).orElse(m2())
m1结果非空还是会执行m2!
所以,如果orElse()中的计算或其他处理业务很多时,推荐使用orElseGet()
map使用替换ifelse
Integer tableId = order.getTableNumberConfigId();
String tableName = Optional.ofNullable(tableId).map(item -> tableNumberConfigService.getById(tableId)).map(TableNumberConfig::getName).orElse("");
UML结构图
https证书配置
使用acme.sh 可以自动管理证书有效期自动续订
案例:
user root;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
client_max_body_size 100m;
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
server {
listen 80;
server_name localhost;
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;
#charset koi8-r;
location / {
access_log logs/newwap_pay.access.log ;
proxy_pass http://36.133.252.148;
}
#access_log logs/host.access.log main;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
# 酒吧小程序
server {
listen 80;
server_name xd.wenhuizh.cn;
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;
#charset koi8-r;
location / {
root html;
}
location /cms-api/ {
access_log logs/cms-api.access.log ;
proxy_pass http://127.0.0.1:9226/;
}
location /h5-api/ {
access_log logs/h5-api.access.log ;
proxy_pass http://127.0.0.1:9239/;
}
location /barcms/ {
alias /usr/local/project/bar-order-static/;
index index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
# 证书配置
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /root/software/openresty/nginx/conf/8991846_wenhuizh.cn.pem;
ssl_certificate_key /root/software/openresty/nginx/conf/8991846_wenhuizh.cn.key;
location / {
access_log logs/newwap_pay.access.log ;
proxy_pass http://36.133.252.148;
}
}
# 酒吧小程序
server {
listen 443 ssl;
server_name xd.wenhuizh.cn;
ssl_certificate /root/software/openresty/nginx/conf/xd.wenhuizh.cn/cert.pem;
ssl_certificate_key /root/software/openresty/nginx/conf/xd.wenhuizh.cn/key.pem;
location /cms-api/ {
access_log logs/cms-api.access.log ;
proxy_pass http://127.0.0.1:9226/;
}
location /h5-api/ {
access_log logs/h5-api.access.log ;
proxy_pass http://127.0.0.1:9239/;
}
location /barcms/ {
alias /usr/local/project/bar-order-static/;
index index.html;
}
}
}
redis分布式锁问题
redis本身是不具备可重入锁功能,需要借助其他方式如redisson
通过下面方式加锁时,如果key和value一样,线程1加锁成功,线程2是会等待的 public boolean lock(String key, String requestId, long expire, long waitTimeout) { long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime <= waitTimeout) { boolean locked = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(key.getBytes(), requestId.getBytes(), Expiration.seconds(expire), RedisStringCommands.SetOption.ifAbsent())); if (locked) { return true; } try { Thread.sleep(100); } catch (InterruptedException e) { log.error("获取锁设置过期时间和获取锁时间", e); Thread.currentThread().interrupt(); } } return false; }
下面加锁方式如果value值一样,会出现, 线程1加锁成功,线程2等待但是等待5s之后就返回失败了,没有加锁成功,就会抛出异常,最后走finally,将线程1的锁给解锁了的情况,所以还是要指定value值不同的uuid String redisId = String.valueOf(uidGenerator.getUID()); try { boolean lock = redisLockUtils.lock(RedisKeyPrefix.patrolCycleLock, redisId); if (!lock) { throw new ServiceException(AppConstant.Status.REDIS_LOCK_ERROR); } info.getIds().forEach(id -> { PatrolStoreInfo storeInfo = validStore(id); // 删除redis数据 redisAppUtil.deleteForHash(RedisKeyPrefix.patrolCycleList, id); this.update(Wrappers.<PatrolStoreInfo>lambdaUpdate().set(PatrolStoreInfo::getCycleStatus, NO.getState()).set(PatrolStoreInfo::getUpdateTime, new Date()) .eq(PatrolStoreInfo::getId, id)); voList.add(storeInfo.getTaskName() + "停止巡视日常成功!"); log.info("任务id:{},名称:{},停止巡视日常成功", id, storeInfo.getTaskName()); }); } finally { redisLockUtils.unlock(RedisKeyPrefix.patrolCycleLock, redisId); }
docker
docker-compose方式启动可以在同级目录制定.env文件指定名称,这样是为了避免
内容为:
COMPOSE_PROJECT_NAME=名称
springboot 应用无缘无故被kill
现象:应用部署在docker中,无法访问,进入容器也找不到java进程,并且没有异常日志,没有oom
原因:系统内存不足,被系统直接kill,通过查看/var/log/messages 日志过滤kill关键字发现被系统杀死
解决:调整内存大小
google guava
key一样时会将值添加到value的list集合中
MultiValueMap<String, String> stringMultiValueMap = new LinkedMultiValueMap<>();
rocketmq无法消费问题
规定是一个主题对应一个消费者组,如果一个消费者组下面有多个主题会出现无法消费问题 4.8版本