开发中遇到的问题和经验 记录 ------- 后端篇

核心价值就是把现实世界的业务操作搬到计算机上,通过计算机软件和网络进行业务和数据处理,但是时至今日,能用计算机软件提高效率的地方,几乎已经被全部发掘过了,必须能够发掘出用户自己都没有发现的需求,必须洞悉用户自己都不了解的自己。于是出现了:

大数据 + 机器学习”

本文致力于解决日常遇见的坑和一些个人经验,解决不必要的坑 耽误开发效


目录

1、 技巧:向spring注入对象:@configuration 的使用:

2、DB2分页查询

3、多线程的快捷写法

注意的点:实现多线程的两种方式:

线程涉及到的函数:

4、同样的项目,同样的数据库,但拷贝到另一个电脑,登录失败

5、上传文件、下载文件

6、关键词:ArrayList  toString() 空格  容易踩的坑

7、Entity转换Vo对象 技巧。po转vo

8、sql查询玄学。关键词:sql limit  hive 

9、前端传后端数据。错误 400,排除技巧

情况一:前端传json格式的数据

情况二:前端传get请求携带的参数

10、排查工具的使用:Arthas、jvm总结

11、json转java对象 技巧

 12、加空字符串的妙用  "对象"+""

13、输出数组 技巧。

14、界面点击按钮不生效,往往是js文件加载错,文件名写错导致的

15、项目本地访问页面正常,部署到服务器上访问不到。

16、应用部署 tomcat部署 和Liberty部署

17、数据库查询 和 hibernate查询的 条数一样,但部分行不一样。

18、linux上查找应用程序所在的 文件夹

19、Idea在debug模式下,停止当前函数(不执行断点后的代码)

20、相同项目代码,运行在两个环境,一个正常执行,一个等待很久才报错

 21、tomcat部署后新war包后,重启后代码似乎没生效

22、java解析json中某个字段

23、SpringBoot的启动原理

24、mybatis#{}和${}的区别

两者的区别

25、编写单元测试,@Autowired注入的对象为null问题。 

26、动态加载配置文件

27、接口开发 、其他系统的接口请求、postman请求可以,java请求有问题。

28、linux查看日志小技巧。

29、SpringBoot配置文件中的值读取、@values注解

30、开发心得、良好的习惯、易维护的代码。

31、部署jar包应用的偷懒技巧。

32、idea必备快捷键

33、guava学习和使用

34、不常见,但很有用的注解

35、PostMan使用技巧

36、mysql 分页需要oder by 吗?需要

36、java CURD mysql时 字段带"-",即短横问题

37、Git命令:

38、Collectors.toMap()、XX.stream().flatMap()的使用

39、myabtis报错:没有XX字段

40、mysql 表被锁

 41、事务注解不生效:

42、jackson 对象转换成字符串,日期问题

43、ThreadLocal修饰符的作用:

44、sql为某字段设置默认值、修改字段类型、sql自动设置更新时间

45、java调用python模型、服务

46、springBoot配置主从数据的配置文件:

47、sprignboot 配置nacos

48、通过断点调试-查看数据源 (借助mybatis的mapper)

49、任务调度xxj-box:

步骤1:maven依赖:

步骤2:bootstrap.properties

步骤3:配置类bean注入 

步骤4:使用样例:

步骤5:登录xxl-job的控制台去查看:

步骤5:在 任务调度中心 配置我们的这个执行器 上面时候执行,执行时携带什么参数:

50、springBoot加载配置文件相关问题

51、springboot 日志只输出 springboot和mybatis的的logo

52、观察接口报警的工具-可视化工具

53、工具-将大文件划分成小文件

54、springBoot记录日志

55、springboot 配置ncoas,指定配置不生效 

56、谷歌API、aws亚马逊API

1、谷歌翻译

 2、谷歌认证实现

 aws亚马逊api调用技巧:

59、get请求,下划线请求参数封装进对象

57、查询日志es的kibaba

58、命令行查看本地Maven仓库地址 linux环境下:

59、springboot加载resource文件夹下 的自定义文件

60、jar 包在后台运行

61、架构师的素养

62、redis技巧、缩短key长度

63、对象集合list 存成csv文件

64、查询单表因数据量较大,导致sql耗时太久而失败。

65、如何生成优雅的listbug:​编辑

66、JSON与对象转换的坑 

67、列表分批处理小技巧

68、限流器 RateLimiter

69、在idea排查CPU、内存OOM的方法

70 、记录一次OOM事故

71、通用的简易网络客户端模板




1、 技巧:向spring注入对象:@configuration 的使用:

(关键词:多数据源  jdbcTemplate @configuration)

 场景:一个项目有多个数据源时,xml里配置了多个数据源。通过@configuration 的使用,其他类直接以 @Authoried方法注入到某类中,进行引用。

1、spring的配置xml文件(即是 web.xml中配置的spring配置文件的那个文件)如:

。。。。。
//一个数据库连接池 db2数据库
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destory-method="close">
    <property name="dirverClassName" value="com.ibm.db2.jcc.DB2Driver">
    <property name="url" value="jdbc:XXXXXXXXXXXX">
    <property name="username" value="XXXX">
    <property name="password" value="XXXX">
    .....//略 具体的数据连接池的细节配置
</bean>

//另一个一个数据库连接池 mysql数据库
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" destory-method="close">
    <property name="dirverClassName" value="com.mysql.jdbc.Driver">
    <property name="url" value="jdbc:XXXXXXXXXXXX">
    <property name="username" value="XXXX">
    <property name="password" value="XXXX">
    .....//略 具体的数据连接池的细节配置
</bean>
。。。。。

 2、使用@configuration注解注入到spring容器中,这样在启动项目的时候,spring会自动将DB2JdbcTemplat、mysqlJdbcTemplate 注入到容器中,示例如下:

@Configuration
public class JdbcTemplateConfig {

    / *Bean 用于把当前方法的返回值作为对象存入spring的ioc容器中
      属性:name 作用:用于指定baan的id,默认值为方法名 */
    @Bean(name ="DB2JdbcTemplate")
    public JdbcTemplate myBean(@Qualifier("dataSouce1")DataSource dataSource) {
        return  new JdbcTemplate(dataSource);
    }

    @Bean(name ="mySqlJdbcTemplate")
    public JdbcTemplate myBean(@Qualifier("dataSouce2")DataSource dataSource) {
        return  new JdbcTemplate(dataSource);
    }
}

3、使用注入的数据源 应用示例:

@Repository
public class myDaoImpl{

 @Autowired
 @Qualifier("mySqlJdbcTemplate")
 privare JdbcTemplate mySqlJDBC;

 @Autowired
 @Qualifier("db2JdbcTemplate")
 privare JdbcTemplate db2JDBC;




//查询示例
public List<User> findUser(String name){
 //此sql是原生的sql,即可以在数据库执行的sql
 String sql = "select * from user where name=?"
 // name处 那个参数 其实是可变参数,一般把参数多的情况下放入到list中,使用list.toArray()
   List<user>  list =db2JDBC.query(sql,new BeanPropertyRowMapper<>(User.class),name);
 
}


//插入示例
public void create(final String name,final int age){
  String sql="insert into user(id,name,age) value(?,?,?)";
  db2JDBC.update(sql,new PreparedStatementSetter(){
   @Override
   public void setValues(PreparedStatement ps)throw SQLException{
    ps.setString(1,UUIDUtil.genericUUID());
    ps.setString(2,name);
    ps.setInt(3,age);
   }
     
 })


//删除示例
public void deleteData(String id){
   String sql="delete user where id=?";
   db2JDBC.update(sql,id);

}

}



......
具体函数中 使用jdbcTemplate的方法即可:
常用的有:(技巧:可以 在sql对每个字段进行 参考 XXEntity属性  进行取别名 就可以直接映射进去了 如 select aa  student_age from XX  解释: 数据库aa表示学生年龄的字段,实体属性为 studentAge)

1、query(将参数都拼接到字符串的sql语句, new BeanPropertyRowMapper(XXEntity.class))
2、query(?代替参数sql语句, new BeanPropertyRowMapper(XXEntity.class),(参数放入到)数组)



}

Spring @Configuration 注解介绍 - 简书

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        // instantiate, configure and return bean ...
    }
}

2、DB2分页查询

公司使用的数据库是db2,不像mysql那样 可以使用 limit  0,10 这样的语法。

所以需要使用:给查询出的结果加 一列“序号”,再对序号进行区间选取:

select * from (
select row_number() over( order by XXX) as rownum, a.*,b.* from tableA left join tableB on
tableA.id =tableB.id where 1=1 XXXXX拼接条件
)where rownum>? and rownum<=?

注意:

1、row_number() over(这里一定要排序否则每次查出来的顺序可能都在变化,导致每页可能存在重复的数据)。

2、 where rownum>? and rownum<=? 一定是单独存在的一个条件,切勿拼接到 条件组合的where中,否则会导致 有时候查出来的数据也正常,有时候不正常,why?因为对查询的结果进行排序,同时还对行号进行筛选,就会导致筛选出的结果的序号 可能不是连续的 。

3、记得,db2 分页是取 区间。而支持limit的数据库 limit 起始位置,每页取的个数。 别记混了。

4、经验:能在java处理的尽量用程序处理,数据库关联2张表,再多就不太好了,应有java小批量查询处理的思想。


3、多线程的快捷写法

使用lamba表达式:(不必再去单独写继承runable的线程类,再重写run方法,现在直接通过:向Thread类传入 ()->{ 要运行的程序},线程名称     即可)

package com.alipay.sofa.boot.examples.demo;

public class MultiThread1 {

    public static void main(String[] args) {
        //  lamba表达式  ()->创建对象的意思
        new Thread(() -> {
            fun("线程11111");
        },"线程11"
        ).start();

        new Thread(() -> {
            fun("线程2222222222");
        },"线程22"
        ).start();
    }


    static void fun(String threadName){
        for (int i = 0; i < 1000; i++) {
            System.out.println("执行"+threadName);
        }
    }
}

 原始的写法:

        1、实现runable接口:

package com.alipay.sofa.boot.examples.demo;

public class MultiThread {


    static class MyThread implements Runnable {
        private String threadName;

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("执行" + threadName);
            }
        }

        public MyThread(String threadName) {
            this.threadName = threadName;
        }
    }

    public static void main(String[] args) {

        MyThread myThread1= new MyThread("线程111");
        MyThread myThread2= new MyThread("线程22");

        // 需要首先实例化一个 Thread,并传入自己的 MyThread
        Thread thread1 = new Thread(myThread1);
        Thread thread2 = new Thread(myThread2);
        
        thread1.start();
        thread2.strat();


    }
}

2、使用继承Thread的方式实现:

package com.alipay.sofa.boot.examples.demo;

public class MultiThread2 {


    static class MyThread extends Thread {
        private String threadName;

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("执行" + threadName);
            }
        }

        public MyThread(String threadName) {
            this.threadName = threadName;
        }
    }

    public static void main(String[] args) {

        MyThread myThread1= new MyThread("线程111");
        MyThread myThread2= new MyThread("线程22");

       //继承Thread不用再放入到Thread中

        myThread1.start();
        myThread2.start();


    }
}

三种方式的的运行结果都是这种交替出现的结果:

注意的点:实现多线程的两种方式:

①类A继承Thread类,重写run方法,调用时直接 new A().strat() 即可

②类A实现  Runable接口,实现run方法,调用时 需要把A传入Thread类中。如: new Thread(new A()).strat()

lamba表达式:  new Thread(()->{ 线程执行的语句 }).start()

线程涉及到的函数:

  1.  sleep()和 wait() :  Thread.sleep(1000), 不释放同步锁, wait()是Object方法,需要通过notify、notifyAll唤醒;
  2. yield() 使当前线程让出cpu,进入到就绪队列中,存在刚到就绪队列又被拿去执行的情况。
  3. join(),通过用于当前线程等到其他线程的结束。如:
    public class Example {
        public static void main(String[] args) throws InterruptedException {
            // 创建并启动第一个线程
            Thread t1 = new Thread(() -> {
                System.out.println("Thread t1 is running");
                // 语句块;
            });
            t1.start(); // 启动 t1 线程
            t1.join();  // 主线程等待 t1 线程完成
    
            // 创建并启动第二个线程
            Thread t2 = new Thread(() -> {
                System.out.println("Thread t2 is running");
                // 语句块;
            });
            t2.start(); // 启动 t2 线程
        }
    }
    
    
    --------------------------------------------------------------------
    
    或者:
    Thread t1=new Thread(()->{ 
     语句块;
    }) ;
    
    
    Thread t2=new Thread(()->{ 
    t1.join();
    语句块;
    }).start() ;
    
    在t2线程里调用t1,表示 t1执行完成 才执行t2

    一、wait()和sleep()的区别
    1、wait()是object类的方法,sleep()是Thread类的方法,

    2、wait()使线程进入等待状态,并且释放了锁,使得其他线程能使用同步控制块或方法,需要使用notify()或notifyAll()方法来唤醒线程,进入就绪状态,再去抢占cpu资源;

    sleep()方法是线程进入睡眠状态,不释放锁;在睡眠时间用完后或使用interrupt()方法中断,线程唤醒进入进入就绪状态;(sleep()方法只是让出CPU,并不会让出同步资源锁)

    3、sleep()方法必须捕获异常,wait()、notify()、notifyAll()方法同样必须需要捕获异常;

扩展:FutureTask的使用

上面提到的都是 基本线程的用户,实际开发过程中都是使用线程池,一般使用如下:

 public void startTask() {
        List<String> siteCampinArnList = this.getSiteCampinArn();
        List<FutureTask<String>> taskList = new ArrayList<>();
        for (String site : siteCampinArnList) {
            FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
                @Override
                public String call() throws Exception {
                     //todo 要运行的函数内容
                    return site+"成功";
                }
            });
            executorService.execute(task); //这里是 线程池 通过bean注入
            taskList.add(task);
        }
    }


///
线程池的使用

    @Bean
    public ExecutorService executorService(){
        ThreadFactory springThreadFactory = new CustomizableThreadFactory("openserch-pool-");
        return  new ThreadPoolExecutor(8, 8, 1,
                TimeUnit.MINUTES,
                new java.util.concurrent.LinkedBlockingQueue<Runnable>(),
                springThreadFactory);
    }

扩展:如何让线程按顺序输出

1、使用线程池,该线程池只有一个线程,每个任务用submit提交到线程池。

2、使用join()  每个线程,调用自己的join()函数。本质上是,主线程等待子线程完成,再顺序执行余下的线程。注意的是:

        //这样是无效的, 调用start() 就意味着 这个线程可能已经在执行
        thread1.start();
        thread2.start();
        thread3.start();
        //再调用join() 已经无用了
        thread1.join();
        thread2.join();
        thread3.join();


//正确的写法是:
 thread1.start();
 thread1.join();

 thread2.start();
 thread2.join();

 thread3.start();

扩展2:如何判定线程是否执行结束? 

方法①:线程实例.isAlive();  线程启动后,只要没有运行完毕,都会返回true

a.join也可以,表示a执行完前,后面的执行都挂起

方法②:使用Thread.activeCount()方法判断当前线程的线程组中活动线程的数目,为1时其他线程运行完毕

方法③:使用线程池java.util.concurrent.Executors,线程池启动后,执行线程池的shutdown()方法,即在所有线程执行完毕后关闭线程池,也能检测到线程关闭


4、同样的项目,同样的数据库,但拷贝到另一个电脑,登录失败

 场景:新人入场,我拷贝了一份旧代码作为练习项目发给他,结果我的电脑可以运行,可以登录进系统。他的电脑却死活登录不进去。

 原因:maven部分依赖的jar包未导入项目,导致登录失败。

注意:把自己的maven拷贝到新人电脑上,他的maven不一定能下载下来jar,原因是 可能你1年前下载的,公司私服上已经删除了 但是你本地仓库已经下载了,所以你的项目并不缺少,但是新人使用你的项目,pom文件依赖的jar 可能在远程仓库上不存在了。

