赞
踩
核心价值就是把现实世界的业务操作搬到计算机上,通过计算机软件和网络进行业务和数据处理,但是时至今日,能用计算机软件提高效率的地方,几乎已经被全部发掘过了,必须能够发掘出用户自己都没有发现的需求,必须洞悉用户自己都不了解的自己。于是出现了:
大数据 + 机器学习”
本文致力于解决日常遇见的坑和一些个人经验,解决不必要的坑 耽误开发效
目录
1、 技巧:向spring注入对象:@configuration 的使用:
6、关键词:ArrayList toString() 空格 容易踩的坑
14、界面点击按钮不生效,往往是js文件加载错,文件名写错导致的
17、数据库查询 和 hibernate查询的 条数一样,但部分行不一样。
19、Idea在debug模式下,停止当前函数(不执行断点后的代码)
20、相同项目代码,运行在两个环境,一个正常执行,一个等待很久才报错
25、编写单元测试,@Autowired注入的对象为null问题。
27、接口开发 、其他系统的接口请求、postman请求可以,java请求有问题。
29、SpringBoot配置文件中的值读取、@values注解
36、java CURD mysql时 字段带"-",即短横问题
38、Collectors.toMap()、XX.stream().flatMap()的使用
44、sql为某字段设置默认值、修改字段类型、sql自动设置更新时间
48、通过断点调试-查看数据源 (借助mybatis的mapper)
步骤5:在 任务调度中心 配置我们的这个执行器 上面时候执行,执行时携带什么参数:
51、springboot 日志只输出 springboot和mybatis的的logo
59、springboot加载resource文件夹下 的自定义文件
(关键词:多数据源 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 ...
- }
- }
公司使用的数据库是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小批量查询处理的思想。
使用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()
-
- Thread t1=new Thread(()->{
- 语句块;
- }) ;
-
- t1.join();
- Thread t2=new Thread(()->{
- 语句块;
- }).start() ;
-
- 在t2线程里调用t1,表示 t1执行完成 才执行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()方法,即在所有线程执行完毕后关闭线程池,也能检测到线程关闭
场景:新人入场,我拷贝了一份旧代码作为练习项目发给他,结果我的电脑可以运行,可以登录进系统。他的电脑却死活登录不进去。
原因: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一下。
前端:
- <!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();
- }
- }
- }

场景:有时候想要使用获取list的元素拼接成字符串,于是:
list.toString().replace("[",").replace("]","");
问题:这种拼接 会在每个元素前有一个逗号和空格,后面再使用此字符串会出现问题。
解决:推荐使用原生自带的拼接
String str = String.join(",",myList);
后台查出实体对象,往往需要转换成Vo对象,向前台展示,实际开发中,很多时候 Vo的字段与实体对象的字段名是有重复的,如果遍历 List<entity> 一个一个set到Vo对象,十分繁琐,这时候可以使用:BeanUtils.copyproperties(entrity,nXXVo());
- //spring提供的工具类
-
- for(User user:userList){
- BeanUtils.copyproperties(user,new UserVo());
- }
注: 需要实体的字段、类型 和Vo的字段、类型都一致 才能设置进去,否则设不进去。对应字段不一样的 可以手动set
经验:此方法对后续维护其实不是很方便
场景:查询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的数据均去空格处理
场景:前端向后台传参数,后台用对象接收,报错400。
原因:一般是因为前端传参不符合 接收对象的属性。即:不匹配。但是传参多时,一个一个核对很耗时,也不一定能核对出来原因。
技巧: 在后台参数列表里,多加一个:BindingResult ,可以查看具体不匹配的字段。
- public String list(BindingResult b,Model model, User user){
- }
后端:使用 @RequestBody注解 标记在 依据json对象转的java对象 即可
后端:使用@RequestParam注解 可以指定参数绑定到当前参数,并可设置默认值
@RequestParam 可以用来接收Get POST类型的请求
@RequestBody 可以用来接收POST请求json格式的参数
调优的目的:为了减少SWT,即:stop world time。 每次发生full gc 都会造成系统的卡顿,所以要尽量避免,full gc.
一般的性能调优都是jstack,但比较好用的是阿里巴巴开发的 Arthas 性能调优非常强大,
使用方法:
1)、下载jar包
2)、 运行jar包
java -jar arthas-boot-jar
3、输入 你想要查看 某jar应用程序的 序号,再回车,会出现:
4、比如上图 发现 ID 为8的线程占用cpu 88%,所以现在查看该线程,使用命令 thread 8 即可打印出现移除的源代码:
2、常遇到的问题:系统应用频繁的full gc,导致用户体验差。
分析:性能调优,不得不先了解jvm的内存模块:
①堆:
标记整理算法:是老年代的
问题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区的比例!。
②方法区(元空间)
存放类编译后的信息,如静态变量、类的属性
③程序计算器
④虚拟机栈
详细可参考:虚拟机栈的栈帧都包含些什么?_yozzs的博客-CSDN博客_虚拟机栈存放什么数据
① 局部变量表(因为局部变量属于函数嘛,都是临时工 要在这里登机一下咯)
② 操作数栈(从局部变量表里取 某变量,在这里进行 加减乘除的运算)
③ 动态链接(理解不够深刻):将符号变量替换成直接引用
④ 方法出口.(计算完结果 把结果返回到 上一个压入栈的 帧的地址,也可能计算途中遇到异常,所以可能返回 异常出口的地址、或者 方法正常执行完成的 返回的地址)。
⑤本地方法栈
和虚拟机栈几乎一样,只不过这里调用的都是 native方法 都是执行c++或者c方法。
(135条消息) 五位卷王 | 总结的十道 JVM 面试真题!(建议收藏)_hzbooks的博客-CSDN博客
使用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]
-

