问题:调用mybatisPlus的 Iservice中的save方法,后台日志打印出来Insert语句,但是没插入到数据库表中;
一共向两个表中插入数据;weather ,向天气表中插入数据成功,但是在异步任务中向 三方同步日志表中 third_request_log 是只打印出了成功Insert 语句和参数,但是实际未插入成功:
代码如下:
package com.atguigu.gulimall.coupon.learn.controller;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.spring.util.ObjectUtils;
import com.atguigu.gulimall.coupon.entity.ThirdRequestLog;
import com.atguigu.gulimall.coupon.entity.WeatherEntity;
import com.atguigu.gulimall.coupon.learn.annotation.ParamValided;
import com.atguigu.gulimall.coupon.learn.entity.CommonConstant;
import com.atguigu.gulimall.coupon.learn.entity.vo.WeatherReqVo;
import com.atguigu.gulimall.coupon.learn.myexception.BusiException;
import com.atguigu.gulimall.coupon.service.ThirdLearnService;
import com.atguigu.gulimall.coupon.service.ThirdRequestLogService;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 调用三方接口 学习
*
* @author: jd
* @create: 2024-05-27
*/
@RestController
@Slf4j
@RequestMapping("/coupon/thirdLearn")
public class HttpThirdCallController {
@Autowired
private Environment environment;
@Autowired
CommonConstant commonConstant;
@Autowired
ThirdLearnService thirdLearnService;
@Autowired
ThirdRequestLogService thirdRequestLogService;
//
private ExecutorService service = Executors.newCachedThreadPool();
//0.模拟数据库中的数据
private static Map<Integer, WeatherReqVo> weatherReqVoMap = null;
static {
weatherReqVoMap = new HashMap<Integer, WeatherReqVo>();//创建一个天气表,存储着现有的,可以查询天气的地市
//初始化表数据
weatherReqVoMap.put(10001, new WeatherReqVo(10001L, "石家庄"));
weatherReqVoMap.put(10002, new WeatherReqVo(10002L, "北京"));
weatherReqVoMap.put(10003, new WeatherReqVo(10003L, "武汉"));
weatherReqVoMap.put(10004, new WeatherReqVo(10004L, "邯郸"));
weatherReqVoMap.put(10005, new WeatherReqVo(10005L, "天津"));
weatherReqVoMap.put(10006, new WeatherReqVo(10006L, "郑州"));
}
// 数据来源: 原文链接:https://blog.csdn.net/QQQZSJ/article/details/109116120
@ApiOperation("第三方调用练习")
@GetMapping("/thirdCall")
@ParamValided
public String getWeather(WeatherReqVo weatherReqVo) {
//校验所传地址在数据库中是否存在
if (!weatherReqVoMap.containsValue(new WeatherReqVo(weatherReqVo.getId(), weatherReqVo.getCityName()))) {
//这里抛出的是自定义的异常,而且这个自定义的异常是被自定义的异常捕捉器给捕捉
throw new BusiException("所查询地市不在数据库可查询地市列表中", CommonConstant.ErrorCode.NO_VALUE_ERROR);
}
//调用三方接口,获取天气
getWeatherMethod(weatherReqVo);
return "查询成功";
}
/**
* 调用三方天气查询接口
*
* @param weatherReqVo
*/
// @Transactional(rollbackFor = Exception.class)
public void getWeatherMethod(WeatherReqVo weatherReqVo) {
//组合参数
HashMap<String, Object> params = new HashMap<>();
params.put("city", weatherReqVo.getCityName());
//获取API_KEY
params.put("key", environment.getProperty("API_KEY")); // 另外一种获取方式 CommonConstant.API_KEY
//拼接参数(包含参数编码)
String queryParams = urlencode(params);
log.info("===========>{}", queryParams);
//原始Connection get请求方式
// String response = doGet(CommonConstant.API_URL, queryParams);
//原始Connection post请求方式
// String response = doPost(CommonConstant.API_URL, queryParams);
//hutool 工具 HttpRequest.post() post请求方式
// String response = httpRequestPost(CommonConstant.API_URL, weatherReqVo.getCityName(), environment.getProperty("API_KEY"));
//springRestTemplate 请求第三方接口 post方式
String response = springRestTemplate(CommonConstant.API_URL, params, queryParams);
log.info("===========>JSONUtil.parseObj(response)\n{}", JSONUtil.parseObj(response));
JSONObject jsonObject = JSONUtil.parseObj(response);
int error_code = jsonObject.getInt("error_code");
if (error_code == 0) {
System.out.println("调用接口成功");
JSONObject result1 = JSONUtil.parseObj(jsonObject.getStr("result"));
JSONObject result = jsonObject.getJSONObject("result");
log.info("========>JSONUtil.parseObj(jsonObject.getStr(\"result\"))\n {}", result1);
System.out.println("======间隔======");
log.info("========>jsonObject.getJSONObject(\"result\")\n {}", result);
JSONObject realtime = result.getJSONObject("realtime");
System.out.println("=============今日天气===============");
//查询地市
System.out.printf("城市:%s%n", result.getStr("city"));
//今日天气
System.out.printf("天气:%s%n", realtime.getStr("info"));
System.out.printf("温度:%s%n", realtime.getStr("temperature"));
System.out.printf("湿度:%s%n", realtime.getStr("humidity"));
System.out.printf("风向:%s%n", realtime.getStr("direct"));
System.out.printf("风力:%s%n", realtime.getStr("power"));
System.out.printf("空气质量:%s%n", realtime.getStr("aqi"));
//保存今日天气
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
WeatherEntity weatherEntity = new WeatherEntity();
weatherEntity.setCity(result.getStr("city"));
weatherEntity.setDate(simpleDateFormat.format(new Date()));
weatherEntity.setTemperature(realtime.getStr("temperature"));
weatherEntity.setHumidity(realtime.getStr("humidity"));
weatherEntity.setInfo(realtime.getStr("info"));
weatherEntity.setDirect(realtime.getStr("direct"));
weatherEntity.setPower(realtime.getStr("power"));
weatherEntity.setAqi(realtime.getStr("aqi"));
LocalDateTime now = LocalDateTime.now();
weatherEntity.setCreateTime(now);
thirdLearnService.save(weatherEntity);
log.info("===============>今日天气保存成功");
//批量保存使用
ArrayList<WeatherEntity> weatherEntityArrayList= new ArrayList<>();
//未来五天天气
System.out.println("=============未来五天天气===============");
JSONArray future = result.getJSONArray("future");
for (int i = 0; i < future.size(); i++) {
//未来日期
String date = future.getJSONObject(i).getStr("date");
//未来某天温度
String temperature = future.getJSONObject(i).getStr("temperature");
//未来某天天气
String weather = future.getJSONObject(i).getStr("weather");
//未来某天风向
String direct = future.getJSONObject(i).getStr("direct");
System.out.printf("%s %s 天气%n", date, result.getStr("city"));
System.out.printf("温度:%s%n", temperature);
System.out.printf("天气:%s%n", weather);
System.out.printf("风向:%s%n", direct);
System.out.println();
//保存未来天气
WeatherEntity futureWeather = new WeatherEntity();
futureWeather.setCity(result.getStr("city"));
futureWeather.setDate(date);
futureWeather.setTemperature(temperature);
futureWeather.setInfo(weather);
futureWeather.setDirect(direct);
LocalDateTime nowTime = LocalDateTime.now();
futureWeather.setCreateTime(nowTime);
weatherEntityArrayList.add(futureWeather);
}
//批量保存
thirdLearnService.saveBatch(weatherEntityArrayList);
log.info("===============>未来天气保存成功");
//异步存入日志表-查询成功
// service.submit(new dealTask(weatherReqVo.getCityName(), CommonConstant.REQUEST_SUCCESS));
//同步插入日志 -查询成功
insertRequestLog(weatherReqVo.getCityName(), CommonConstant.REQUEST_FAIL);
} else {
System.out.println("调用接口失败,失败原因" + jsonObject.getStr("reason"));
throw new BusiException("查询失败!", CommonConstant.ErrorCode.BUSI_ERROR); //因为这种异常 我做了全局的异常捕获,所以报错的时候,会报出具体的原因,这种的肯定更好
// throw new RuntimeException("查询失败!"); // 因为对这种异常没有做全局的捕获,所以请求完报错后直接提示错误信息 500,没有提示具体的原因 "status": 500, "error": "Internal Server Error",
//异步存入轨迹表-查询成功
// service.submit(new dealTask(weatherReqVo.getCityName(), CommonConstant.REQUEST_FAIL));
}
}
/**
* RestTemplate 进行第三方接口调用
* get请求:三种方式 :
* 1、restTemplate.getForObject(allUrl, String.class, params) url中的参数用占位符,并且将参数通过map的方式放到getForObject()方法参数中 这种调用成功
* 2、restTemplate.getForObject(allUrl, String.class) 直接将参数拼接到url里面,不在 getForObject 中传参。这种调用失败
* 3、restTemplate.exchange(allUrl,
* HttpMethod.GET,
* request,
* String.class,
* params) 这种请求是带请求头的,url传参和第一种的是一样的,也是url是有占位符,最后一个参数是“参数Map”
*
* @param apiUrl
* @param queryParams
* @return
*/
public String springRestTemplate(String apiUrl, Map params, String queryParams) {
Map<String, Object> map;
RestTemplate restTemplate = new RestTemplate();
//第一种调用通方式-start,调用restTemplate.getForObject https://www.cnblogs.com/windyWu/p/16872871.html 见博客 url中的参数用占位符,并且将参数通过map的方式放到getForObject()方法参数中,这种是可以调用通过,并返回正确参数
String allUrl = apiUrl + "?city={city}&key={key}";
log.info("========> 完整allUrl {}", allUrl);
String resultObject = restTemplate.getForObject(allUrl, String.class, params);
//第一种:-end
// //第二种:-start调用restTemplate.getForObject ,直接将参数拼接到url里面,不在 getForObject 中传参。这种调用失败,响应: {"result":null,"reason":"暂不支持该城市","error_code":207301}
// String allUrl = new StringBuffer(apiUrl).append("?").append(queryParams).toString();
// log.info("========> 完整allUrl {}", allUrl);
// String resultObject = restTemplate.getForObject(allUrl, String.class);
//第二种:-end
//第一种和第二种请求方式 都是调用的 restTemplate.getForObject 响应结果是用下面这两行解析
map = JSONUtil.parseObj(resultObject); //需要知道的是 为什么这里既能用Map<String, Object> map来接受,也能用JSONObject jsonObject来接受,
// 因为JSONObject其实就是相当于前面的map结构,里面存储的也是键值对,可以看上面的方法 httpRequestPost() 中的注释部分put方法。也可以很明显的看出JSONObject结构类似于map
JSONObject jsonObject = JSONUtil.parseObj(resultObject);
/*
//第三种调用方式-start,带请求头的,
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// build the request
HttpEntity request = new HttpEntity(headers);
// make an HTTP GET request with headers
ResponseEntity<String> resultObject1 = restTemplate.exchange(
allUrl,
HttpMethod.GET,
request,
String.class,
params
);
// 第三种方式 调用的 restTemplate.exchange 响应结果是用下面这种方式进行解析,需要拿到响应结果中的getBody,才是用前两种方式得到的响应体
map = JSONUtil.parseObj(resultObject1.getBody());
HttpStatus statusCode = resultObject1.getStatusCode();
int statusCodeValue = resultObject1.getStatusCodeValue();
HttpHeaders headers1 = resultObject1.getHeaders();
//第三种-end */
//下面代码是三种请求方式公用的;
int code = Integer.parseInt(map.get("error_code").toString());
if (code == 0) {
System.out.println("响应成功");
log.info("=================>jsonObject \n {}", JSONUtil.toJsonStr(jsonObject)); //将JSONObject 对象转换为 String
log.info("=================>map \n {}", map);//这行的输出和上面一行的输出是完全一样的(已经验证过了)
//取值练习
JSONObject result = jsonObject.getJSONObject("result");
//获取地市
String city = result.getStr("city"); //特别注意,如果get之后是一个对象,则用jsonObject.getJSONObject,如果是得到一个基本类型,则是使用JSONObject对象的getStr()、getInt()等方法
//获取今天温度
JSONObject realtime = result.getJSONObject("realtime");
String temperature = realtime.getStr("temperature");
System.out.printf("===springRestTemplate==%s温度%s%n===", city, temperature);
}
return JSONUtil.toJsonStr(resultObject);
}
//异步任务
public class dealTask implements Runnable{
//(String cityName,String code)
private String cityName;
private String code;
public dealTask(String cityName, String code) {
this.cityName = cityName;
this.code = code;
}
//业务处理
@Override
public void run() {
log.info("===异步存入轨迹表===");
try {
//10s后存储日志表
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
insertRequestLog(cityName,code);
}
}
/**
* 记录请求日志
* @param cityName
* @param code
*/
public void insertRequestLog(String cityName, String code) {
ThirdRequestLog thirdRequestLog = new ThirdRequestLog();
thirdRequestLog.setCity(cityName);
thirdRequestLog.setRequestCode(code);
LocalDateTime now = LocalDateTime.now();
thirdRequestLog.setCreateTime(now);
thirdRequestLogService.save(thirdRequestLog);
}
/**
* URL入参参数 编码
*
* @param params
* @return
*/
private String urlencode(Map<String, ?> params) {
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<String, ?> stringEntry : params.entrySet()) {
try {
stringBuilder.append(stringEntry.getKey()).append("=").append(URLEncoder.encode(stringEntry.getValue() + "", "UTF-8")).append("&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
String result = stringBuilder.toString();
//截取 从第一个字符到最后一个&,之间的字符,用于后面拼接到url后面当入参
result = result.substring(0, result.lastIndexOf("&"));
return result;
}
}
异步插入日志调用:
service.submit(new dealTask(weatherReqVo.getCityName(), CommonConstant.REQUEST_SUCCESS));
日志打印如下:
待截图
猜测原因:
1、
(1)将当前类的@Transactional(readOnly = true)注解去掉,代表当前类的方法修改数据库操作可以生效。 (不涉及)
(2)在当前类的方法外面加上@Transactional(rollbackFor = Exception.class)注解,代表当前类除了加上@Transactional(rollbackFor = Exception.class)注解的方法修改数据库操作可以生效,但是当前类的其他方法修改数据库的操作不能生效。(可能是)
(3)自调用问题:如果你在同一个类的内部直接调用另一个方法,那么Spring的事务管理可能不会生效。你应该将第二个方法移到另一个bean中,并通过注入的方式调用它。(可能是
(4)事务传播行为:如果你调用的第二个方法也带有@Transactional
注解,并且你希望它在第一个方法的事务中运行,你需要确保事务的传播行为(propagation behavior)是正确的。默认的传播行为是REQUIRED
,它表示如果当前没有事务,则创建一个新事务;如果当前存在事务,则加入该事务。但是,如果第二个方法被配置为不同的传播行为(如REQUIRES_NEW
),那么它将启动一个新的事务,这可能不是你想要的 (不涉及)
(5)异常处理:如果第二个方法中的代码抛出了异常,但该异常没有被捕获或正确处理,那么它可能会导致事务回滚。但是,如果异常被捕获并且没有重新抛出(或者抛出了一个不被rollbackFor
指定的异常),那么事务可能不会回滚。确保你正确地处理了所有可能导致事务失败的异常。 (不涉及)
(6)日志和打印:你提到的“打印了insert语句”可能是通过日志或调试信息看到的。这并不意味着语句已经执行并成功插入了数据。它只是表示该语句已经被准备并发送到数据库。要确定语句是否真的执行了,你需要检查数据库或更详细的日志输出。
(7)数据库连接和事务隔离级别:确保数据库连接是有效的,并且事务的隔离级别允许你看到预期的更改。在某些情况下,事务的隔离级别可能会导致你无法立即看到其他事务所做的更改。
(8)检查第二个方法的实现:确保第二个方法中的逻辑是正确的,并且没有导致插入失败的代码(如条件语句、循环、异常处理等)。
(9)启用Spring事务调试日志:通过配置Spring的日志级别为DEBUG或TRACE,你可以获得关于事务管理的更多详细信息。这有助于你诊断问题所在。
(10)检查数据库约束和触发器:有时,数据库约束(如唯一性约束、外键约束等)或触发器可能会导致插入失败,即使你的代码逻辑看起来是正确的。确保检查数据库的任何相关约束和触发器。
具体原因待实验出来!