扩展:maven知识:

 maven的配置文件:setting.xml

 1.profile
profiles下面可以配置多个repositories,用profile下不同的id进行区分。当不设置activeProfiles时,配置了多个profile时,默认是都有效,会依次进行尝试下载。

2.mirrors:要去下载jar包的地址
mirror是仓库的镜像备份,通过mirrorOf配置来拦截对应的repositories,想要拦截特定的repositories,就在mirrorOf配置上repository的id进行拦截,也可以配置*来拦截所有仓库,在不配置仓库时默认的仓库id为central。
3.server :认证时用,用于私服认证
当仓库需要认证时,需要配置,server的id需要与repositories保持一致生效。很多大佬说server可以和pom文件里的repositories可以一起使用,不清楚能否和settings里的repositories一起使用。

额外的:有的项目需要依赖自己的jar或者模块,所以运行项目时,先lifecycle中先mvn install一下。  


5、上传文件、下载文件

前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/donwFileTest">下载</a>


<form  enctype="multipart/form-data" method="post">
    <span>上传</span>
    <input id="myUpload" name="file" type="file">
</form>



</body>
</html>

<script>
    $("#myUpload").on('change',function (){
        console.log("上传");

        //获取文件  不可省略取【0】
        var file =$('#myUpload')[0].files[0];
        var formData = new FormData();
        formData.append("file",file);
        $.ajax({
            url:"/uploadFileTest",
            dataType:'json',
            type:'POST',
            async: false,
            data: formData,
            processData : false, // 使数据不做处理
            contentType : false, // 不要设置Content-Type请求头
            success:function (result){
                alert("上传成功了")
            }
        })
    })
</script>

上传文件:

package com.alipay.sofa.boot.examples.demo.controller;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;


@Controller
public class DownFile {

    @RequestMapping("/index")
    public String index(HttpServletResponse response) throws IOException {

        return "index1.html";
    }

   

    @RequestMapping("/uploadFileTest")
    public void uploadFileTest(Model model, @RequestParam(value = "file", required = false) MultipartFile file) throws IOException {
        if (!file.isEmpty()) {
            byte[] buffer = new byte[1024 * 1024];
            int byteread = 0;
            FileOutputStream fs = new FileOutputStream("C:/Users/wjw\\Documents\\资料\\fileName");
            fs.write(buffer, 0, byteread);
            
        } else {
            model.addAttribute("result", "上传失败");
            return;
        }

    }
}

下载文件所在的目录:

 下载文件代码:

package com.alipay.sofa.boot.examples.demo.controller;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;



@Controller
public class DownFile {

    @RequestMapping("/donwFileTest")
    public  void downFile(HttpServletResponse response) throws IOException{

        //该文件存在项目 resources文件夹下:
        String fileName="file.txt";

        //设置强制下载不打卡
        response.setContentType("application/force-download");
        response.addHeader("Content-Disposition","attachment;fileName="+fileName);

        //读取文件
        ClassPathResource cpr = new ClassPathResource(""+fileName); //路径从resources下开始
        InputStream is=cpr.getInputStream();
        IOUtils.copy(is,response.getOutputStream());
        response.flushBuffer();

        //关闭流
        if(is!=null){
            is.close();
        }
    }
}

6、关键词:ArrayList  toString() 空格  容易踩的坑

场景:有时候想要使用获取list的元素拼接成字符串,于是:

list.toString().replace("[",").replace("]","");

问题:这种拼接 会在每个元素前有一个逗号和空格,后面再使用此字符串会出现问题。

解决:推荐使用原生自带的拼接

String str = String.join(",",myList);


7、Entity转换Vo对象 技巧。po转vo

后台查出实体对象,往往需要转换成Vo对象,向前台展示,实际开发中,很多时候 Vo的字段与实体对象的字段名是有重复的,如果遍历 List<entity> 一个一个set到Vo对象,十分繁琐,这时候可以使用:BeanUtils.copyproperties(entrity,nXXVo());

//spring提供的工具类

for(User user:userList){
    BeanUtils.copyproperties(user,new UserVo());
}

 注: 需要实体的字段、类型 和Vo的字段、类型都一致 才能设置进去,否则设不进去。对应字段不一样的 可以手动set

经验:此方法对后续维护其实不是很方便 


8、sql查询玄学。关键词:sql limit  hive 

场景:查询hive表。使用limit n,m 进行分页。得到的数据不符合预期;具体原因:

user表中符合王二的数据有100+条,如下sql,但取出来的数据 只有18条。和预期20条不符合。

select * from user  where name like '王二%'  limit 0,20 //取前20条数据

原因:移除关键字查询 查询结果符合预期:查出来了20条数据

select * from user    limit 0,20 //取前20条数据

分析:既然移除关键字的拼接 就能正常查询,不移除就不行。那应该是这个关键字有问题。

后来发现 hive  一般是数据文件或者解析导入的数据,存在某些字段的数据可能有空格(坑啊)。

所以对应hive表的关键字查询 需要对关键字段名加trim(). 如下:

select * from user  where name like '王二%'  limit 0,20 //取前20条数据  原先的如果user某行name列存在 “ 王二” 这时候就查不出来。

所以对应hive表 关键字查找的 均一股脑的:(只适用于字符串 类型的,日期类型的不可以加)

 select * from user  where trim(name) like '王二%'  limit 0,20 

注:trim(表字段XX),表示对该列 XX的数据均去空格处理


9、前端传后端数据。错误 400,排除技巧

场景:前端向后台传参数,后台用对象接收,报错400。

原因:一般是因为前端传参不符合 接收对象的属性。即:不匹配。但是传参多时,一个一个核对很耗时,也不一定能核对出来原因。

技巧: 在后台参数列表里,多加一个:BindingResult ,可以查看具体不匹配的字段。

public String list(BindingResult b,Model model, User user){
}

情况一:前端传json格式的数据

后端:使用 @RequestBody注解 标记在 依据json对象转的java对象 即可

情况二:前端传get请求携带的参数

 后端:使用@RequestParam注解 可以指定参数绑定到当前参数,并可设置默认值

 @RequestParam 可以用来接收Get POST类型的请求

@RequestBody 可以用来接收POST请求json格式的参数

10、排查工具的使用:Arthas、jvm总结

调优的目的:为了减少SWT,即:stop world time。 每次发生full gc 都会造成系统的卡顿,所以要尽量避免,full gc.

一般的性能调优都是jstack,但比较好用的是阿里巴巴开发的 Arthas 性能调优非常强大,

官方文档:快速入门 — Arthas 3.5.4 文档

使用方法:

1)、下载jar包

2)、 运行jar包 

        java -jar arthas-boot-jar

 3、输入 你想要查看 某jar应用程序的 序号,再回车,会出现:

4、比如上图 发现 ID 为8的线程占用cpu 88%,所以现在查看该线程,使用命令 thread 8 即可打印出现移除的源代码:

2、常遇到的问题:系统应用频繁的full gc,导致用户体验差。

分析:性能调优,不得不先了解jvm的内存模块:

①堆:

  • 线程共享,是minor gc 和full gc 垃圾回收的主要地方。
  • 空间划分:新生代老年代 。默认占内存空间 1:2
    • 新生代又分:eden区、s0区 、s1区。默认占空间比例是8:1:1
  •  标记整理算法:是老年代的

  •  深刻讨论:

  • 问题1:为什么堆内存要分为新生代和老年代?

    • 答:因为JAVA对象90%以上的对象都是朝生夕死的,其中GC回收的成本很高,为了提高性能所以将新生成的对象放在Eden区,将扛过多次GC的“老家伙”放在老年代

  • 问题2:那为什么新生代还需要继续细分?

    • 答:因为Eden区的绝大部分对象寿命很短,那么Eden每次满了清理垃圾,存活的对象被迁移到老年区,老年区满了,就会触发Full GC,Full GC是非常耗时的,设立s区的一个目的就是在Eden区和老年代中增加一个缓冲池,放一些“年纪不够老”的对象,增加垃圾回收性能

  • 问题3:触发GC的流程

    • 答:GC分为 minor GC 和Full G

               minor GC: 新生对象都会放在eden区,当eden区放满,触发minor gc,将eden区还存活的对象age+1放到s0区,然后清空 enden区和s1区,然后继续。。eden区再满时,将eden还存活的对象 和s1区还存活的对象age+1,然后放到s0区,再清空eden区和s`。。。(期间age>15的对象就会被放入到老年代,大对象(占from(to)区内存空间的一半以上都算大对象)也会放入到老年代)就这样周而复始,直至某一次 enden区的对象+ to/from区的对象移到 from/to区时,放不下了,就会把对象都放入到老年代。清空新生代,继续执行上述。

                Full GC:最终老年代越放越多,直至放不下,就会触发full gc。

                minor GC的S0和S1区的设置为了解决复制算法的碎片化

                所以,通过上述会发现,频繁造成full gc的原因是因为:新生代区总是放不下,频繁地向老年代放对象导致的,所以如果我们增大 新生代区的内存空间,新生代区的空间 大到基本能够支撑 业务线程执行完后的下一个周期的时间间隔((因为线程执行完,局部变量会立刻被回收,而该线程中对象并不会立刻回收,而是要等到一次 gc,gc发现这些对象被root引用,就视为垃圾才回收)。该回收的对象也基本都会在minor gc 就给回收掉了,所以很少有对象能够放到 老年代,自然也会导致full gc的频率下降!!。

        所以解决的办法是: 把 新生代 和老年代 默认的比例1:2 改大一点,根据业务线程评估会new对象,基本是按一个对象1kb累计,最后评估的结果再放大10-1000倍,就是一次线程执行完一次应该占用的 内存,在知道新生代和老年代占空间的大小下,就可以粗略算出minor gc 和 full gc 触发的频率。 

 具体场景:假如堆的空间有3G,则新生代1G,老年代1G,enden取800M,to 区100M,from区 100M,假如订单系统,每秒会有400个请求,一个订单系统线程可能创建10kb的空间占用,所以每秒会在eden区生成:400*10kb/1024~=4M,  为了避免没考虑到的对象,所以放大10倍,所以 每秒有 40M存放到enden区,所以大约 800M/40M=20s 就会造成一次 minor gc  ,或者说20s后这些占40M空间的基本会被gc回收掉(因为一两秒之后 这40M很有可能变成垃圾了,因为线程(不是复杂的业务线程 基本都能很看执行结束)可能已经结束了,属于该线程的局部变量已经被回收了,但该线程的创建的对象也失去了root引用,就等gc执行时把他给回收了,所以 这40M的(存储对象的空间)就应该被回收了)但有可能个别线程因某个原因执行的稍稍慢一点点,eden回收了大部分的空间,如19个40M,但还有一个40M正在被线程持有,这个40M就该移动到to/from区了。但40M (可能大于to/from区的一半则)视为大对象,就会放入到了老年代。频繁导致full gc的原因就在这里!! 

总结:几分钟、几小时就触发了full gc。原因是因为: 业务线程引用的对象所占的空间 大于或等于了 s0/s1区 的一半,容易被直接放入老年代。

解决方法: 调整 新生代、老年代的比例,以及 eden 、s0、s1区的比例!。

②方法区(元空间)

存放类编译后的信息,如静态变量、类的属性

③程序计算器

  • 线程私有
  • 用于控制线程执行字节码的行号指示器,告诉cpu下一条执行该执行谁了,由字节码执行器来负责修改其内容。(因为java是多线程,执行到一半cpu可能干其他事情去了,等cpu有资源时,要根据程序计算器继续干接下来的工作)

④虚拟机栈

详细可参考:虚拟机栈的栈帧都包含些什么?_yozzs的博客-CSDN博客_虚拟机栈存放什么数据

  • 描述jvm执行方法的内存模型,每次调用一个函数都对应 一个 “栈帧”压入虚拟机栈,函数调用结束又表示,该函数调用完成。  
  • 帧栈,主要是 函数的相关信息:包含了

        ①  局部变量表(因为局部变量属于函数嘛,都是临时工 要在这里登机一下咯)

        ②  操作数栈(从局部变量表里取 某变量,在这里进行 加减乘除的运算)

        ③ 动态链接(理解不够深刻):将符号变量替换成直接引用

        ④ 方法出口.(计算完结果 把结果返回到 上一个压入栈的 帧的地址,也可能计算途中遇到异常,所以可能返回  异常出口的地址、或者 方法正常执行完成的 返回的地址)。

⑤本地方法栈 

和虚拟机栈几乎一样,只不过这里调用的都是 native方法 都是执行c++或者c方法。

(135条消息) 五位卷王 | 总结的十道 JVM 面试真题!(建议收藏)_hzbooks的博客-CSDN博客

11、json转java对象 技巧

使用alibaba的fastjson 即可:


JSONbject.toJavaObject(XXX,XXXX.class);//参数1是JSON对象  参数2是要封装进去的对象类文件
//单独使用
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<XXXXX> rootProducts = objectMapper.readValue(data, new TypeReference<>() {});

 个人倾向使用fastJson但的确存在不少未知bug,踩坑到哭

 使用jackjson的 工具类(推荐使用)

fastjson的坑:is开头接收boolean类型的会解析不到!

(对data好像也解析不到,未测试,但见有此现象)

@Slf4j
public class JsonUtil {
 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static{
//对不存在的数据字段 解析不到不报错
 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
 public static String getValue(String content, String key) {
        String value = "";
        try {
            JsonNode jsonNode = OBJECT_MAPPER.readTree(content);
            jsonNode = jsonNode.get(key);
            if (Objects.nonNull(jsonNode)) {
                value = jsonNode.toString();
            }
        } catch (Exception e) {
            log.error("get value from json failed! key[" + key + "].", e);
        }

        return value;
    }

    /**
     * 对象转json字符串
     *
     * @param object
     * @return
     */
    public static String convertJson(Object object) {
        try {
            return OBJECT_MAPPER.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            log.error("get json from value failed! value[" + object.toString() + "].");
            return "";
        }
    }


    /**
     * @param content json字符串
     * @param valueType class
     * @param <T> 泛型
     * @return 该类型
     */
    public static <T> T convertObject(String content, Class<T> valueType) {
        try {
            return OBJECT_MAPPER.readValue(content,valueType);
        } catch (JsonProcessingException e) {
            log.error("get object from json failed! json[" + content + "].",e);
            return null;
        }
    }
}

使用方法:

json转对象

 通过key获取value

注:有时候要解析比较特别的json字符串:如下:(即:带转义的字符串型json)

"{\"platform_id\":\"10000\",\"site_id\":\"20000\",\"store_id\":30000,\"datas\":[{\"product\":{\"product_id\":\"5203225\",\"app_id\":\"10000\",\"product_code\":\"17288278\",\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"category_id\":\"3105\",\"front_category_id\":null,\"price\":\"24.99\",\"msrp\":\"66.99\",\"middle_east_price\":\"30.99\",\"id\":\"5203225\",\"operator_id\":0,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"group_id\":2065087,\"image_id\":3683062},\"skus\":[{\"id\":19700443,\"product_id\":\"5203225\",\"code\":\"CN-1075-5203225-19700443\",\"sku_code\":1728827801,\"price\":\"24.99\",\"msrp\":\"66.99\",\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"image_id\":3683062,\"group_id\":2065087},{\"id\":19700444,\"product_id\":\"5203225\",\"code\":\"CN-1075-5203225-19700444\",\"sku_code\":1728827802,\"price\":\"24.99\",\"msrp\":\"66.99\",\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"image_id\":3683062,\"group_id\":2065087},{\"id\":19700445,\"product_id\":\"5203225\",\"code\":\"CN-1075-5203225-19700445\",\"sku_code\":1728827803,\"price\":\"24.99\",\"msrp\":\"66.99\",\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"image_id\":3683066,\"group_id\":2065087},{\"id\":19700446,\"product_id\":\"5203225\",\"code\":\"CN-1075-5203225-19700446\",\"sku_code\":1728827804,\"price\":\"24.99\",\"msrp\":\"66.99\",\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"image_id\":3683066,\"group_id\":2065087}],\"skuApp\":{\"1728827801\":{\"app_id\":\"10000\",\"sku_id\":19700443,\"sku_code\":1728827801,\"product_id\":\"5203225\",\"product_code\":\"17288278\",\"status\":2,\"color_scheme\":\"Beige\",\"created_at\":\"2020-10-28 11:24:53\"},\"1728827802\":{\"app_id\":\"10000\",\"sku_id\":19700444,\"sku_code\":1728827802,\"product_id\":\"5203225\",\"product_code\":\"17288278\",\"status\":2,\"color_scheme\":\"Beige\",\"created_at\":\"2020-10-28 11:24:53\"},\"1728827803\":{\"app_id\":\"10000\",\"sku_id\":19700445,\"sku_code\":1728827803,\"product_id\":\"5203225\",\"product_code\":\"17288278\",\"status\":2,\"color_scheme\":\"Grey\",\"created_at\":\"2020-10-28 11:24:53\"},\"1728827804\":{\"app_id\":\"10000\",\"sku_id\":19700446,\"sku_code\":1728827804,\"product_id\":\"5203225\",\"product_code\":\"17288278\",\"status\":2,\"color_scheme\":\"Grey\",\"created_at\":\"2020-10-28 11:24:53\"}},\"product_code\":\"17288278\",\"platform_id\":\"10000\",\"site_id\":\"20000\",\"store_id\":30000,\"translation\":[{\"id\":1244908,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Russian\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:16\",\"updated_at\":\"2020-10-28 11:25:16\"},{\"id\":1244907,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Swedish\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:13\",\"updated_at\":\"2020-10-28 11:25:13\"},{\"id\":1244906,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Dutch\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:11\",\"updated_at\":\"2020-10-28 11:25:11\"},{\"id\":1244905,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Portugal\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:09\",\"updated_at\":\"2020-10-28 11:25:09\"},{\"id\":1244904,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"ChineseTraditional\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:07\",\"updated_at\":\"2020-10-28 11:25:07\"},{\"id\":1244903,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"German\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:04\",\"updated_at\":\"2020-10-28 11:25:04\"},{\"id\":1244902,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Italian\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:02\",\"updated_at\":\"2020-10-28 11:25:02\"},{\"id\":1244901,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"French\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:25:00\",\"updated_at\":\"2020-10-28 11:25:00\"},{\"id\":1244900,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Spanish\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:24:58\",\"updated_at\":\"2020-10-28 11:24:58\"},{\"id\":1244899,\"app_id\":10000,\"product_id\":5203225,\"product_code\":17288278,\"name\":\"hbdtgsbhg\",\"description\":\"dgfsbhfds\",\"language\":\"Arabic\",\"status\":4,\"user_id\":0,\"created_at\":\"2020-10-28 11:24:55\",\"updated_at\":\"2020-10-28 11:24:55\"}],\"operator_id\":0,\"id\":\"5203225\",\"sku_images\":[{\"product_id\":\"5203225\",\"sku_id\":19700443,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf087b8e.jpg\",\"sort\":0,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683061},{\"product_id\":\"5203225\",\"sku_id\":19700443,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"sort\":1,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683062},{\"product_id\":\"5203225\",\"sku_id\":19700443,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf394927.jpg\",\"sort\":2,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683063},{\"product_id\":\"5203225\",\"sku_id\":19700443,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf523de2.jpg\",\"sort\":3,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683064},{\"product_id\":\"5203225\",\"sku_id\":19700444,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf087b8e.jpg\",\"sort\":0,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683061},{\"product_id\":\"5203225\",\"sku_id\":19700444,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"sort\":1,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683062},{\"product_id\":\"5203225\",\"sku_id\":19700444,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf394927.jpg\",\"sort\":2,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683063},{\"product_id\":\"5203225\",\"sku_id\":19700444,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf523de2.jpg\",\"sort\":3,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683064},{\"product_id\":\"5203225\",\"sku_id\":19700445,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1999cc5.jpg\",\"sort\":0,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683065},{\"product_id\":\"5203225\",\"sku_id\":19700445,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"sort\":1,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683066},{\"product_id\":\"5203225\",\"sku_id\":19700445,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1d3a24b.jpg\",\"sort\":2,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683067},{\"product_id\":\"5203225\",\"sku_id\":19700445,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1e3dc97.jpg\",\"sort\":3,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683068},{\"product_id\":\"5203225\",\"sku_id\":19700446,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1999cc5.jpg\",\"sort\":0,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683065},{\"product_id\":\"5203225\",\"sku_id\":19700446,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"sort\":1,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683066},{\"product_id\":\"5203225\",\"sku_id\":19700446,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1d3a24b.jpg\",\"sort\":2,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683067},{\"product_id\":\"5203225\",\"sku_id\":19700446,\"src\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1e3dc97.jpg\",\"sort\":3,\"template_no\":1,\"scale_type\":1,\"group_id\":2065087,\"image_id\":3683068}],\"product_icons\":[{\"product_id\":\"5203225\",\"scale_type\":1,\"template_no\":1,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"group_id\":2065087,\"image_id\":3683062}],\"product_sku_icons\":[{\"sku_id\":19700443,\"sku_code\":1728827801,\"product_id\":\"5203225\",\"template_no\":1,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"group_id\":2065087,\"image_id\":3683062},{\"sku_id\":19700444,\"sku_code\":1728827802,\"product_id\":\"5203225\",\"template_no\":1,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993cf2ce6e2.jpg\",\"group_id\":2065087,\"image_id\":3683062},{\"sku_id\":19700445,\"sku_code\":1728827803,\"product_id\":\"5203225\",\"template_no\":1,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"group_id\":2065087,\"image_id\":3683066},{\"sku_id\":19700446,\"sku_code\":1728827804,\"product_id\":\"5203225\",\"template_no\":1,\"icon\":\"http:\\\/\\\/aaawebstatic.s3.us-west-2.amazonaws.com\\\/origin\\\/product\\\/000000000000\\\/5f993d1ad6730.jpg\",\"group_id\":2065087,\"image_id\":3683066}]}]}"

扩展:有时候想直接提取json中某个字段对应的value,可以使用以下工具类

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JsonUtils {

    public static List<String> getFieldValues(String json, String fieldName) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readTree(json);
        List<String> values = new ArrayList<>();
        getFieldValues(rootNode, fieldName, values);
        return values;
    }

    public static void getFieldValues(JsonNode node, String fieldName, List<String> values, String dateFormat) {
        if (node.isObject()) {
            node.fields().forEachRemaining(entry -> getFieldValues(entry.getValue(), fieldName, values, dateFormat));
        } else if (node.isArray()) {
            node.elements().forEachRemaining(element -> getFieldValues(element, fieldName, values, dateFormat));
        } else if (node.has(fieldName)) {
            String fieldValue = node.get(fieldName).asText();
            if (dateFormat != null && !dateFormat.isEmpty()) {
                LocalDateTime dateTime = LocalDateTime.parse(fieldValue, DateTimeFormatter.ISO_DATE_TIME);
                fieldValue = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            }
            values.add(fieldValue);
        }
    }
}


------------------------------------
使用方法:
String jsonString = "{\"users\":[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"},{\"id\":3,\"name\":\"Charlie\"}]}";
List<String> names = JsonUtils.getFieldValues(jsonString, "name");
System.out.println(names); //[Alice, Bob, Charlie]

 12、加空字符串的妙用  "对象"+""

当你使用 对象.toString() 方法时,最好使用 +"" 方式 代替,因为 对象有可能是null,执行时会导致空指针异常。但是null对象+字符串=“null”  所以还要replaeAll("null","");

13、输出数组 技巧。

把数组放入到 ArrayList中

14、界面点击按钮不生效,往往是js文件加载错,文件名写错导致的

虽然这个问题很low 但是使用idea,有时候使用Refaotr导致改文件名会不小心改动了其他的地方。 

浏览器控制台:Failed to load resouce:the XXX.js server responesd with a status of 404 即战斗资源。

15、项目本地访问页面正常,部署到服务器上访问不到。

场景:使用springmvc查询数据并返回页面。本地运行正常,但在服务器部署出现如下错误:

报错日志:Could not resolve view with name 'XXXX' in servlet with name 'springmvc'

 思路:发现 return "XXXX"  是 XXXX和实际的页面文件名称不一样,自然是找不到页面的了,可是为什么 本地可以 而服务器却不可以?难道是运行在tomcat上的项目  spring的视图解析器不区分大小写,而运行在liberty上区分大小写?

原因:只是指定servlet相对路径下的视图文件。所以是否敏感取决于servlet对文件名大小写是否敏感,或者说归根到底,取决于操作系统对文件名大小写是否敏感。

总结:无论是什么,最好return的页面和实际页面名称保持一致。莫粗心,慎用idea的查找替换

16、应用部署 tomcat部署 和Liberty部署

1、tomcat部署 ,老生常谈,把应用程序的war包放到tomcat下的 webapps,然后去上级目录的bin目录下,运行 shutdown.sh  或startup.sh即可对应用进行停、启操作

2、(IBM公司开发的)liberty部署。特点:轻量级(占内存少)、动态(更新配置文件重启项目,自己会更新) 将war包放到  XXXXX/dropins 目录下即可,然后去 XXX/servers 下执行:(发现在任意目录下 也可以执行 下面语句,可能公司的服务器配了全局变量)

server start war包名称   //不带后缀 启动应用
server stop war包名称   //不带后缀  关闭应用

参考文章:liberty | 在IDEA整合Springboot与IBM liberty_Eshare分享-CSDN博客

17、数据库查询 和 hibernate查询的 条数一样,但部分行不一样。

场景:测试测试查询条件,我在数据库里造了几条不同的数据,只改动了部分列的值。

现象:数据库查的条数 和开发的系统查的条数一致,但内容却不一致。

原因:查询的该表未设置主键,而hibernate的实体类上指定了主键。于是在查询时候,hibernate会认为相同 id值的就是同一个对象,造成  行覆盖,出现了:明明数据库有这条数据,但在java查询查出来的还是  却没有这条数据.

18、linux上查找应用程序所在的 文件夹

基本思路:找到应用程序的pid,再根据pid找到文件夹

方法1:根据应用端口号查找pid,在根据pid查找位置

//步骤1: 根据端口号找到pid
netstat -nptl |grep 9088   //快速记忆法   nptl  你怕他了

会输出:tcp6 0 0 0::9088 :::* LISTEN 29998/java 

//发现是 9088的端口 是由 pid为 29998的 进程在占用

//步骤2:根据pid找到 进程所在的目录  linux会为每个进程创建一个文件夹 在/proc目录下 pid作为文件夹名

cd /proc/29998
ls -al
会输出: 其中有:“cwd -> /webapps/wlp/usr/servers/ofp "
所以进程是在 /webapps/wlp/usr/servers/ofp 目录下

方法二:根据 ps命令查找pid,再根据pid查找位置。

ps -ef |grep ofp    
//输出 几行有 ofp的 进行信息, 第一列就是pid

方法三:top -c命令

top -c  // -c是显示 路径名称

19、Idea在debug模式下,停止当前函数(不执行断点后的代码)

场景:断点调试查看某个功能时,有时候只是为了看流程,但当其执行到操作数据库时,并不想真的去提交数据到数据库,这时候要么终止项目,再重启项目(效率低),要么使用这个方法:

 

 参考博客:

 Idea在debug模式下,直接停止程序(不执行断点后的代码)_云别-CSDN博客_idea结束debug

20、相同项目代码,运行在两个环境,一个正常执行,一个等待很久才报错

关键词:断点调试执行要等很久

场景:拷贝项目到本地,运行发现断点执行某句语句时,要等很久。

原因:一般情况是最后报 超时错误。(多翻一下报错日志,看看含time、socket字样),发现是 请求超时等问题,查看我这项目也没什么请求的啊。报错是执行查询hbase时报错的。

解决:项目使用的数据库地址是  映射的。即在host文件中配置了 具体的ip地址,而我的电脑host没有那个映射 自然 查询不到数据库导致的报错。坑爹项目!

 21、tomcat部署后新war包后,重启后代码似乎没生效

(关键字:部署war包,像没部署一样、tomcat)

场景:新打的war包,修复了bug。部署到测试环境下,但修复bug仍就出现,本地却是好的。

原因:此刻tomcat正在运行旧的war包,这时新的war包放到tomcat时,我们会以为覆盖旧的war包。再重启tomcat就可以运行最新的war包了。但事实上是:当你把新war包覆盖旧war包的时候,这时tomcat会解压旧的war包,生成 rcp文件夹(我的是rcp.war)。每次启动tomcat都运行的是 rcp文件夹的代码,即 :运行的旧的代码。

解决:删除 tomcat解压生成的 同名项目 文件夹,即删除 rcp文件夹(我的项目名叫rcp)。再重启tomcat即可。

总结:先停服务(shutdown.sh),再更新war包,再启动服务(start.sh).

文章参考:Tomcat 何时解压war包 - fatsnake - 博客园

22、java解析json中某个字段

方法1:

json字符串: {"age":"10","name":"李四","address":{"area1":"萧山区","area2":"杭州市"} }


JSONObject jsonParam =JSONObject.parseObject(json的字符串);

//getString()方法 只解析 当前一层的 key:value。 
String  age = jsonParm.getString("age");  //age=10
String  address= jsonParm.getString("address");  //address={"area1":"萧山区","area2":"杭州市"}

T object = JSONobject.toHavaObject(json字符串,T.class); //解析出T类 参数自动匹对  


注:这些方法依赖fastjson包

方法2:(推荐)但需要引入

         <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.4</version>
        </dependency>

import cn.hutool.json.JSONObject;
。。。。
 
public static void main(String[] args) {
        JSONObject jsonObject = new JSONObject("{\"name\":\"李四\"}");
        Object name = jsonObject.get("name");
        System.out.println(name); //输出李四
    }

23、SpringBoot的启动原理

知识点学习:

  1、为什么使用springBoot?(以web项目为例)

①简化配置。否则要在web.xml中注册SpringMVC的DispatcherServlet,拦截器、过滤器等

②统一管理各个组件依赖的jar包。手动维护免费、费事。

所以SpringBoot在此基础上,整合了一套快速开发的工具包。开箱即用,一行代码就能启动。

2、SpringBoot场景启动器starter。不同场景有对应的启动器,那么他的作用是什么呢?

starter的实现逻辑主要由两个基本部分组成:

xxxAutoConfiguration:自动配置类,不同场景下,自动配置类的内容就不一样。对某个场景下需要使用到的一些组件进行自动注入,并利用xxxProperties类来进行组件相关配置。    如:spring-boot-starter-模块名

xxxProperties:某个场景下所有可配置属性的集成,在配置文件中配置可以进行属性值的覆盖。

所以,引入starter后,springBoot就会在启动的时候帮我们完成相关的:自动配置、自动导入

3、SpringBoot的启动原理?

首先,查看SpringBootApplication注解结构:

  • SpringBoot在启动的时候从类路径下的(所有的)META-INF/spring.factories中获取EnableAutoConfiguration指定的所有自动配置类的全限定类名

  • 将这些自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作;

  • 整个J2EE的整体解决方案和自动配置都在spring-boot-autoconfigure的jar包中;

  • 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件,并配置好这些组件 ;

  • 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作

文章参考:图文并茂,Spring Boot Starter 万字详解!还有谁不会?

精通Spring?请吃我一狗腿!【文末送书】

扩展:@import注解,顾名思义,导入,即把类加入Spring IOC容器。 
有多种方式能让类加IOC容器管理,如@Bean、@Component等,@Import是另外一种方式,更加快捷。

@Import支持 三种方式
1.带有@Configuration的配置类(4.2 版本之前只可以导入配置类,4.2版本之后 也可以导入 普通类)        加上@Configuration是为了能让Spring 扫描到这个类
2.ImportSelector 的实现
3.ImportBeanDefinitionRegistrar 的实现

实现自己的的start:如:my-spring-boot-start

①创建项目,父pom依赖 springboot-boot-start

②创建XXXProperties 配置参数自动封装成对象的类,如叫 MyProperties.java

        可能使用到的注解有:@ConfigurationProperties(prefix = "XXX.YYY")

 ③创建XXXAutoConfiguration,如叫:MyAutoConfiguration

             注1)需将此类注入到spring中:因此使用:@Configuration

             注2)使用配置文件: @EnableConfigurationProperties(MyAutoConfiguration.class)

             注3)(@EnableConfigurationProperties,本质就是@Import注解)

              注4)通常会在此类中要注入的bean,但会依据@ConditionalOnMissingBean判断是否要注入,如果spring容器中已经存在bean了,这也是 自动注入的关键

④在resources/META-INF/spring.factories中配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
我们的类路径

24、mybatis#{}和${}的区别

两者的区别

  • #{}:使用的是预编译,对应JBDC中的PreparedStatement,井好
  • ${}:mybatis不会修改或者转义字符换,直接输出变量值

扩展:

坑:有时候 偷懒 直接传参,如:String ids ="1,2,3" 字符串作为参数  在xml中拼接xsql时

select * from XXX_table  in (#{ids}) 而没使用foreach进行拼接。

结果:不报错,但返回的数据只有一条 

总结:偷懒的写法,日志打印 只有一个?   常规的写法,日志打印 ??? 3个占位符

文章参考:#{}和${}的区别_lt_zl的博客-CSDN博客

25、编写单元测试,@Autowired注入的对象为null问题。 

场景:自己编写了一个查询的query()函数(该函数所在类名:A),想在测试类中调用 该函数,因此,我写的测试类代码如下:

@RunWitg(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(class= 启动类.class)

public class MyTest{
    
    //能注入成功,未运行时,一般会提示报错说找不到bean,不用管,运行后就不报错了
    @Autowired
    private QueryUser queryUser;
    
    @Test
    public void test(){
        /*getUser方法 中 使用了注入sqlsession对象,这个是有值的*/
        User user = queryUser.getUser();
    }

     @Test
    public void test1(){
        QueryUser  queryUser1= new QueryUser();
        /*getUser方法 中 使用了注入sqlsession对象,这个是有null的*/
        User user = queryUser1.getUser();
    }
}

对比 test和test1 可以发现,自己创建的对象queryUser1中注入其他对象,但事实上并没注入成功?为什么呢?  

个人觉得,自己创建的对象 不受spring容器控制,和spring容器是两回事,所该对象引用的一些spring的对象,自然注入不进来。(该观点可能错误,未看源码的猜测)。

解决方法:使用自动注入的对象,可以使用spring容器中的主动注入对象。(即上述代码中test方法的使用)。

后续更新:

步骤1:创建一个基类。子类去继承这个基类,就可以了

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class BaseTests {

}

步骤2:

package com.11.search.service;


import com.11.search.BaseTests;
import com.11.search.entity.ProductIndexEs;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

//继承基类 顺便写
public class myServiceTest extends BaseTests {
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    public  void ss(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.matchAllQuery());
        NativeSearchQuery build = builder.build();
        SearchHits<ProductIndexEs> search = elasticsearchRestTemplate.search(build, ProductIndexEs.class);
        System.out.println("");
    }

}

扩展注意知识点:

  •  ①子类继承父类BaseTests 的测试类 必需要是public类
  •  ②单元测试时开启事务(默认开启回滚),执行成功,但数据库并未改增删。(防止测试污染数据库)

如果想让单元测试插入数据库则需要加上@Roolback(false),如下:

    @Test
    @Transactional(rollbackFor = Exception.class)
    @Rollback(false)  //不写的话,单元测试成功后也默认回滚
   public  void test() {
        insertAll();
        quertList();
        System.out.println("hello");
    }

26、动态加载配置文件

运行中的项目,更新了配置文件,又不想重启,如何让程序自动更新加载这个文件呢?

首先,想到的是写个死循环,让这个线程一直去查看文件是否改变,改变则重新读取。但是浪费cpu资源啊。

个人想法(未实践):使用观察者模式,A改变状态,通知B去读取配置文件。即:运行的程序比如预留了一个controller的requestMapping,如:htttp://ip/item/myselfequest。当我们更新完服务器上的配置文件后,在服务器上请求一下预留的那根请求。curl "htttp://ip/item/myselfeques",通知B去重新读取配置文件。也就是手动触发。

27、接口开发 、其他系统的接口请求、postman请求可以,java请求有问题。

场景:开发稍大的系统,都会遇到 A服务器上的项目,调用B服务器上的项目。A如何像postman一样去调用呢?

解决

方法1: 使用 RestTemplate. 。常规方法

RestTemplate 用法详解_Lzc的博客-CSDN博客_resttemplate

ResrTemplate的默认请求头是:application/json

源码中的默认设置的值

 此方法:一般没什么问题,很方便,但有时候会因为请求头、协议一些问题 卡壳。。。

package com.接口请求;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class MyRestTemplete {

    public static void main(String[] args) throws IOException {

    RestTemplate restTemplate = new RestTemplate();
//
        String url = "http://localhost:8080/test1";
        Map<String,Object> map = new HashMap<>();
        map.put("name","张三");
       // Object object=  restTemplate.getForEntity(url,Object.class,map);
        Object object1=  restTemplate.postForObject(url,map,Object.class);

    }


}

文章参考:RestTemplate发送HTTP、HTTPS请求_JustryDeng-CSDN博客_resttemplate调用https接口

RestTemplate HttpMessageConverter报错的解决方案no suitable HttpMessageConverter - 未月廿三 - 博客园

此次记录一次使用RestTemplate发送 form-data的post请求:(其他几种方式没能成功)亲测可用。

public static XXX sendPostByFormData() {

        //1 创建请求对象
        RestTemplate restTemplate = new RestTemplate();

        //2 设置请求头信息
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        //3 设置请求的参数
        MultiValueMap<String,String> params = new LinkedMultiValueMap<>(); //这里是导入 import org.springframework.util.LinkedMultiValueMap包下的
        params.add("name","张三");

        //4 将参数设置进 请求实体内
        HttpEntity<MultiValueMap<String,String>> httpEntity= new HttpEntity<>(params,headers);

        //5 发起请求,拿到请求的结果   注:此次 最好使用String.class接收,之后再使用fastJson进行解析。因为如果 返回的json对象中含 数组类型的,即使我们使用List<Object>接收,会接收不到值,细看会发现list的对象中的JSONObject对象不再是HashMap而是LinkedHashMap
        ResponseEntity<String> response = restTemplate.exchange("http://localhost:8080/test1", HttpMethod.POST,httpEntity,String.class);
        String strBody = response.getBody();

        //解析对象
        XXX  xxx = JSON.parseObject(strBody,XXX.class); //XXXX为某个对象类型
        return  xxx;

    }

失败的案例记录1://请求返回200 ok,但参数解析失败

 public Object ss(){

        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("name","张三");

        RestTemplate restTemplate = new RestTemplate();
        String response =restTemplate.patchForObject("http://localhost:8080/test1",paramMap,String.class);
        System.out.println( response);//请求返回200 ok,但参数解析失败
        return response;

    }

 //失败案例2

好文参考学习 SpringBoot的HttpMessageConverter使用(1)RestTemplate中的应用 - 简书

方法2: 用postman生成代码,贴到代码中,会像在postman上测的那样。

使用postman自动生成java代码。理论上postman能够请求成功的接口,通过自动生成就能拷贝在代码中请求成功。

1/3点击code按钮,

 2/3选择编程语言:

 3/3 自己的项目依赖okhttp的jar包

         <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.8.1</version>
        </dependency>

 把代码贴到自己的项目中 就ok了! (注:导包时,都选okhttp的包)

此方法,目测是凡是 postman可以做的,都可以使用java来做。

请求接口中踩的坑:

1、要确认别人提供的接口版本是否最新,别人家修改了url,你还在傻傻用上一个版本的url,出现postman请求没问题,自己却请求接口不存在、或者解析参数错误 的这种。low坑

28、linux查看日志小技巧。

高效的查找命令,有助于快速有效定位问题。

grep命令

  小技巧:有时候输出的日志密密麻麻,希望用颜色标识日志中匹配到的内容,方便聚焦。于是:

grep --color  "要查找的内容"  文件名           //注:而不是 -color

 默认匹配到的是棕红色 

全局配置,避免每次输入--color参数,自动给grep加颜色:

[root@home]#vim ~/.bashrc    //打开配置文件,添加 下面的语句

alias grep='grep --color'   //  以后输入grep 就相当于输入了 grep --color


//保存bashrc文件后,更新系统配置

[root@home]#source ~/.bashrc    
惯例使用的  :

//查找 XXX.log的日志文件  只找 421723199606258699  的字符串

cat  XXX.log |  grep 421723199606258699  


比较好用的写法是:
//grep 要查找的内容  文件名

//如: 查找当前 logs目录下的 所有的log中含 421723199606258699  的日志

grep  421723199606258699 ./logs/*.log

有时候我们输入一个关键字出现好多信息,比如把前几天的日志也给查找出来了。这时候我们可以只要后面最新的几条就好:

grep  张三  my.log  | tail -n 2  //找到张三 并打印最后出现2次张三的行    

awk命令 

场景:想要查看某条日志B,前面 离A最近的某个特定日志A: 即 A日志,和B 日志 两个字符串中间还隔着 好多不需要的日志。

形如:(因为很多时候 B报错了,现在想看A传入的参数,所以B在错误的位置很好定位,但是A可能要翻很久)

//现在想获取 B 记录前的 A记录
log.info("A log....");

///中间还有很多 log.info
log.info("XXXX log....");

log.info("B log....");

 那么可以使用命令:(基本思想,使用 先入后出的思想,使用tac反向查找达到找 最近出现的一行)

grep "B" /XXX/XXX/pathFile/XX.log   //定位B

//步骤1:定位B, 向前查找10000行(最好看一下处理一次的流程大概要输出多少日志,这个值就取多少,一般100000行应该能包含了)  并把这部分日志 导入 到 temp.txt 文件中
grep  -B 10000 "B" /XXX/XXX/pathFile/XX.log > temp.txt  

//步骤2: 使用tac命令,从某文件的最后一行开始输出
tac temp.txt | grep "A"


29、SpringBoot配置文件中的值读取、@values注解

场景:一般java取值从配置文件中取我们配好的值,但是避免有人误删了配置文件中的值,所以会在代码中也有默认值,如下写法:

 即使配置文件和java中的值不一样,会先读取nacos里的,其次是配置文件里的,最后是默认值,如果没有则会报错。

30、开发心得、良好的习惯、易维护的代码。

  • 抽取函数,传参最好不要传对象,而是传基本类型。参数超过4个传对象还是不错的

    因为其他地方想调时,对象类型限制了,且因为函数内部有使用  XXX= 对象.属性 这样的赋值,导致函数不能通用,所以如果参数不是很多的话,除了 controller层其他层传参最好使用 基本类型的方式。

    此方式会很大程度上解决,很多函数功能相似,但又无法适用当前开发需求 的问题

  • 对与查找的逻辑,最好使用java8 的stream,将其转化为 map的方式,提高查询效率,且方便越多 

31、部署jar包应用的偷懒技巧。

执行jar命令:sh run.sh 即可启动

#脚本解释:     文件名可叫 run.sh
// 功能:自动将当前目录下的jar包 执行 java -jar XX.jar  并且吧 标准输出 和错误输出 重定向到黑洞

#!/bin/sh


# 获取当前目录
DIR='dirname $0'

#打开当前目录
cd $DIR

#执行java -jar XXX.jar    解释:把标准输出重定向到“黑洞”,还把错误输出2重定向到标准输出1
nohup  java -jar 'ls | grep jar'> /dev/null 2>&1 &


#将该应用的pid记录到tpid文件中 方便一会停止运行
echo $!>tpid

#提示用户启动成功
echo Start Success!

 结束jar命令: sh stop.sh

#!/bin/bash

#cat tpid | xargs kill -9

pid='cat tpid'

while :
do
    rt='ps -ef | grep java | grep $pid'
    if[[ $rt =~$pid ]]
    then
        kill $pid
    else
        echo $pid is killed
        break;
    fi
done

32、idea必备快捷键

Ctrl+Alt+V    自动创建返回值及变量

Ctrl+Alt+ <-- 调回上次调用该函数的位置     特别有效防止看代码时跳的晕头转向

33、guava学习和使用

guava技巧_飞花落雨的博客-CSDN博客

34、不常见,但很有用的注解

@JsonInclude(JsonInclude.Include.NON_NULL)  //标注在类头上,过滤json字段中为null的字段。
@Data //注解在类上, 为类提供读写属性, 此外还提供了 equals()、hashCode()、toString() 方法
@JsonProperty //此注解用于属性上,作用是把该属性的名称序列化为另外一个名称,1.前端传参数过来的时候,使用这个注解,可以获取到前端与注解中同名的属性  2。后端处理好结果后,返回给前端的属性名也不以实体类属性名为准,而以注解中的属性名为准
@Valid  //会去验证后面对象里面的每个属性,每个属性看是否符合要求,不符合时返回message
如:void myFunctuon(@Valid User user) {//todo}

而User中:

@Data
public class User {
    @NullNot(message = "用户名不能为空啊")
    private String username;
 }
  /**关键字,不引主,插入会报错,group在sql里是关键字,所以执行时会报错,
    看到日期打印的结果,框架本就提示出此错误可能是因为使用关键字导致的,
   起因: 数据库建表一般都是用引号引住的字段,所以可以见表成功,但是你的mybatis使用的时候没有引,
导致使用关键字报错,故,需要对有问题的字段进行 引用即可
*/
    @TableField("`group`")
    private String group;

注:如果前端传 http://XX/?name=1,2,3,4 后端类属性使用。private List<Stirng> name 即可自动转换为列表!(暂未亲测) 

扩展:

注解Autowaired与 Resource的区别

@Autowired//默认按type注入(spring的注解)
@Qualifier("cusInfoService")//一般作为@Autowired()的修饰用


@Resource(name="cusInfoService")//默认按name注入,可以通过name和type属性进行选择性注入(javaee的注解)

推荐使用:@Resource,可以使代码更优雅,避免警告提示

 @Autowired 与@Resource的区别(详细)_raymond_2580的博客-CSDN博客_@resource @autowired

35、PostMan使用技巧

场景:每次本地部署应用和远程部署一样,都要手动各创建一份请求,除了ip地址不同,其他路径和参数一模一样,如下:(原先自己都是在postman中创建两个文件夹,老粘贴复制,烦死。)

//本地请求
127.0.0.1:8080/user/operators
//远程请求
192.168.11.1:8080/user/operators

所以,有没有方法:把ip地址变成变量,切换“环境”就自动改变变量的值?postMan提供了!!

方法:

 如果想创建环境,就选 No Environment 再点击小眼睛,就会弹出新窗口,添加,如下:

 

 如果下修改环境,就选 该环境,再点小眼睛。

详情参考:(65条消息) postman初级-1-环境变量:增、删、改、切换_花测试-CSDN博客_postman怎么删除环境变量

36、mysql 分页需要oder by 吗?需要

场景:按照mysql的数据结果,默认排序结果是 id列从小到大的排列。

select * from user limit 0,5  取 前5条数据 这是没有什么问题的,不用加order by

如果在sql语句中不指定order by排序条件,那么得到的结果集的排序顺序是与查询列有关的。因为不同的查询列可能会用到不同的索引,从而导致顺序不同。所以无论什么情况下,还是加上为好。

(65条消息) MySQL查询默认排序与order by排序_韩某的博客-CSDN博客_mysql查询结果默认排序

36、java CURD mysql时 字段带"-",即短横问题

 场景:java插入msyql时,失败,发现某字段带"-"

解决方法:加引号

原来是:
   @ApiModelProperty("繁体中文翻译")
    @TableField("zh-tw_name")
    public String zhtwName;

解决后是:加 ` //查询插入都不报错
   @ApiModelProperty("繁体中文翻译")
    @TableField("`zh-tw_name`")
    public String zhtwName;