当你使用 对象.toString() 方法时,最好使用 +"" 方式 代替,因为 对象有可能是null,执行时会导致空指针异常。但是null对象+字符串=“null” 所以还要replaeAll("null","");
把数组放入到 ArrayList中
虽然这个问题很low 但是使用idea,有时候使用Refaotr导致改文件名会不小心改动了其他的地方。
浏览器控制台:Failed to load resouce:the XXX.js server responesd with a status of 404 即战斗资源。
场景:使用springmvc查询数据并返回页面。本地运行正常,但在服务器部署出现如下错误:
报错日志:Could not resolve view with name 'XXXX' in servlet with name 'springmvc'
思路:发现 return "XXXX" 是 XXXX和实际的页面文件名称不一样,自然是找不到页面的了,可是为什么 本地可以 而服务器却不可以?难道是运行在tomcat上的项目 spring的视图解析器不区分大小写,而运行在liberty上区分大小写?
原因:只是指定servlet相对路径下的视图文件。所以是否敏感取决于servlet对文件名大小写是否敏感,或者说归根到底,取决于操作系统对文件名大小写是否敏感。
总结:无论是什么,最好return的页面和实际页面名称保持一致。莫粗心,慎用idea的查找替换。
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博客
场景:测试测试查询条件,我在数据库里造了几条不同的数据,只改动了部分列的值。
现象:数据库查的条数 和开发的系统查的条数一致,但内容却不一致。
原因:查询的该表未设置主键,而hibernate的实体类上指定了主键。于是在查询时候,hibernate会认为相同 id值的就是同一个对象,造成 行覆盖,出现了:明明数据库有这条数据,但在java查询查出来的还是 却没有这条数据.
基本思路:找到应用程序的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是显示 路径名称
场景:断点调试查看某个功能时,有时候只是为了看流程,但当其执行到操作数据库时,并不想真的去提交数据到数据库,这时候要么终止项目,再重启项目(效率低),要么使用这个方法:
参考博客:
Idea在debug模式下,直接停止程序(不执行断点后的代码)_云别-CSDN博客_idea结束debug
关键词:断点调试执行要等很久
场景:拷贝项目到本地,运行发现断点执行某句语句时,要等很久。
原因:一般情况是最后报 超时错误。(多翻一下报错日志,看看含time、socket字样),发现是 请求超时等问题,查看我这项目也没什么请求的啊。报错是执行查询hbase时报错的。
解决:项目使用的数据库地址是 映射的。即在host文件中配置了 具体的ip地址,而我的电脑host没有那个映射 自然 查询不到数据库导致的报错。坑爹项目!
(关键字:部署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 - 博客园
方法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); //输出李四
- }
知识点学习:
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 万字详解!还有谁不会?
扩展:@import注解,顾名思义,导入,即把类加入Spring IOC容器。
有多种方式能让类加IOC容器管理,如@Bean、@Component等,@Import是另外一种方式,更加快捷。
@Import支持 三种方式
1.带有@Configuration的配置类(4.2 版本之前只可以导入配置类,4.2版本之后 也可以导入 普通类) 加上@Configuration是为了能让Spring 扫描到这个类
2.ImportSelector 的实现
3.ImportBeanDefinitionRegistrar 的实现
①创建项目,父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=\
- 我们的类路径
扩展:
坑:有时候 偷懒 直接传参,如:String ids ="1,2,3" 字符串作为参数 在xml中拼接xsql时
select * from XXX_table in (#{ids}) 而没使用foreach进行拼接。
结果:不报错,但返回的数据只有一条
总结:偷懒的写法,日志打印 只有一个? 常规的写法,日志打印 ??? 3个占位符
文章参考:#{}和${}的区别_lt_zl的博客-CSDN博客
场景:自己编写了一个查询的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("");
- }
-
- }

