调用mybatisPlus的 Iservice中的save方法,后台日志打印出来Insert语句,但是没插入到数据库表中;
问题:调用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)检查数据库约束和触发器:有时,数据库约束(如唯一性约束、外键约束等)或触发器可能会导致插入失败,即使你的代码逻辑看起来是正确的。确保检查数据库的任何相关约束和触发器。
具体原因待实验出来!
![](https://images.cnblogs.com/cnblogs_com/isme-zjh/1665628/t_200309175526zhifubao.png?a=1583807649478)
您的资助是我最大的动力!
金额随意,欢迎来赏!