切记 不是 加 '  //查询不报错了 插入报错
   @ApiModelProperty("繁体中文翻译")
    @TableField("'zh-tw_name'")
    public String zhtwName;

解决思路:发现打印的sql执行出错,看了没问题,去msql中查询却发现的确报错。所以加单引号

37、Git命令:

场景:开发时,一般分配的任务,自己在远程新开一个My分支,每次都在My分支上进行修改和提交,到任务都做完汇总的时候,再合并的主分支上。

或者自己先在本地创建本地分支,进行开发和修改,然后将本地推送到远程 同名分支,也即是自己的这个分支推送到远程,开发完成再和主分支合并。

命令:

1,git clone  ssh://git@XXXX 克隆项目分支

2,git checkout master 切换到master分支

3,git checkout -b playBackQueue   创建playBackQueue分支并切换至这个分支,这个是本地分支

4,git branch 查看当前分支,*应该在playBackQueue分支上

5,git add .   添加修改

6,git status 查看工作区状态,即修改的文件有哪些

7,git commit -m"注释" 提交修改到当前分支

8,git push origin playBackQueue:playBackQueue把本地新建的分支推到远程分支,冒号前面是远程分支名,也可以与本地分支名不同


//9,10步骤 是将  playBackQueue 合并到master分支上
9,git checkout master 切换回master分支