如果想让单元测试插入数据库则需要加上@Roolback(false),如下:
- @Test
- @Transactional(rollbackFor = Exception.class)
- @Rollback(false) //不写的话,单元测试成功后也默认回滚
- public void test() {
- insertAll();
- quertList();
- System.out.println("hello");
- }
运行中的项目,更新了配置文件,又不想重启,如何让程序自动更新加载这个文件呢?
首先,想到的是写个死循环,让这个线程一直去查看文件是否改变,改变则重新读取。但是浪费cpu资源啊。
个人想法(未实践):使用观察者模式,A改变状态,通知B去读取配置文件。即:运行的程序比如预留了一个controller的requestMapping,如:htttp://ip/item/myselfequest。当我们更新完服务器上的配置文件后,在服务器上请求一下预留的那根请求。curl "htttp://ip/item/myselfeques",通知B去重新读取配置文件。也就是手动触发。
场景:开发稍大的系统,都会遇到 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坑
高效的查找命令,有助于快速有效定位问题。
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"
-
-
场景:一般java取值从配置文件中取我们配好的值,但是避免有人误删了配置文件中的值,所以会在代码中也有默认值,如下写法:
即使配置文件和java中的值不一样,会先读取nacos里的,其次是配置文件里的,最后是默认值,如果没有则会报错。
因为其他地方想调时,对象类型限制了,且因为函数内部有使用 XXX= 对象.属性 这样的赋值,导致函数不能通用,所以如果参数不是很多的话,除了 controller层其他层传参最好使用 基本类型的方式。
此方式会很大程度上解决,很多函数功能相似,但又无法适用当前开发需求 的问题
执行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

Ctrl+Alt+V 自动创建返回值及变量
Ctrl+Alt+ <-- 调回上次调用该函数的位置 特别有效防止看代码时跳的晕头转向
@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
场景:每次本地部署应用和远程部署一样,都要手动各创建一份请求,除了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怎么删除环境变量
场景:按照mysql的数据结果,默认排序结果是 id列从小到大的排列。
select * from user limit 0,5 取 前5条数据 这是没有什么问题的,不用加order by
如果在sql语句中不指定order by排序条件,那么得到的结果集的排序顺序是与查询列有关的。因为不同的查询列可能会用到不同的索引,从而导致顺序不同。所以无论什么情况下,还是加上为好。
(65条消息) MySQL查询默认排序与order by排序_韩某的博客-CSDN博客_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中查询却发现的确报错。所以加单引号
场景:开发时,一般分配的任务,自己在远程新开一个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远程(相当于告诉 这个文件我删了 你得删吧)
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.姓名)
场景:使用mybatis查询或插入时,myabatis报错,查无此列,核对了 表结构、以及对应的实体 发现都没问题 也都没这个字段,那么这个字段哪里来的??
解决:发现 对应的实体 继承了 其他类, 该类的私有字段也给继承回来了!!
所以:子类继承父类 是继承了父类的所有!! 包括private修饰的属性!!
场景:一般发生在更新或者删除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后发现也好了。
场景:由于要数据解析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 接口 实现参数入参的统一格式
场景:公司使用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 ;
-
- }

多个线程各自拷贝一份 ThreadLocal修饰的变量 到自己本地,即:不会对原本的变量修改,只修改副本。
注:ThreadLocal变量通常设置为static的原因:避免每次使用该类就创建该变量,节省空间。不然,每次创建 一个值相等的对象,但这些对象的地址又不同,造成浪费
一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。
原文链接:https://blog.csdn.net/u013543848/article/details/102980066
注:其实这是 覆盖操作,所以记得 可能需要 补上一些额外的 限制语句,如下: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代码中,就不用再写设置 这两个字段的业务逻辑了。
思路: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();
- }
-
- }