10,git merge playBackQueue 合并自己的分支到master

11,浏览器查看是否提交上去了读代码

12,git push origin :playBackQueue 删除远程分支

或者git push origin --delete playBackQueue都可以实现删除远程分支

12,git branch -d playBackQueue 删除playBackQueue本地分支

13、将当前分支关联到指定分支
 git checkout -b gpf origin/gpf    # 新建本地分支gpf与远程gpf分支相关联 

(68条消息) gitLab新建分支给远程分支提交代码_yana_balabala的博客-CSDN博客_gitlab提交新分支

上述命令遇到冲突就不好搞了,对应融合最好使用idea自带的。如果想将opensearch分支融合到masrer分支,则

①切换分支到master(如果本地已经有master分支,最好也切远程的,因为可以更新一下本地的master分支)

 ②git->merge 

 

③执行融合,弹出来的下拉选项,如果没有opensearch 说明opensearch最新的代码已经融合到master分支了。如果有,那说明确实要融合一下最新的。

 注:以上融合都是本地分支的融合,远程的分支还没融合这时候,虽然我并没有对mater分支进行修改,使用不了(因为提交也是没文件让你提交)。但是你这时候应该push一下到远程,push后才是真的推送到远程。

扩展:如果comit 且push到远程分支的话,现在想取消这次操作,则:

Revert Commit--》再push

(流程:先把本地的提交恢复到原来的,然后再提交恢复后的,如果第一次创建文件并push了一个文件,现在Revert Commit 变成了没有创建文件的状态,恢复后的 其实就是把刚才的给删除了),再push远程(相当于告诉 这个文件我删了 你得删吧)

38、Collectors.toMap()、XX.stream().flatMap()的使用

Collectors.toMap()该函数的参数key、value均不能为空,否则空指针异常

codeMap = ValuesList.stream().filter(item->Objects.nonNull( item.getCode())).collect(Collectors.toMap(Values::getId,Values::getCode,(v1,v2) -> v1));

扩展:想要获取 列表A中的列表B,将B中的所有集合放到C列表中

//存在空指针异常 i.getB()可能为空
List<B> c= products.stream().flatMap(i ->i.getB().stream()).collect(Collectors.toList());

//使用Optional.ofNullable(可能为空的对象) 防止A中的B 没有对象导致空指针异常
List<B> c= A.stream().flatMap(i ->Optional.ofNullable( i.getB()).orElseGet(ArrayList::new).stream()).collect(Collectors.toList());

注:toMap() 使用时,最好  考虑到 key重复的情况。如:

//身份证号为key:value为name
XXX.toMap("persion身份证号",persion.姓名,(v1,v2)->v2) //如果遇到重复的key 那就保留最新的value

 代替:

XXX.toMap("persion身份证号",persion.姓名)

39、myabtis报错:没有XX字段

场景:使用mybatis查询或插入时,myabatis报错,查无此列,核对了 表结构、以及对应的实体 发现都没问题 也都没这个字段,那么这个字段哪里来的??

解决:发现 对应的实体 继承了  其他类, 该类的私有字段也给继承回来了!!

所以:子类继承父类 是继承了父类的所有!! 包括private修饰的属性!!

40、mysql 表被锁

场景:一般发生在更新或者删除sql操作时。出现的情况是应用程序 被kill -9 pid死的 (正确操作:应该kill pid),更新sql语句时,结果java代码就卡在这里不动,好半天后 报Lock wait timeout exceeded try restarting transaction。

java应用程序结束,已经开启的事务不一定结束!(未验证)

解释1:

原因:

锁表,是因为有个上一个事务A没有提交,现在来了事务B 要操作 该资源(指:行 表),导致B没法操作该资源,事务A又迟迟没了“动静”,出现 锁表。 该资源(指:行(如果是innoDB:可锁行、表), 表(如果是MyIsAm,只可锁表))

1.无索引情况下更新数据
begin;-- 开启事务
update tb_user set phone=‘15167891234’ where name=‘小花’;-- 修改,先别commit事务
再开一个窗口,直接运行命令:
update tb_user set phone=‘15167891234’ where name=“小明”;-- 发现一直卡着不能执行
但将第一个窗口的sql COMMIT之后,第二个窗口的更新语句就能执行了,说明在where条件后没索引的情况下锁表。


2.有索引情况下更新数据
create index index_name on tb_user(name);
加完索引之后继续按照1的步骤去执行,发现窗口2不会卡着了,立马执行了,说明没有锁表了,然后将相同的update语句在打开的2个窗口内执行(即:更新用一条),发现第2个窗口会一直卡着,说明在where条件后有索引的情况下锁行。

总结
在update/delete情况下,如果没有索引,会锁表,如果加了索引,就会锁行。但是其中过滤条件最好在主键索引情况下执行,因为过滤条件在非主键索引情况下,mysql会先锁住非主键索引,再锁定主键索引,如果此时恰好该行记录又根据主键索引更新,有可能也会发生冲突。
ps:数据库锁表时间一般为50s。
 

解释2:

//查看mysql自带的表中有哪些 事务
SELECT * FROM information_schema.INNODB_TRX;

//找到 trx_sql_thread_id  杀掉即可
kill  XXXX

 如果怕以后再出现类似情况,在kill -9 XXX 时,让java持续知道自己被杀了,然后赶快回滚本次事务就行了。 

解决办法:kill XXX  少用kill -9 XXX  前者会"有序退出程序",后者直接 嗝屁退出程序

kill 和 kill -9 是常用的命令,都可以用来杀死进程。 那 kill 与 kill -9 有什么区别呢?

kill命令默认的信号就是15,也就是 kill -15 ,被称为优雅的退出。 当使用kill -15时,系统会发送一个SIGTERM的信号给对应的程序。 当程序接收到该信号后,具体要如何处理是自己可以决定的

 注:其他博客都是 trx_state 是lock 才kil,而我遇到的是running状态,kill后发现也好了。

 41、事务注解不生效:

场景:由于要数据解析MQ拉取的参数,我在service中创建了 A函数进行解析,然后调用带事务注解的B函数,B中有对数据库操作的 C,D函数。 结果在B中执行完C,执行D时抛出异常,但数据没回滚。伪代码如下:

class  AAAA{


    /**A 调用带事务注解的B,此刻事务没开启,原因:事务注解基于动态代理,
    那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,
而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了
    */
    public void A(){
        //业务逻辑
        this.B();
    }

    @Transactional(rollbackFor = Exception.class)
    public void B(){
      this.C();//插入更新操作
       1/0;
      this.D();//插入更新操作
       
    }

    private void C(){//todo}
    private void D(){//todo}
    
}

解决: 将注解移到 函数A头上即可。

扩展:

事务注解失效的原因:

①事务注解标注的当前函数的访问修饰符不是public

在t同一个类中,函数A调用 带有@Transactional标注的函数B方法B是不会开启事务的(我的情况就是这个)

③捕获异常抛出事务却不回滚。

(我在 类头上加事务注解,发现开启了事务,但是B中执行C后发生异常,数据并没有被回滚,原因是:如果方法中捕获异常后手动抛出异常,事务并没有回滚。

解决办法:Spring事务默认支持RuntimeException异常,抛出的异常为RuntimeException异常及其子类异常事务均可生效,而我们日常常见的异常基本都继承自RuntimeException,所以无需指定异常类型事务也能生效。
但若手动抛出Exception异常,而Exception是RuntimeException的父类,会导致事务不生效。
 

@Transactional(rollbackFor = RuntimeException.class) 
//默认,如果你抛出Exception 他是拦截不到的 所以要么你 抛出 RuntimeException 要么 你这里写 rollbackFor= Exception.class

可是Exception是包含了 RuntimeException的啊,不知道为什么!

但是,下面的代码标记在方法上,是可以拦截 检查和不可检查 的异常的!!

@Transactional(rollbackFor = Exception.class)

总结:最好加在方法上,加类头上好像拦截不了 运行时的异常(不可检查的异常)

小扩充:Exception分:可检查的(如I/O异常)、和不可检查的(如空指针异常,即根据运行的结果有不可预期的可能)。 

(73条消息) @Transactional注解不起作用解决办法及原理分析_一撸向北的博客-CSDN博客_transactional注解失效

Spring事务不生效原因及解决方案_spring 事务失效_冰糖码奇朵的博客-CSDN博客

扩展:全局异常

springBoot中提供了全局异常的拦截处理。定义全局拦截方法,在类头上标记注解:

@RestControllerAdvice

代码如下:



import com.XX.product.config.MybatisPlusConfig;
import com.XX.product.model.common.ResponseDTO;
import com.XX.product.exception.ProductServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Set;


@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    protected <T> ResponseDTO<T> handleInternalException(Exception e) {
        String errCode;
        StringBuilder message = new StringBuilder();
        if (e instanceof ValidationException) {
            // 参数校验报错
            errCode = 50001;
            ConstraintViolationException exs = (ConstraintViolationException) e;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                message.append(item.getMessage()).append(";");
            }
        } else if (e instanceof BindException) {
            // 参数绑定报错
            errCode = 5002;
            List<FieldError> fieldErrors = ((BindException) e).getFieldErrors();
            for (FieldError fieldErr : fieldErrors) {
                message.append(fieldErr.getDefaultMessage()).append(";");
            }
        } else if (e instanceof MethodArgumentNotValidException) {
            // 参数不合法报错
            errCode = 50003;
            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
            List<FieldError> fieldErrors = methodArgumentNotValidException.getBindingResult().getFieldErrors();
            for (FieldError fieldErr : fieldErrors) {
                message.append(fieldErr.getDefaultMessage()).append(";");
            }
        } else if (e instanceof ProductServiceException) {
            // 业务报错
            ProductServiceException productServiceException = (ProductServiceException) e;
            message = new StringBuilder(productServiceException.getMessage());
            errCode = productServiceException.getErrCode();
        } else {
            // 其他报错
            errCode = 500;
            message = new StringBuilder(ErrorCodeEnum.UNKNOWN_ERR.getDesc() + ":" + e.getMessage());
        }
 log.error("系统全局异常定位:{}", Arrays.stream(e.getStackTrace()).limit(3).collect(Collectors.toList()));

        log.error(e.getMessage(), e);
        ResponseDTO<T> responseBean = new ResponseDTO<>();
        responseBean.setMessage(message.toString());
        responseBean.setCode(errCode);
        responseBean.setStatusCode(500);
        return responseBean;
    }

}

异常什么时候抛出,什么时候捕获呢? 

程序上:捕获,是程序继续还会执行,抛出,当前程序会中断

个人业务需求上:某个字段的、或者众多批次中有个别失败,在不影响大局的情况下捕获。

基本原则:能抛则抛

扩展:

@ControllerAdvice + ResponseBodyAdvice接口 实现参数返回的统一格式

@ControllerAdvice + RequestBodyAdvice 接口 实现参数入参的统一格式

42、jackson 对象转换成字符串,日期问题

场景:公司使用jackson转换成字符串时,发现日期对象变成了时间戳的方式。一般使用jackSon会处理很多不同类型的对象,而有的对象的日期对象是:Date ,有的又是 LoaclDateTime 对象。。。

解决: Data使用 SimpleDateFormat格式化,LoaclDateTime使用JavaTimeModule格式化,且可以同时使用。

方法:

public String dddd(Object object){

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);

        String contentJson = null;
        try {
            contentJson = objectMapper.writeValueAsString(content);
        } catch (JsonProcessingException e) {
            log.error("get json from value failed! value[" + content + "].", e);
        }

        returen contentJson ;

}

43、ThreadLocal修饰符的作用:

多个线程各自拷贝一份 ThreadLocal修饰的变量 到自己本地,即:不会对原本的变量修改,只修改副本。

注:ThreadLocal变量通常设置为static的原因:避免每次使用该类就创建该变量,节省空间。不然,每次创建 一个值相等的对象,但这些对象的地址又不同,造成浪费

一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。
原文链接:https://blog.csdn.net/u013543848/article/details/102980066

(74条消息) 深入学习java源码之ThreadLocal.get()()与ThreadLocal.initialValue()_wespten的博客-CSDN博客_java threadlocal.get

44、sql为某字段设置默认值、修改字段类型、sql自动设置更新时间

注:其实这是 覆盖操作,所以记得 可能需要 补上一些额外的 限制语句,如下:unsingned not null

//添加默认值、并且修改备注
alter table tableXXXXX modify column product_status tinyint(3) unsigned NOT NULL default  1 comment '1 草稿,4下架,11上架';

 让sql维护更新时间、创建时间

//修改tableXXXXX 表的created_at  字段设置为timestamp 类型,并设置默认值为 创建该记录的时间
ALTER TABLE tableXXXXX MODIFY created_at  timestamp DEFAULT CURRENT_TIMESTAMP;

//修改tableXXXXX 表的created_at  字段设置为timestamp 类型,并设置默认值为 更新该条记录的时间
ALTER TABLE tableXXXXX MODIFY updated_at  timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;


好处: java代码中,就不用再写设置 这两个字段的业务逻辑了。

45、java调用python模型、服务

思路:http

python服务开启代码:

from flask import Flask,request
app = Flask(__name__)


@app.route("/", methods=["POST"])
def hello():
    print(request.form["name"])
    return "Hello World!"

@app.route("/recommend_gpt", methods=["GET"])
def recommend_gpt():
    user_id = request.args.get("user_id")
    # 在这里可以使用user_id参数来执行其他逻辑
    return "Hello, user with ID {}".format(user_id)



if __name__ == "__main__":
    app.run(port='8080')
 

java调用代码:

import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class TestRcp {

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        String  url="http://127.0.0.1:8080/";
        MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
        params.add("name","张三");
        Object object1=  restTemplate.postForObject(url,params,String.class);
        System.out.println();
    }

}

46、springBoot配置主从数据的配置文件:

关键词:主从复制,多数据源

步骤1:maven依赖:

 <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.codehaus.groovy</groupId>
                    <artifactId>groovy</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

步骤2:application.properies文件 

#配置主从数据源,要基于MySQL主从架构。
spring.shardingsphere.datasource.names=m0,s0

spring.shardingsphere.datasource.m0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.jdbc-url=jdbc:mysql://192.168.11.131:3306/product-service?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=11