关键词:主从复制,多数据源
步骤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负责查询,两处数据源可以配置不同,也可以配置为同一个数据源
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
优势,可以通过界面去管理或触发 任务执行,比@schedule 注解强多了,后者是死的、定时的,一般用于单机上的执行。
使用:
- <dependency>
- <groupId>com.xuxueli</groupId>
- <artifactId>xxl-job-core</artifactId>
- <version>${xxljob.version}</version>
- </dependency>
- 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
- 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;
- }
-
-
-
- }

- 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("");
- }
- }

idea控制台也可以看见是否注册成功
xxl-job的控任务调度执行也可以核对是否注册上来:
关键词:@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>

(可能配置麻烦)
grafana 是一款采用 go 语言编写的开源应用,是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知
配置样例:
钉钉 自定义机器人接入:自动发到群消息:
https://blog.csdn.net/u013372493/article/details/124819854
- //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();
- }
- }

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" 是否存在。(上文的配置有给出,所以存在)
场景:开发项目,引入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是程序启动时的引导,包含了应用上下文的引导
前提是公司已经给你申请了 秘钥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
方式一: 比较原始的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()));
- }
身份认证:
- @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博客
场景:get请求模式不准变化的需求,且众多参数,接收参数的对象,如果属性和get请求携带的参数 一模一样,自然会接收成功,如下:
- get 请求: XXX/XX/site_id=100¶m1=&....
-
- 接收该请求要封装的对象:
-
- @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;}
-
- }
查询思路:先理解你公司所在的索引字段,一般是某个模块 会应了一个项目名称字段:如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: 远程主机强迫关闭了一个现有的连接。
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 默认仓库地址
方式1:
String path = this.getClass().getClassLoader().getResource("my.json").getPath();
方式2:(推荐)
ClassPathResource resource = new ClassPathResource("my.json");
nohup java -jar babyshark-0.0.1-SNAPSHOT.jar > log.file 2>&1 &
上面的2 和 1 的意思如下:
0 标准输入(一般是键盘)
1 标准输出(一般是显示屏,是用户终端控制台)
2 标准错误(错误信息输出)
参考:(89条消息) Java -jar 如何在后台运行项目_刘信坚的博客的博客-CSDN博客_jar如何在后台运行
架构设计的主要目的是为了解决软件系统复杂度带来的问题,解决:高性能、高可用、可扩展
网友感悟是:架构即(重要)决策,是在一个有约束的盒子里去求解或接近最合适的解。这个有约束的盒子是团队经验、成本、资源、进度、业务所处阶段等所编织、掺杂在一起的综合体(人,财,物,时间,事情等)。
架构无优劣,但是存在恰当的架构用在合适的软件系统中,而这些就是决策的结果。 需求驱动架构。
在分析设计阶段,需要考虑一定的人力与时间去"跳出代码,总揽全局",为业务和IT技术之间搭建一座"桥梁"。
至少应该拿出3种架构方案,避免个人局限性。
对方案的选择可以通过:
(质量属性:如:性能、复杂度、人力、硬件成本、可靠性、可维护性)
今日得到:
1 架构是为了应对软件系统复杂度而提出的一个解决方案。
2 架构即(重要)决策
3 需求驱动架构,架起分析与设计实现的桥梁
4 架构与开发成本的关系
依据原则:合适原则、简单原则、演化原则
场景:搜索服务,调用第三方服务,为了降低成本,故做缓存,降低调用第三方服务的频率,用户体验也不错。但面对 用户搜索请求,可能拼接很多条件,所以key应该如何设计呢?
思路:利用:相同的字符串经过md5算法,得到的 32位是一样的。因此,将 参数对象 变成字符串,通过 (md5算法)映射 成一个32位的 字符串
解决:
总结:有时候,可以把当前问题转换等价成 另一种方式。要有维度映射的思维
关键词: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();
- }
- }

测试:
关键词 :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();
- }
上面其实就是一个元素,实际应该为 List.of("1","2","5","7","8","15")
实际执行的sql日志如下:(原本期望,应该有 )
使用技巧:
List.of(数组)
Arrays.asList(数组)
场景:放入缓存的对象,再取出时,发现缺少下划线
原因分析:发现缺少下划线的字段,都是用了 @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,只不过的每个属性可以定义成不同的类型
场景:使用fastjson解析json时,有些环境下,用boolean类型解析不到 is开头的属性
解决办法:尽量不使用is开头的属性,或者使用字符串去接收该json的值
场景:有时候,我们业务需要处理 较大的列表,可能会拿着这个列表去执行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]
场景:上传数据过程中,调用第三方接口,每分钟不能超过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());
- }
- }

另外,多线程使用同一个加锁的函数,让函数进行睡眠,也能起到限制调用
实现限流器的算法一般:
场景:在本地运行调试重新,寻找哪些方法比较占cpu、内存
工具:使用 profiler
步骤:
1、选取要观察的应用
2、右键可以看该应用的cpu、head memory 、threads如下:
3、最nb的功能是:(将会监听一段时间,然后你手动停止监听,就可以查看结果了。类似于医生听诊后,得出结论,该结论能查看 每个函数占用的内存和cpu)
听诊结果:
蓝色部分,类似于堆栈信息,下一层级的是上一层级的总和,如
场景:。应用部署一段时间后就停止了停止,日志有出现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种如下:
场景:经常调用第三方服务、无非是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;
- }

//最终调用示例
场景: 多个线程操作同一个全局变量list时,需要使用线程安全的list
线程安全的三种办法:
其中,CopyOnWriteArrayList效率最高,其原理是 对Arraylist操作前,先拷贝,再进行增删,增删后,再把原指针指向最新的数组对象。因为涉及到创建、拷贝对象,所以适合 读多写少的情况
文章参考:(129条消息) 三种线程安全的List_橙不甜橘不酸的博客-CSDN博客_线程安全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;
- }
-
-
- }

关键词:工具,数据提取
解决:
- 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;
- }
关键字:技巧、效率
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
如下:
场景:项目越来越大代码越来越难维护,虽然一直本着 3层的结构进行(如下:)开发,但每个人开发人员理解和接触的层名称不太一样。
controller---->service----->dao
解释:早期使用mybatis的xml编写放在了 mapper中或使用hibernate,查数据的定义函数放在了dao层中,每个表在dao中都唯一对应,service层却与数据库中的表不在一一对应,可能会注入多个dao,整合好后再返回数据即可。
controller--->service---->mapper
解释:随着mybatis-plus的使用,越来越多的sql不再写在mapper.xml中,使用代码生成器会生成每个数据库表对应的一个service类,其实这个虽然叫service 但本质上就是 版本1 中的dao层,于是有出现概念模糊的,有时候会把 一个复杂的业务写在 某个表对应的service中,导致后期维护越来越难!!,这也是项目维护的痛点
鉴于版本2,于是出现
controller----->bus----->service----->mapper
解释:bus层用来放 复杂的业务(涉及到2+张表以上的查询,因为该层某类要注入2+个以上的 service类),这样就只需要在sevice层中 进行lamba的简单查询语句即可,以便后期需求复用。
总结:随着mybatis-plus和jdk的升级,淡化掉了dao层或mapper层,本质一直都是三层结构,尽量本着:操作数据库的类职能单一 ,不易复用的代码才是真的的业务层
形如以下mapper.selectLisy()查询就不应该出现在逻辑代码中(因为下下面还有map的操作等),
这样写,以后有人使用查询还要再写一遍!
应该是
使用某个表对应的service.XXXX();该XXX方法的实现在,该表对应的service中:
:
关键词:开关、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();
- 。。。。
为了提高开发效率,工具的使用贼方便
①界面主题:Material Theme Ui 选Moonlight主题
②MyBatisX
③GitToolBox 点击某行代码,显示代码谁某时写的
④Grep Console 方便看控制台日志,可以自定义设置颜色
⑤Maven Helper 能够查看maven的jar依赖、冲突
⑥ SequenceDiahram 对函数右键 选择后,生成 调用链,方便理解和阅读
⑦Translation 翻译
⑧Github Copilot (Ai代码机器人,收费,淘宝50认证学生一年免费)
java项目中,一般都有util包,用于存放自定义的工具类,为了避免重复造轮子,且自己写的工具类还可能有bug.
事实上很多hutool、Guava、Apache Commons包已经给我们提供了各类工具,
每当自己想创建一个工具类时,记得先去看看hutool中是否含有,或者第三方工具包已经含有
由于文字太大:分2篇
(146条消息) 开发中遇到的问题和经验 记录 ------- 后端篇(2)_飞花落雨的博客-CSDN博客
81、细节bug
①集合判空不要用Objects.isNull(集合),因为集合元素为0个的结果是为假
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。