spring.shardingsphere.datasource.s0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.s0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s0.jdbc-url=jdbc:mysql://192.168.11.132:3306/product-service?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.s0.username=root
spring.shardingsphere.datasource.s0.password=11
spring.shardingsphere.datasource.s0.connection-timeout=60000  //配置超时,想要看值设置 参考 项目启动时 注入数据源的 对象,看其属性的设置值情况
#读写分离规则, m0 主库,s0 从库
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s0

spring.shardingsphere.props.sql.show=false
spring.shardingsphere.props.check.table.metadata.enabled=false
spring.shardingsphere.props.max.connections.size.per.query=100

此处m0库负责增删改,s0负责查询,两处数据源可以配置不同,也可以配置为同一个数据源 

47、sprignboot 配置nacos

 1、maven依赖

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${nacos.config.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>guava</artifactId>
                    <groupId>com.google.guava</groupId>
                </exclusion>
            </exclusions>
        </dependency>

2、配置文件:

bootstrap.properties内容:

spring.cloud.nacos.config.group=DEFAULT_GROUP  //指定nacos上的组
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.server-addr=nacos-server.dev.interfocus.tech:443
spring.cloud.nacos.config.namespace=771917b3-7305-4be2-XXXX-efd6c9860a5f  //每个环境naocs上的唯一id
spring.cloud.nacos.config.refresh-enabled=true
spring.cloud.nacos.config.enabled=true

48、通过断点调试-查看数据源 (借助mybatis的mapper)

49、任务调度xxj-box:

优势,可以通过界面去管理或触发 任务执行,比@schedule 注解强多了,后者是死的、定时的,一般用于单机上的执行。

使用:

步骤1:maven依赖:

  <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>${xxljob.version}</version>
        </dependency>

步骤2:bootstrap.properties

xxl.job.admin.addresses=https://xxl-job.dev.interfocus11.tech/
xxl.job.accessToken=
xxl.job.executor.appname=11-community-service
xxl.job.executor.address=
xxl.job.executor.ip=
xxl.job.executor.port=9081
xxl.job.executor.logpath=/var/log/java/xxl-community
xxl.job.executor.logretentiondays=30

步骤3:配置类bean注入 

package com.11.community.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class XxlJobConfig {
    private final   Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken:}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address:}")
    private String address;

    @Value("${xxl.job.executor.ip:}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    /**
     *  执行器
     *
     * @return XxlJobSpringExecutor
     */
    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        logger.info(">>>>>>>>>>> xxl-job adminAddresses=" + adminAddresses + " appname=" + appname + " port=" + port);
        return xxlJobSpringExecutor;
    }



}

步骤4:使用样例:

package com.11.community.controller.xxljob;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.11.community.bo.PostFactorBo;
import com.11.community.bo.PostScoreTempBo;
import com.11.community.bo.es.PostProductIdBo;
import com.11.community.bo.es.PostProductIdTempBo;
import com.11.community.bo.es.ProductIndexEs;
import com.11.community.constant.BaseConstant;
import com.11.community.entity.PostScore;
import com.11.community.query.post.PostsWithProductQuery;
import com.11.community.service.IPostScoreService;
import com.11.community.service.IPostService;
import com.11.community.utils.DateUtils;
import com.11.community.utils.DecimalUtils;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.text.ParseException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 
 * xxl job定时任务
 *
 * @Author mcj
 * @Date 2022/2/15
 */
@Slf4j
@Component
public class PostSortJobHandler {

    @Autowired
    private IOpenSearchCommonService commonService;

    /**
     * xxl job 调用
     */
   @ApiOperation("同步数据到openSearch信息")
    @XxlJob(value = "openSearchPut") //我们的 任务调度的执行器 就叫 openSearchPut
    public void queryAndPut() {
        String productIds = XxlJobHelper.getJobParam();//获取 xxl-job的参数,如果不传,则是空字符串
        try {
            commonService.startTask(productIds);
        }catch (Exception e){
            log.error(e.getMessage());
            e.printStackTrace();
            throw e;
        }finally {
            MybatisPlusConfig.myTableName.set("");
        }
    }

步骤5:登录xxl-job的控制台去查看:

idea控制台也可以看见是否注册成功 

xxl-job的控任务调度执行也可以核对是否注册上来: 

步骤5:在 任务调度中心 配置我们的这个执行器 上面时候执行,执行时携带什么参数:

 

50、springBoot加载配置文件相关问题

关键词:@value赋值失败、 指定加载配置文件失败、no active profile set

场景:使用配置类时,注入bean,该bean依赖@valu("${XXX}"),发现报错没解析,如下:

(注:截图中 port的值的value实际写错了,导致项目一直卡住,不报错也没信息输出) 

 发现:编译后的target目录没有配置文件,那么需要在pom中设置如下:

  <build>        
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

 待续.....

场景:项目启动。没有详细的信息,只有框架logo输入 ,如下:

原因:和日志输出有关。应该是日志路径或者日志配置有问题。自己创建了新环境的配置文件而日志配置文件中没有配该环境下的:

解决:

扩展:

日志模板: 

application.properties的日志文件配置:

#指定日志文件的配置文件
logging.config=classpath:logback-spring.xml
#日志的输出等级,可以指定具体某个目录下的才输出,如果 设置 logging.level.root=debug 则整个项目含jar包中的也输出的 贼多
logging.level.com.example=debug
logging.file.path=/log

 logback-spring.xml的日志模板配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--日志输出格式设置-->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!--日志颜色的设置-->
    <conversionRule conversionWord="clr"
                    converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!--自己也可以定义转换规则,如 第三方日志收集器kinesis 接受的是json的字符串流,那我们需要把 日志输出 以json的格式输出-->
<!--    <conversionRule conversionWord="message" converterClass="com.XXX.common.converter.MyMessageConverter"/>-->

    <!--从application.properties 加载一些变量 如路径、项目名称(用于自定义log文件名称)
     如:取变量:logging.file.path的值 在本文件声明为 log_path,使用时格式:${log_path}
     如:取变量:log.project.name的值 在本文件声明为 project_name 使用时格式:${project_name}     -->
    <springProperty scope="context" name="log_path" source="logging.file.path"/>
    <springProperty scope="context" name="project_name" source="log.project.name"/>


    <!--控制台输出-->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!--日志文件输出到文件设置-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log_path}/${project_name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--不同环境下 设置输出的流:是输出到控制台stdout、还是输出到文件file,还是都输出?-->
    <springProfile name="test">
        <root level="info">
            <!--将流输出到控制台-->
            <appender-ref ref="stdout"/><!--已经在appender定义了-->
            <!--将流输出到文件-->
            <appender-ref ref="file"/><!--已经在appender定义了-->
            <!--将流输出到第三方日志收集平台 如 kinesis-->
            <!--            <appender-ref ref="KINESIS"/>--> <!--那么也需要在appender定义-->
        </root>
    </springProfile>
    <springProfile name="alpha">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
        </root>
    </springProfile>
    <springProfile name="prod">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
        </root>
    </springProfile>
    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
        </root>
    </springProfile>
    <!--不指定环境的情况下(使用 默认环境)-->
    <springProfile name="default">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
        </root>
    </springProfile>
</configuration>

52、观察接口报警的工具-可视化工具

(可能配置麻烦)

grafana 是一款采用 go 语言编写的开源应用,是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知

配置样例:

 

钉钉 自定义机器人接入:自动发到群消息:

https://blog.csdn.net/u013372493/article/details/124819854

53、工具-将大文件划分成小文件

//inputFile 文件路径
//outputFile 输出路径
// gb  拆分文件的大小, 单位gb
public static void fileToSamll(String inputFile, String outputFile,int gb) {

        try {
            int hash = gb * 1024 * 1024 * 1024;

            FileReader read = new FileReader(inputFile);
            BufferedReader br = new BufferedReader(read,10*1024 * 1024);
            String row;

            int num = 0;

            int fileNo = 1;
            FileWriter fw = new FileWriter(outputFile+fileNo +".csv");
            while ((row = br.readLine()) != null) {
                num =row.length() +num;
                fw.append(row + "\r\n");
                if((num / hash) > (fileNo - 1)){
                    fw.close();
                    fileNo ++ ;
                    fw = new FileWriter(outputFile+fileNo +".csv");
                }
            }
            fw.close();
            System.out.println("fileNo="+fileNo);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

54、springBoot记录日志

1、配置文件 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--日志输出格式设置-->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>

<!--    <conversionRule conversionWord="message" converterClass="com.aaa.common.converter.aaaMessageConverter"/>-->
<!--    <conversionRule conversionWord="exception" converterClass="com.aaa.common.converter.aaaExMessageConverter"/>-->
<!--    <conversionRule conversionWord="server_addr" converterClass="com.aaa.product.config.IpConverterConfig"/>-->
<!--    <springProperty scope="context" name="kinesis.region"  source="kinesis.region" />-->
<!--    <springProperty scope="context" name="kinesis.streamName" source="kinesis.streamName"/>-->
    <!--log.path 变量在application.properties 有配置-->
    <springProperty scope="context" name="log_path" source="log.path"/>
    <!--project_name变量在application.properties 有配置-->
    <springProperty scope="context" name="project_name" source="log.project.name"/>


<!--    <appender name="KINESIS" class="com.gu.logback.appender.kinesis.KinesisAppender">-->
<!--        <bufferSize>1000</bufferSize>-->
<!--        <threadCount>20</threadCount>-->
<!--        <region>${kinesis.region}</region>-->
<!--        <maxRetries>3</maxRetries>-->
<!--        <shutdownTimeout>30</shutdownTimeout>-->
<!--        <streamName>${kinesis.streamName}</streamName>-->
<!--        <encoding>UTF-8</encoding>-->
<!--        <layout class="ch.qos.logback.classic.PatternLayout">-->
<!--            <pattern>{"created_time":"%d{yyyy-MM-dd HH:mm:ss.SSS,UTC}","project_name":"${project_name}","server_ip":"%server_addr","current_thread":"%thread","client_ip":"%mdc{CLIENT_IP}","class":"%class{255}","line":"%line","level_name":"%level","message":"%message","stack_trace":"%exception{100}"}-->
<!--            </pattern>-->
<!--        </layout>-->
<!--    </appender>-->

    <!--控制台输出-->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!--日志文件输出-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log_path}/${project_name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

<!--    <logger name="KinesisLogger" additivity="false">-->
<!--        <appender-ref ref="KINESIS"/>-->
<!--    </logger>-->


    <springProfile name="test">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
<!--            <appender-ref ref="KINESIS"/>-->
        </root>
    </springProfile>
    <springProfile name="alpha">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
<!--            <appender-ref ref="KINESIS"/>-->
        </root>
    </springProfile>
    <springProfile name="prod">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
<!--            <appender-ref ref="KINESIS"/>-->
        </root>
    </springProfile>
    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
            <!--            <appender-ref ref="KINESIS" />-->
        </root>
    </springProfile>
    <springProfile name="default">
        <root level="info">
            <appender-ref ref="stdout"/>
            <appender-ref ref="file"/>
            <!--            <appender-ref ref="KINESIS" />-->
        </root>
    </springProfile>
</configuration>

注:如果控制台有日志打印,但是日志文件没有日志打印,请查看springboot的启动环境,不指定环境,则是 default,如下:

 那么去检查,springProfile name="default" 是否存在。(上文的配置有给出,所以存在)

55、springboot 配置ncoas,指定配置不生效 

场景:开发项目,引入nacos。死活连接不上远程的nocas配置。配置如下:

maven依赖:

  //springboot版本是2.3.12.RELEASE

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.7.RELEASE</version>
        </dependency>

配置文件:application.properties

spring.application.name=11-searchTemp
spring.profiles.active=dev

spring.cloud.nacos.config.group=DEFAULT_GROUP
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.namespace=771917b3-7305-4be2-9c0b-efd6c9860a5f
spring.cloud.nacos.config.server-addr=XXXXXXX:443
spring.cloud.nacos.config.refresh-enabled=true
spring.cloud.nacos.config.enabled=true

问题:配置了这些配置和没配一样,问题排查方法 :

 发现NacosPropertySourceBuilder 类输出的这个 Igrore the empty nacos XXX,所以全局查找(idea 中,按两次sheift) r如下:

 找到日志的代码:

考虑到,是其他调用此方法输出的这些,那么断点调试,找到调用此方法的函数,看看传入的数据 对不对就行了!

 

这样跳出函数几次,很有可能就找到我们配置的数据,开始没加载我们指定的数据,这里都是默认的localhost:8848 等其他默认。

原因找到了,是没加载配置文件,为什么没加载我们指定的配置呢?

扩展知识:bootstrap优先级高于 appliaction文件 ,yml优先级高于properties。

经验建议:提早配置、引导配置 尽量配置到 bootstrap中

application.properties和 bootstrap.yml 区别 - 简书

问题解决:将项目中的 application.properties 文件名修改为 bootstrap.properties

读取远程的配置文件验证:

个人心得:application 更注重的是 应用本身的上下文引导,bootstrap是程序启动时的引导,包含了应用上下文的引导

56、谷歌API、aws亚马逊API

1、谷歌翻译

前提是公司已经给你申请了 秘钥key  

详情参考:

官方参考:快速入门  |  Cloud Translation  |  Google Cloud谷歌翻译SDK (Google Translate SDK)的使用_nicolelili1的博客-CSDN博客_谷歌翻译开放平台

步骤开始:未安装或者配置 上述博客的的任何东西。

 步骤1:引入依赖

        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>google-cloud-translate</artifactId>
            <version>2.1.13</version>
        </dependency>

步骤2:接口调用

package com.search.service.impl;

import com.google.cloud.translate.Translate;
import com.google.cloud.translate.TranslateOptions;
import com.google.cloud.translate.Translation;
import com.google.cloud.translate.v3.*;
import com.11.search.service.IGoogleTranslationService;
import org.springframework.stereotype.Service;

import java.io.IOException;

/**
 * @author wwwV_Jb0
 * * @date 2022/5/13
 */
@Service
public class GoogleTranslationServiceImpl implements IGoogleTranslationService {
    @Override
    public String translateToEn(String keyword) throws IOException {
        
        //该秘钥是 付费的,一般是AIz开头的
        String key="xxxxxxxxxxxxxxxxxxx";
        Translate translate = TranslateOptions.newBuilder().setApiKey(key).build().getService();
        String targetLanguage = "en";
        String sourceLanguage  = "zh-CN";//可以传空 会自动检测原语种
        Translate.TranslateOption srcLang = Translate.TranslateOption.sourceLanguage(sourceLanguage);
        Translate.TranslateOption tgtLang = Translate.TranslateOption.targetLanguage(targetLanguage);
        // Use translate `model` parameter with `base` and `nmt` options.
        Translate.TranslateOption model = Translate.TranslateOption.model("base");

        String sourceText = "裙子 ";
        Translation translate1 = translate.translate(sourceText, srcLang, tgtLang, model);
        String translatedText = translate1.getTranslatedText();
        System.out.println(translatedText);

        return null;
    }
}


打印:,如果秘钥错误,则会报错:如下:

。。。。。
com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
GET https://translation.googleapis.com/language/translate/v2?key=AIzaSyBmdOdgjKSPKDfiUTW1EAwe5bVLPzfXXXX&model=base&q=%E8%A3%99%E5%AD%90%20&source=zh-CN&target=en
{
  "code" : 400,
  "details" : [ {
    "@type" : "type.googleapis.com/google.rpc.ErrorInfo",
。。。。。

可以看出,调用api也是发送请求,只不过参数 进行了拼接,所以 直接通过 restTemplate发送get请求 应该也是可以。如下,验证也是成功的的。

 主要是如何拼接url的get请求:

格式如下:

//共4个参数: key、model、q(即关键字) 、source(原始语言)、target(目标语言)
https://translation.googleapis.com/language/translate/v2?key=AIzaSyBmdOdgjKSPKDfiUTW1EAhe5bVXXXXX&model=base&q=长裙&source=zh-CN&target=en

 2、谷歌认证实现

方式一: 比较原始的request请求

(谷歌文档的风格)

首先,为了避免多次创建客户端,多次读取密钥,所以将创建的客户端通过java的配置类 注入spring中

 认证代码:(基本原理使用谷歌的对象,对request对象设置一些认证的操作 

使用上:

//方法一认证: 其中, google-credentials是谷歌授权账号去“凭证”下载的json密钥
    @Bean
    public GoogleCredential googleAuthorize() throws IOException {
        ClassPathResource resource = new ClassPathResource("google-credentials.json");
        return GoogleCredential.fromStream(resource.getInputStream()).createScoped(Collections.singleton("https://www.googleapis.com/auth/cloud-platform")).setExpirationTimeMilliseconds(new Long(3600000L));
    }

    @Bean    
    public HttpRequestFactory httpRequestFactory() throws GeneralSecurityException, IOException {
        HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        return httpTransport.createRequestFactory();
    }

-------------------------------------------------------使用方面------------------------
public void sss(){
//其中map是我们的请求体,即依据json字符串,构造的map。如:{“name”:"李四"} 则map.put("name","李四")  

//调用谷歌的buildPostRequest获取request
HttpRequest request = httpRequestFactory.buildPostRequest(new GenericUrl(谷歌文档某API的URL), new JsonHttpContent(new GsonFactory(), map));
//给谷歌的HttpRequest 设置密钥
googleAuthorize.initialize(request);
HttpResponse response = request.execute();
System.out.println(response.parseAsString());
}

注:如果报错40X,请依据返回的报错结果,调整json,一般是构造的map和json对不上,另一个是 value的类型不对(常规字段基本均为字符串)

方式二:谷歌对象的认证

方法二:谷歌的风格都是 XXXServiceClient客户端,想要对其设置 传入xxxServiceSettings 对象即可
    @Bean
    public Credentials getCredentials() throws IOException {
        ClassPathResource resource = new ClassPathResource("google-credentials.json");
        return ServiceAccountCredentials.fromStream(resource.getInputStream());
    }

    @Bean
    public XXXServiceClient XXXServiceClient() throws IOException {
        Credentials creds = this.getCredentials();
        XXXServiceSettings xxxServiceSettings = XXXServiceSettings.newBuilder().setCredentialsProvider(FixedCredentialsProvider.create(creds))).build();
        return XXXServiceClient.create(xxxServiceSettings);
    }

---------------------------------使用方面----------------------------------------------
基本和XXXServiceClient的方法有关,不太灵活只能调用该客户端提供的一些方法,但是方便和标准


注:谷歌提供的对象、或者要传入函数的对象 一般都不可new, 
而是  XXXX对象.newBuilder().setXXX()......bulid(); 

方式三:有时候可能业务安全方面,本地不让保存 google-credentials.json,需要配置在配置中心去管理,所以我们可以先把 google-credentials.json内容变成字符串(可通过map一个一个属性put再转成字符串)。


    @Value("${google.search.key}")
    private String googleSearchKey;
    @Bean
    public Credentials getCredentials() throws IOException {
        //map变成输入流
//        InputStream in = new ByteArrayInputStream(JSON.toJSONString(map).getBytes());
//        ClassPathResource resource = new ClassPathResource("google-credentials.json");
        return ServiceAccountCredentials.fromStream(new ByteArrayInputStream(googleSearchKey.getBytes()));
    }

 aws亚马逊api调用技巧:

身份认证:

@Configuration
public class AWSConfig {

    @Value("${aws.accessKeyId}")
    private String awsAccessKeyId;
    @Value("${aws.seretACCessKey}")
    private String awsAccessKeySecet;

    @Bean
    public PersonalizeClient personalizeClient() {
        AwsBasicCredentials awsCreds = AwsBasicCredentials.create(awsAccessKeyId, awsAccessKeySecet);
        return PersonalizeClient.builder().credentialsProvider(StaticCredentialsProvider.create(awsCreds)).region(Region.US_WEST_2).build();
    }
}

 java文档参考:aws-doc-sdk-examples/javav2 at main · awsdocs/aws-doc-sdk-examples (github.com)

 该github项目的所有示例代码,除了没有按照其配置认证环境,而是需要提供代码实现认证,

惯用模板套路: 通过获取 XXXCredentials.create() 获取证书对象,然后把这个对象丢到 某目标客户端:

XXXClient.builder().credentialsProvider(XXXXCredentialsProvider.create(awsCreds)).region(Region.US_WEST_2).build();

其实很多时候,第三方认证让配置系统环境变量,即在项目启动前先用代码或者指定jvm参数进行设置,如:

文章参考:【SpringBoot】启动前执行的几种方式_可耳(keer)的博客-CSDN博客

59、get请求,下划线请求参数封装进对象

场景:get请求模式不准变化的需求,且众多参数,接收参数的对象,如果属性和get请求携带的参数 一模一样,自然会接收成功,如下:

get 请求: XXX/XX/site_id=100&param1=&....

接收该请求要封装的对象:

@Data
public QO{

private Integer site_id;
private String param1;
}

这样,调用时,可能是 qo.getSite_id();  非常丑。

所以,通过以下实现:

和上述方法一样,但是 生成set、get方法后,将方法名修改去掉 下划线, 并删除 set方法,如下:

@Data
public QO{

  private Integer site_id;
  private String param1;

//切记 不能要 get_siet_id的方法
public get siteId(){  return site_id;}
public get param1(){  return param1;}

}

57、查询日志es的kibaba

查询思路:先理解你公司所在的索引字段,一般是某个模块 会应了一个项目名称字段:如project_name,想要查看日志是否发送错误,可以看 level字段:于是写出的查询语句为:

product_name:"模型名称"  AND level "error"

其中,level是你公司在同步到es上 log.error的信息一般通过脚本同步解析,赋值该字段,

一般的 log.info 日志信息会放到 message字段(我公司是这样设计的表结构,每个公司不一样哈)

例如:下面想查询: product-service 模块的日志 且 log.info输出 query:default; 

 或:使用

 

技巧:当一个项目很大时,线程很多,依据日志排错,日志相关时间段内打印的日志是各个线程掺杂着出现的,这时候我们可以评估  单位时间内(如5分钟、1分钟,主要看时间段内的日志数量来确定)按线程聚合 ,甚至抛出异常的都不知道是哪个接口抛出的(如IOException:Broken pipe)

 使用dev tool的方式查询示例:



GET java-log-2022-06-21/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "match_phrase": {
                        "project_name": {
                            "query": "product-service"
                        }
                    }
                },
                {
                    "match_phrase": {
                        "message": {
                            "query": "query:default:"
                        }
                    }
                }
            ]
        }
    },
    "_source": [
   "project_name",
    "message"
  ],
      "from": 0,
    "size": 10000
}

扩展:IOException:broken pipe

原因:A请求B,B还在处理A的请求,A这时候就不等待结果取消了请求

①在linucx系统上报错为: IOException:broken pipe

②在window系统上报错为:java.io.IOException: 远程主机强迫关闭了一个现有的连接。

58、命令行查看本地Maven仓库地址 linux环境下:

1. 如果是只需要查看本地仓库的话可以使用如下命令:(会下载,然后打印路径)

mvn help:evaluate -Dexpression=settings.localRepository | grep -v '\[INFO\]'

默认路径:/home/用户/.m2/repository

2. 在运行maven命令时,添加-X 或者 -Debug参数

mvn -X


会打印出相关结果

...
[DEBUG] Reading global settings from C:\Maven\conf\settings.xml
[DEBUG] Reading user settings from C:\segphault\.m2\settings.xml
[DEBUG] Using local repository at C:\Repo


文章参考:命令行查看本地Maven仓库地址_xinglu31的博客-CSDN博客_linux maven 默认仓库地址

59、springboot加载resource文件夹下 的自定义文件

方式1:

 String path = this.getClass().getClassLoader().getResource("my.json").getPath();
     

方式2:(推荐)

 ClassPathResource resource = new ClassPathResource("my.json");

60、jar 包在后台运行

nohup java -jar babyshark-0.0.1-SNAPSHOT.jar > log.file 2>&1 &

上面的2 和 1 的意思如下:

0    标准输入(一般是键盘)
1    标准输出(一般是显示屏,是用户终端控制台)
2    标准错误(错误信息输出)

参考:(89条消息) Java -jar 如何在后台运行项目_刘信坚的博客的博客-CSDN博客_jar如何在后台运行

61、架构师的素养

 架构设计的主要目的是为了解决软件系统复杂度带来的问题,解决:高性能、高可用、可扩展

网友感悟是:架构即(重要)决策,是在一个有约束的盒子里去求解或接近最合适的解。这个有约束的盒子是团队经验、成本、资源、进度、业务所处阶段等所编织、掺杂在一起的综合体(人,财,物,时间,事情等)。

架构无优劣,但是存在恰当的架构用在合适的软件系统中,而这些就是决策的结果。 需求驱动架构。

在分析设计阶段,需要考虑一定的人力与时间去"跳出代码,总揽全局",为业务和IT技术之间搭建一座"桥梁"。


至少应该拿出3种架构方案,避免个人局限性。

对方案的选择可以通过:

  •        数量对比法:简单地看哪个方案的优点多就选哪个
  •        优缺点的加权法对比:让业务人员对质量属性进行排序

                (质量属性:如:性能、复杂度、人力、硬件成本、可靠性、可维护性)

今日得到:

1 架构是为了应对软件系统复杂度而提出的一个解决方案。

2 架构即(重要)决策

3 需求驱动架构,架起分析与设计实现的桥梁

4 架构与开发成本的关系 

依据原则:合适原则、简单原则、演化原则 

62、redis技巧、缩短key长度

场景:搜索服务,调用第三方服务,为了降低成本,故做缓存,降低调用第三方服务的频率,用户体验也不错。但面对 用户搜索请求,可能拼接很多条件,所以key应该如何设计呢?

思路:利用:相同的字符串经过md5算法,得到的 32位是一样的。因此,将 参数对象 变成字符串,通过 (md5算法)映射 成一个32位的 字符串

解决:

总结:有时候,可以把当前问题转换等价成 另一种方式。要有维度映射的思维

63、对象集合list 存成csv文件

关键词:list转csv

场景:虽然可以借助 csv依赖 ,一个字段一个字段的塞进去,但是效率慢

思路:根据csv是用 逗号,作为单元格进行区分,那么我们利用反射,可以一劳永逸

解决方法:

package com.XX.product.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ListToCsvUtil<T> {

    /**
     * @param list 对象列表
     * @param clazz 对象.class
     * @param header  是否需要表头
     * @return 对象属性值字符串
     */
    public String list2Csv(List<T> list, Class<T> clazz,Boolean header) {

        //创建
        List<Field> allFields = new ArrayList<>(100);

        // 获取当前对象的所有属性字段
        // clazz.getFields():获取public修饰的字段
        // clazz.getDeclaredFields(): 获取所有的字段包括private修饰的字段
        allFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        // 获取所有父类的字段, 父类中的字段需要逐级获取
        Class clazzSuper = clazz.getSuperclass();

        // 如果父类不是object,表明其继承的有其他类。 逐级获取所有父类的字段
        while (clazzSuper != Object.class) {
            allFields.addAll(Arrays.asList(clazzSuper.getDeclaredFields()));
            clazzSuper = clazzSuper.getSuperclass();
        }


        StringBuilder stringBuilder = new StringBuilder();
        if(header) {
            List<String> fields = allFields.stream().map(Field::getName).map(String::valueOf).collect(Collectors.toList());
            String headerString = String.join(",", fields);
            stringBuilder.append(headerString).append("\n");
        }

         // 设置字段可访问, 否则无法访问private修饰的变量值
        for (Field field : allFields) {
            field.setAccessible(true);
        }

        for (T item : list) {
           List<String> objectList = new ArrayList<>();
            for (Field field : allFields) {

                try {
                    // 获取字段名称
                   // String fieldName = field.getName();
                    // 获取指定对象的当前字段的值
                    String fieldVal = field.get(item)+"";
                    objectList.add(fieldVal);
                  //  System.out.println(fieldName + "=" + fieldVal);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
            String oneObject = String.join(",", objectList).replaceAll("\r\n"," ");
            stringBuilder.append(oneObject).append("\n");
        }

         for (Field field : allFields) {
            field.setAccessible(false);
        }
        return stringBuilder.toString();
    }
}

测试:

64、查询单表因数据量较大,导致sql耗时太久而失败。

关键词 :mybatis分页、慢sql、耗时久

场景:没有表关联、纯粹因为查询表数据较大,导致查询失败,一般是数据导出类的场景遇见

思路:分批查询。最后再用 list.addAll(部分list) 得到最终结果。如果结果过大,则考虑 批次设置稍大一点,文件分块传输。

分页代码:

        ArrayList<AwsTrueFitBO> resultList = new ArrayList<>();
  
        Long productCount = saleProductsService.selectCount(List.of(ProductConst.ONLINE, 
        for (int pageIndex = 0; pageIndex < (productCount / pageSize) + 1; pageIndex++) {
            List<AwsTrueFitBO> awsTrueFitBySize = getProductsByStatus(tableSuffix, pageIndex + 1, pageSize);
            if(!CollectionUtils.isEmpty(awsTrueFitBySize)) {
                resultList.addAll(awsTrueFitBySize);
            }
        }
        return resultList;
    public List<SaleProducts> getProductsByStatus(List<Integer> status, Integer start, Integer pageSize) {


//如果使用XXXMapper调用 则为 XXMapper.selectPage(xx,xx);//可看源码 很清晰
        IPage<SaleProducts> page = new Page<>(start, pageSize);
        page.setRecords(new ArrayList<SaleProducts>());
        return page(page, new LambdaQueryWrapper<SaleProducts>()
                .in(SaleProducts::getProductStatus,status)
                .isNull(SaleProducts::getDeletedAt)
                .orderByAsc(SaleProducts::getId))
                .getRecords();
    }

65、如何生成优雅的list
bug:

上面其实就是一个元素,实际应该为 List.of("1","2","5","7","8","15") 

实际执行的sql日志如下:(原本期望,应该有 ) 

 使用技巧:

List.of(数组)

Arrays.asList(数组)

66、JSON与对象转换的坑 (1)

场景:放入缓存的对象,再取出时,发现缺少下划线

原因分析:发现缺少下划线的字段,都是用了 @JsonProperty注解进行标注,而放入缓存时,使用的是JSON.toJSONString() 方法转换成字符串进行存储的。经过实验测试,发现 JSON.toJSONString()方法打印的原始的属性名称。

解决办法:使用:@JSONField代替@JsonProperty

对象上使用注解  

JSON.toJSONString(实例对象)//方法,会打印 原始对象 ,如:打印的是json中的key是将是 minPrice

扩展:@JSONField代替@JsonProperty的区别

①所属包不一样,@JSONField是fastjosn包里的,@JsonProperty是json包里的

②转换对象,调用配合的方法不一样。

@JsonProperty 搭配ObjectMapper().writeValueAsString(实体类)方法使用,将实体类转换成字符串。搭配ObjectMapper().readValue(字符串)方法使用,将字符串转换成实体类

@JSONField 搭配JSON.toJSONString(实体类)方法使用,将实体类转换成json字符串。搭配JSON.parseObject(字符串,实体类.class)方法使用,将字符串转换成实体类。

③ @JSONField  是转换成JSON字符串时,使用JSON.toJSONString()别名生效。

  @ JsonProperty是转换成 JSON字符串时,使用JSON.toJSONString()别名不效。

如果既想 转对象也别名生效、又想转字符串也生效,那么就两个一块加!如:

扩展:

1、将json字符串转换成对象时,使用@JsonProperty是无效的,需要使用@JSONField 

2、JSON字符串中含有不同key但都是同级,需求又是想把它们作为同级 看做是一个list时,定义成Map即可。

如:A,B.C同级 其中B是列表:

{
    "A": "1",
    "B": [
        "21",
        "22",
        "23"
    ],
    "C": 3
}

这定义的JAVA对象是:

class Obejct{
    private String A;
    private List<String> B;
    private String C;
}

 JSON是如下情况时,应该使用map转换

{
    "A":"1",
    "B":{
        "B1":"21",
        "B2":"22",
        "B3":"23"
    },
    "C":3
}
class Obejct{
    private String A;
    private Map<String,Object> B;
    private String C;
}

测试结果 

总结:对象本质就是Map,只不过的每个属性可以定义成不同的类型

JSON与对象转换的坑 (2) 

场景:使用fastjson解析json时,有些环境下,用boolean类型解析不到 is开头的属性

解决办法:尽量不使用is开头的属性,或者使用字符串去接收该json的值 

67、列表分批处理小技巧

场景:有时候,我们业务需要处理 较大的列表,可能会拿着这个列表去执行sql,导致查询慢、查询sql的结果大,甚至内存溢出。这时候就需要我们  把 大任务,分小点去处理。

分析:按正常操作,使用 写个循环,算区间值,取区间XXList.subList(start,end) ,能做 但是麻烦

更好的解决办法:使用java自带的 Lists.partion(列表,分批大小),遍历块,处理块!

         //创建20个元素的list
        List<Integer> list = new ArrayList<>(22);
        for (int i = 0; i < 22; i++) {
            list.add(i);
        }
        
        //按5个一组进行处理(输出)
        List<List<Integer>> partition = Lists.partition(list, 5);
        for (List<Integer> integers : partition) {
            System.out.println(integers);
        }

输出:

[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[10, 11, 12, 13, 14]
[15, 16, 17, 18, 19]
[20, 21]

68、限流器 RateLimiter

场景:上传数据过程中,调用第三方接口,每分钟不能超过100次请求,超过后将被直接抛出异常,多线程情况下,没法确保 单位时间内的请求是否过于密集

分析:能不能参考缓存的涉及,生产这随便生产,但我消费者按固定速度去取?

解决办法:使用限流器

google开源工具包guava提供了限流工具类RateLimiter,该类基于“令牌桶算法”,非常方便使用。

代码使用:

import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;
import org.springframework.util.StopWatch;
。。。。
  
public static void main(String[] args) {
       
        //1分钟内调test的次数不超过10次
        //限流器
        RateLimiter rateLimiter = RateLimiter.create(10);//其实参数就是QPS的值
        for (int i = 0; i < 1000; i++) {
            //计时器
            StopWatch stopwatch = new StopWatch();
            stopwatch.start();

            //执行目标函数
            rateLimiter.acquire();
            test();

            //打印执行多久
            stopwatch.stop();
            System.out.println( stopwatch.getTotalTimeMillis());
        }

    }

    public static void test(){
        System.out.println("test");
    }

执行结果:基本是100ms执行一次,实现了1秒执行10下的机制

 

多线程下使用:无论创建1个线程还是20个线程,下列输出只会输出每秒打印一次:

执行了  16669386XX035

import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;

public class JsonTest {



    public static void main(String[] args) {

        //1分钟内调test的次数不超过100次
        //限流器
        RateLimiter rateLimiter = RateLimiter.create(1);
        for (int i = 0; i < 20; i++) { //101执行
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if (rateLimiter.tryAcquire()) {
                            //执行
                            tagrget();
                        }
                    }
                }
            }).start();
        }
    }

    public static void tagrget() {
        System.out.println("执行了"+"  "+System.currentTimeMillis());
    }
}

另外,多线程使用同一个加锁的函数,让函数进行睡眠,也能起到限制调用 

实现限流器的算法一般:

69、在idea排查CPU、内存OOM的方法

场景:在本地运行调试重新,寻找哪些方法比较占cpu、内存

工具:使用 profiler 

步骤:

1、选取要观察的应用

 

 2、右键可以看该应用的cpu、head memory 、threads如下:

 3、最nb的功能是:(将会监听一段时间,然后你手动停止监听,就可以查看结果了。类似于医生听诊后,得出结论,该结论能查看 每个函数占用的内存和cpu)

 听诊结果:

 蓝色部分,类似于堆栈信息,下一层级的是上一层级的总和,如

70 、记录一次OOM事故

场景:。应用部署一段时间后就停止了停止,日志有出现OOM。新增了3个任务,A任务每10分钟执行一次(2分钟左右能执行完成),B任务有一天执行一次(1小时左右能够执行完成)。AB每次执行均使用同一个线程池,且每个任务需要35个线程执行,C任务每次也需要有35个线程。执行4个小时左右。线程池设置如下:

 原因分析:考虑到服务的的限制,A任务执行的又快,所以设置如上。事实上,单独运行A任务时的执行情况是:35个任务到提交到线程池,其中1-8个任务直接拿去现有的线程池开始执行,剩下的35-8=27个任务在linkedBockingQueue阻塞队里里等待,由于没有指定阻塞队里大小(默认:Ineger.max)因此,虽然没有达到 maximumPoolSize,但也不会再创建新线程,直至1-8个任务逐个完成,空闲的线程才去处理被堵塞的。

当B任务开始后。B占用1-8个线程,队列堵塞27个,后来的A的任务每隔10分钟提交35个任务,由于B任务持续60分钟左右才执行完成,则 队列会堵塞,27+60/10 *35=117个任务

同类C任务开始后,会阻塞 117+240/10 *35=957个,最终导致OOM

解决方法:一个项目中应该对不同的任务创建不同的线程池,避免多个任务共用一个线程池

注意: 由此可知,创建线程池时,LinkedBlockingQueue   必须指定阻塞队列的大小!否则线程池往往 maxnumPooLSize没气作用,是个摆设!

阻塞队列有6个种,常见3种如下:

  1. ArrayBlockingQueue
  2. LinkedBlockingQueue   默认大小 Integer.max
  3. PriorityBlockingQueue    默认大小11,优先级按 compareTo方法排序

71、通用的简易网络客户端模板

 场景:经常调用第三方服务、无非是url、请求头设置(有时候需要将密码设置进请求头)、请求参数 共3个组成,自己编写总是随心所欲没有多少规范,使用的也比较原生,因此想找应该通用的工具类,代码如下:

构建请求函数:

    public static HttpRequest httpRequest(String uri, String key, String method, String contents) {
        contents = contents == null ? "" : contents;
        var builder = HttpRequest.newBuilder();
        builder.uri(URI.create(url));
        builder.setHeader("content-type", "application/json");
        builder.setHeader("api-key", key);

        switch (method) {
            case "GET":
                builder = builder.GET();
                break;
            case "HEAD":
                builder = builder.GET();
                break;
            case "DELETE":
                builder = builder.DELETE();
                break;
            case "PUT":
                builder = builder.PUT(HttpRequest.BodyPublishers.ofString(contents));
                break;
            case "POST":
                builder = builder.POST(HttpRequest.BodyPublishers.ofString(contents));
                break;
            default:
                throw new IllegalArgumentException(String.format("Can't create request for method '%s'", method));
        }
        return builder.build();
    }


发起请求:

    private final static HttpClient client = HttpClient.newHttpClient();//全局变量

    private static HttpResponse<String> sendRequest(HttpRequest request) throws IOException, InterruptedException {
        log.info(String.format("%s: %s", request.method(), request.uri()));
        return client.send(request, HttpResponse.BodyHandlers.ofString());
    }

响应情况:

    public static boolean isSuccessResponse(HttpResponse<String> response) {
        try {
            int responseCode = response.statusCode();

            log.info("\n Response code = " + responseCode);

            if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_ACCEPTED
                    || responseCode == HttpURLConnection.HTTP_NO_CONTENT || responseCode == HttpURLConnection.HTTP_CREATED) {
                return true;
            }

            // We got an error
            var msg = response.body();
            if (msg != null) {
                log.error(String.format("\n MESSAGE: %s", msg));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

//最终调用示例

72、线程安全的List

场景: 多个线程操作同一个全局变量list时,需要使用线程安全的list

线程安全的三种办法:

  • ①Vector
  • ②Collections.synchronizedList()
  • ③CopyOnWriteArrayList.

其中,CopyOnWriteArrayList效率最高,其原理是 对Arraylist操作前,先拷贝,再进行增删,增删后,再把原指针指向最新的数组对象。因为涉及到创建、拷贝对象,所以适合 读多写少的情况

文章参考:(129条消息) 三种线程安全的List_橙不甜橘不酸的博客-CSDN博客_线程安全list

73、比较两个list中的元素是否相等 ,以比较两个集合是否相等

关键字:集合比较、对象比较

场景:新老脚本改造,需要对比两个数据库的记录是否相等,因此需要分别连接两个数据库,查询出后,对记录进行比较。部分字段又不想参与比较,如id、create_time这些无意义的比较想忽略

思路:集合中的元素,原本可以重写每个类的compare()方法,但是只能通用一直类型的,既然需要大范围的调用,排除该方法。考虑到 toSting()方法,比较每个对象的toString即可

解决:思想对象转map,移除不参与比较的字段,再用toString(),进行比较

package com.XXX.comparedata.utils;


import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class CompareListData<T> {

    public boolean compare(List<T> list1, List<T> list2) {
        if (list1.size() != list2.size()) {
            return false;
        }
       //list中的元素转换成map
        List<Map<String, Object>> src = getMapList(list1);
        List<Map<String, Object>> tag = getMapList(list2);
        //比较两个map集合是否一致
        return Objects.deepEquals(src, tag);
    }

    @NotNull
    private List<Map<String, Object>> getMapList(List<T> list) {
        List<Map<String, Object>> arrayList = new ArrayList<>();
        list.forEach(t -> {
            //Map<String, Object> map = BeanUtils.beanToMap(t);//该方法会改变源对象
            Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(t), Map.class);
            //移除这些值(改map不可变,所以采用值覆盖的方式)
            if(map.get("createTime")!=null){
                map.put("createTime",null);
            }
            if(map.get("updateTime")!=null){
                map.put("updateTime",null);
            }
            if(map.get("id")!=null){
                map.put("id",null);
            }
            arrayList.add(map);
        });
        return arrayList;
    }


}

74、获取符合正则表达式的字符串集合

关键词:工具,数据提取

解决:

  private Set<Integer> getInfoByReg(String s, String regex) {
        Set<Integer> imagesIdList = new HashSet<>();
        Pattern pattern = Pattern.compile(regex);

        java.util.regex.Matcher matcher = pattern.matcher(s);
        while (matcher.find()) {
            System.out.println(matcher.group(1));
            imagesIdList.add(Integer.parseInt(matcher.group(1).replace("\"", "")));
        }
        return imagesIdList;
    }

75、java中的基础知识点

关键字:技巧、效率

  1. 函数的参数不能设置默认值,但可以通过重载进行实现
  2. 事务的本质是原子性,虽然他的特点有4个:一致性、隔离性、原子性、持久性。
    1. 在读写分离时,如果某个函数开启了事务,该函数涉及到的一切增删改查将都会在主库(默认写)中执行,在调试程序时需要关闭该事务,以方便调试持续,查就会一定在从库,增删改一定在主库执行。读写分离似乎能够找到整个流程是否存在增删改的操作,以便在最初查询时就选主库或从库(但判断条件后的增删改查它好像选的不一定对!)

76、RabbitMq知识点

rabitMq的概念关系:

虚拟机上有多个交换机、每个交换机上有多个队列.,虚拟机类似于命名空间的作用

rabbitMq交换机的3种类型:topic、fanout、direct,这么多类型是为了各种匹配队列

当交换机类型是topic时 使用注解监听:直接指名队列即可

@RabbitListener(queues = {"队列名称"}, containerFactory = "occContainerFactory", concurrency = "1") 

 当交换机类型是direct时,使用注解监听:

   @RabbitListener(containerFactory = "XXXContainerFactory",
           bindings = {@QueueBinding(
                   value = @Queue("queueName"),
                   exchange = @Exchange(value = "XXXexchange"),
                   key = {"路由key"}
           )})

扩展:假如 AAExchange 已经绑定了队列A,现在想让消息也复制一份发送到队列B的做法:

①新建队列B②将队列别 绑定到交换机AAExchange,并绑定和队列A的路由key一样的key

如下:

77、项目结构-经验

场景:项目越来越大代码越来越难维护,虽然一直本着  3层的结构进行(如下:)开发,但每个人开发人员理解和接触的层名称不太一样。

演化-1版本:

controller---->service----->dao

解释:早期使用mybatis的xml编写放在了 mapper中或使用hibernate,查数据的定义函数放在了dao层中,每个表在dao中都唯一对应,service层却与数据库中的表不在一一对应,可能会注入多个dao,整合好后再返回数据即可。

演化-2版本

controller--->service---->mapper

解释:随着mybatis-plus的使用,越来越多的sql不再写在mapper.xml中,使用代码生成器会生成每个数据库表对应的一个service类,其实这个虽然叫service 但本质上就是 版本1 中的dao层,于是有出现概念模糊的,有时候会把 一个复杂的业务写在  某个表对应的service中,导致后期维护越来越难!!,这也是项目维护的痛点

演化-3版本

鉴于版本2,于是出现

controller----->bus----->service----->mapper

解释:bus层用来放 复杂的业务(涉及到2+张表以上的查询,因为该层某类要注入2+个以上的  service类),这样就只需要在sevice层中  进行lamba的简单查询语句即可,以便后期需求复用。

总结:随着mybatis-plus和jdk的升级,淡化掉了dao层或mapper层,本质一直都是三层结构,尽量本着:操作数据库的类职能单一 ,不易复用的代码才是真的的业务层

形如以下mapper.selectLisy()查询就不应该出现在逻辑代码中(因为下下面还有map的操作等),

 这样写,以后有人使用查询还要再写一遍!

应该是

使用某个表对应的service.XXXX();该XXX方法的实现在,该表对应的service中:

: 

78、nacos的项目中引用的值

关键词:开关、naocs刷新

场景:项目代码部署后,不想来回部署项目,而实现if esle之类的代码生效,这时候可以使用nacos配置中心的功能

方案1实现:

①naocs的该项目的配置文件夹中 配置了 AA=true

 ②类头上标记刷新注解 :@RefreshScope

③引用naocs的变量 :@Value("${AA:true}") //可以设置默认值   如下: 

import org.springframework.cloud.context.config.annotation.RefreshScope;


@RefreshScope  //标记这个文件的值会刷新
@Compent
class aaa{

    //引用naocs中的变量
    @Value("${AA}")
    pribate Boolean aaaa;

    public void sss(){
        //业务代码
        if(aaaa){
            //todo1
        }esle{
            //todo2    
        }

    }
}

方案2实现:推荐(遇到一次方案1不生效,即使改了naocs的变量值,控制台打印了AA change了 但值就是没变化)

新创建配置类,该类只有各种naocs上的变量,如下:

使用值的地方用 bean注入的方式 :

@Auowire

priavte  NacosConfig  naocos;


。。。。。
Boolean = nacos.getBasic();
。。。。

79、idea必装插件:

为了提高开发效率,工具的使用贼方便

①界面主题:Material Theme Ui  选Moonlight主题

②MyBatisX

③GitToolBox  点击某行代码,显示代码谁某时写的

④Grep Console   方便看控制台日志,可以自定义设置颜色

⑤Maven Helper  能够查看maven的jar依赖、冲突

⑥ SequenceDiahram 对函数右键 选择后,生成 调用链,方便理解和阅读

⑦Translation  翻译

⑧Github Copilot  (Ai代码机器人,收费,淘宝50认证学生一年免费)

80、工具类的使用

java项目中,一般都有util包,用于存放自定义的工具类,为了避免重复造轮子,且自己写的工具类还可能有bug.
事实上很多hutool、Guava、Apache Commons包已经给我们提供了各类工具,
每当自己想创建一个工具类时,记得先去看看hutool中是否含有,或者第三方工具包已经含有

由于文字太大:分2篇

(146条消息) 开发中遇到的问题和经验 记录 ------- 后端篇(2)_飞花落雨的博客-CSDN博客

81、细节bug

①集合判空不要用Objects.isNull(集合),因为集合元素为0个的结果是为假

  • 7
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值