Java学习总结(答案版)

4.14 学习总结:

按照以日为维度查询一周中每一天的统计数 使用DATE_FORMAT(r.createDate,‘%Y-%m-%d’)最后要group by DATE_FORMAT(r.createDate,‘%Y-%m-%d’)
按照以月为维度查询半年中每月的统计数 使用DATE_FORMAT(r.createDate,‘%Y-%m’)最后要group by DATE_FORMAT(r.createDate,‘%Y-%m’)
mysql中使用date_add()函数

4.15学习总结

了解到MySql的日期函数date_sub(now(), interval 7 day)
比如now() 得到当前时间是 2022-01-15 22:36:43
那么date_sub(now(), interval 7 day) 得到的时间就是 2022-01-08 22:36:43
current_date 获取当前时间,精确到日

4.18 学习总结:

(重点:@PostMapper)在restful风格里面 传参里面的参数必须序列化,要不然只会传入其格式例如:json/application
@PostMapper @RequestBody @Valid DynamicTrendAnalysisParam queryParam
查询时不能全部查出,试试分俩个sql写出来

4.19学习总结:

数据概括:(查询慢的sql)select 查询 关联字段放到where 比如 u.tenanatId=s.tenantId 还有查询时间范围拿到where里面
类型分析: 按照type进行分析就要group by type 关键字on(里面只要关联字段)

4.20学习总结:

热度分析:SaleManRankReportServiceImpl 296行
Set typeEnumSet = new HashSet<>();
typeEnumSet.add(ClewTypeEnum.STYLE);
typeEnumSet.add(ClewTypeEnum.PHOTO);
typeEnumSet.add(ClewTypeEnum.RECOM_ATTIRE);
typeEnumSet.add(ClewTypeEnum.IMG_TEXT);

for (ClewTypeEnum typeEnum:typeEnumSet){
if(map.containsKey(typeEnum)){
DynamicHeatAnalysisVo dynamicHeatAnalysisVo= map.get(typeEnum);
voList.add(dynamicHeatAnalysisVo) ;
}else{
DynamicHeatAnalysisVo dynamicHeatAnalysisVo= new DynamicHeatAnalysisVo(typeEnum,null);
voList.add(dynamicHeatAnalysisVo) ;
}
}

4.25学习总结:

1462行CustomerMapper.xml
关于在controller中传参把wrapper作为参数传递
https://blog.csdn.net/weixin_42260782/article/details/122232629?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165085451516781483756079%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165085451516781483756079&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-122232629.142v9control,157v4control&utm_term=mapper.xml%E4%B8%ADew%E6%98%AF%E5%95%A5%E6%84%8F%E6%80%9D&spm=1018.2226.3001.4187

List 转 Map<String, T>
List 转 Map<String, List>
List 转 Map<String, Count>
List<Map<String, Object>> 转 Map<String, List<Map<String, Object>>>

List 转 Map<String, List>
使用groupingBy根据gender属性分组
https://blog.csdn.net/winterking3/article/details/116457573?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165088493416782350981412%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165088493416782350981412&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-4-116457573.nonecase&utm_term=stream%E6%B5%81%E5%A4%84%E7%90%86list%E8%BD%ACmap&spm=1018.2226.3001.4450

4.27学习总结:

Jenkins+Docker的操作
https://blog.csdn.net/hlkt521/article/details/107188180?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165103454016781432988457%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165103454016781432988457&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-107188180.142v9control,157v4control&utm_term=jenkins&spm=1018.2226.3001.4187

5.9 学习总结:

在进行模糊查询时(查询货号或者商品名称)
不加括号: AND st.styleName LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ) OR ( st.styleId LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ))
加了括号:AND (st.styleName LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ) OR ( st.styleId LIKE CONCAT( ‘%’, ‘ww’, ‘%’ )))

区别: 加入括号只能查货号 和 产品名称
不加括号:只能查询商品名称 或者货号 而且不加括号数据比加了括号的数据要多

不加括号:or前面条件失效,后面条件生效

https://blog.csdn.net/liyue071714118/article/details/108656396?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E4%B8%ADand%E5%92%8Cor%E7%9A%84%E5%8C%BA%E5%88%AB&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-2-108656396.nonecase&spm=1018.2226.3001.4187

判断一个数据是否为0 if(!xxx){}

面试题

1.多线程

1.1 为什么会发生线程安全问题?你在项目中是怎么解决的?

CPU在多个线程间切换,可能导致某些重要的指令不能完整执行,出现数据的问题。

出现线程安全问题的三个条件:

  • 多个线程
  • 同一个时间
  • 执行同一段指令或修改同一个变量
/**
 * 银行转账的案例
 */
public class BankDemo {

    //模拟100个银行账户
    private int[] accounts = new int[100];

    {
        //初始化账户
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = 10000;
        }
    }

    /**
     * 模拟转账
     */
    public void transfer(int from,int to,int money){
        if(accounts[from] < money){
            throw new RuntimeException("余额不足");
        }
        accounts[from] -= money;
        System.out.printf("从%d转出%d%n",from,money);
        accounts[to] += money;
        System.out.printf("向%d转入%d%n",to,money);
        System.out.println("银行总账是:" + getTotal());
    }

    /**
     * 计算总余额
     * @return
     */
    public int getTotal(){
        int sum = 0;
        for (int i = 0; i < accounts.length; i++) {
            sum += accounts[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        BankDemo bank = new BankDemo();
        Random random = new Random();
        //模拟多次转账过程
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                int from = random.nextInt(100);
                int to = random.nextInt(100);
                int money = random.nextInt(2000);
                bank.transfer(from,to,money);
            }).start();
        }
    }
}

线程安全问题的解决方法

解决方法:给程序上锁,让当前线程完整执行一段指令,执行完释放锁,其它线程再执行

几种不同上锁方法:

  • 同步方法
  • 同步代码块
  • 同步锁
同步方法

给方法添加synchronized关键字

作用是给整个方法上锁

过程:

当前线程调用方法后,方法上锁,其它线程无法执行,调用结束后,释放锁。

    /**
     * 模拟转账
     */
    public synchronized void transfer(int from,int to,int money){
        if(accounts[from] < money){
            throw new RuntimeException("余额不足");
        }
        accounts[from] -= money;
        System.out.printf("从%d转出%d%n",from,money);
        accounts[to] += money;
        System.out.printf("向%d转入%d%n",to,money);
        System.out.println("银行总账是:" + getTotal());
    }

锁对象:

  • 非静态方法 --> this
  • 静态方法 —> 当前类.class
同步代码块

粒度比同步方法小,粒度越小越灵活,性能更高

给一段代码上锁

synchronized(锁对象){
	代码
}

锁对象,可以对当前线程进行控制,如:wait等待、notify通知;

任何对象都可以作为锁,对象不能是局部变量

        //同步代码块
        synchronized (lock) {
            accounts[from] -= money;
            System.out.printf("从%d转出%d%n", from, money);
            accounts[to] += money;
            System.out.printf("向%d转入%d%n",to,money);
            System.out.println("银行总账是:" + getTotal());
        }

synchronized的基本的原理:

一旦代码被synchronized包含,JVM会启动监视器(monitor)对这段指令进行监控

线程执行该段代码时,monitor会判断锁对象是否有其它线程持有,如果其它线程持有,当前线程就无法执行,等待锁释放

如果锁没有其它线程持有,当前线程就持有锁,执行代码

底层汇编实现:

monitorenter
....
monitorexit
同步锁

在java.concurrent并发包中的

Lock接口

基本方法:

  • lock() 上锁
  • unlock() 释放锁

常见实现类

  • ReentrantLock 重入锁
  • WriteLock 写锁
  • ReadLock 读锁
  • ReadWriteLock 读写锁

使用方法:

  1. 定义同步锁对象(成员变量)
  2. 上锁
  3. 释放锁
//成员变量
Lock lock = new ReentrantLock();

//方法内部上锁
lock.lock();
try{
	代码...
}finally{
	//释放锁
	lock.unlock();
}

三种锁对比:

  • 粒度

    同步代码块/同步锁 < 同步方法

  • 编程简便

    同步方法 > 同步代码块 > 同步锁

  • 性能

    同步锁 > 同步代码块 > 同步方法

  • 功能性/灵活性

    同步锁(有更多方法,可以加条件) > 同步代码块 (可以加条件) > 同步方法

1.2 并发与并行,线程与进程、

    - 串行    
      多个指令依次执行
   - 并发
     每个线程单独执行一段指令,一个cpu在线程间切换(并不是同时执行)
   - 并行
     多个CPU内核同时执行多个线程,线程是同时执行的
       -  进程是程序执行相关资源(CPU、内存、磁盘等)分配的最小单元
   
  进程之间是相互独立的,有自己的内存空间
  
 - 线程是CPU资源分配的最小单元
  
  进程包含一个或多个线程
  
  线程需要的资源更少,可以看做是一种轻量级的进程
  
  线程会共享进程中的内存,线程也有独立的空间(栈、程序计数器)
  
  线程相互通信更加方便

1.3 如何创建多线程

   1. 继承Thread类
   2. 实现Runnable接口
   3. 实现Callable接口
   4. 使用线程池

1.4 线程池你了解多少?

    作用:回收利用线程资源

    线程是一种宝贵的系统资源,执行完任务后会死亡,如果有大量任务需要处理,需要                   频繁的创建和销毁线程,造成系统性能降低。

    线程池会保存一定量的线程,线程执行完任务后,会回到线程池中,等待下一个任                       务,节省系统资源,提升性能
    
    #### 线程池的使用

顶层接口:Executor

  • execute(Runnable) 启动线程执行一个任务
ExecutorService
继承 Executor

添加线程池管理方法,如:shutdown()、shutdownNow()

Executors

用于创建线程池的工具类

主要的方法

方法名说明
newCachedThreadPool()创建长度不限的线程池
newFixedThreadPool(int )创建固定长度的线程池
newSingleThreadExecutor()创建单一个数的线程池
newScheduledThreadPool(int)创建可以调度的线程池

1.5 你看过关于多线程的源码?

      建议自己百度白嫖+自己理解!!!

1.6 线程的生命周期

线程几种状态:

- 新建 NEW
- 准备/就绪 START
- 运行 RUNNING
- 阻塞 BLOCKING
- 死亡 DEAD

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgjRdMTG-1656029061357)(C:\Users\Dinghao\AppData\Roaming\Typora\typora-user-images\1655864557955.png)]

线程的常用方法

常用方法

方法介绍
start()启动
stop()停止(禁用,可能导致线程死锁等问题),停止线程可以让run执行结束
String getName()获得线程的名字
setName(String)设置线程名字
sleep(long)进入睡眠,毫秒
setPriority(int)设置线程的优先级(1~10从低到高)越高抢CPU几率更高
setDaemon(boolean)设置为后台线程 true ,后台线程是为其它线程服务的,如果没有其它线程存在,就自动死亡;使用案例:GC就是一种后台线程
join()线程的加入(合并)让其它线程先执行完,再执行自己的指令

2.锁相关的问题

2.1 synchronized  与  volatile 的区别?
    volatile是一个类型修饰符(type specifier);
    volatile,它能够使变量在值发生改变时能尽快地让其他线程知道;
    关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块;
    多线程访问volatile不会发生阻塞,而synchronized会出现阻塞;
    volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步;
    关键字volatile解决的下变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题。


​ 2.2 什么是乐观锁,悲观锁?
​ https://blog.csdn.net/firstcode666/article/details/122789487?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594641016780357273021%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165594641016780357273021&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-122789487-null-null.142v20control,157v15new_3&utm_term=%E4%BB%80%E4%B9%88%E6%98%AF%E4%B9%90%E8%A7%82%E9%94%81%EF%BC%8C%E6%82%B2%E8%A7%82%E9%94%81&spm=1018.2226.3001.4187


​ 2.3 锁有哪几种(锁的分类)?
​ https://blog.csdn.net/m0_60092917/article/details/118880821?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594644416781667853241%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594644416781667853241&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-118880821-null-null.142v20control,157v15new_3&utm_term=+%E9%94%81%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%EF%BC%88%E9%94%81%E7%9A%84%E5%88%86%E7%B1%BB%EF%BC%89&spm=1018.2226.3001.4187

3.java集合

ypora-user-images\1655945696558.png)]](https://img-blog.csdnimg.cn/ccb0b087060a4faab383505a18d33d3a.png)

3.1你在项目中一般用的啥集合?

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EqFlGGo-1656029061361)(C:\Users\Dinghao\AppData\Roaming!在这里插入图片描述

在这里插入图片描述

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 判断容量 扩容
        elementData[size++] = e; //存数据到数组中
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
    	//保证新集合的容量不能低于10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
		//扩容
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //判断当前容量是否超过数组的长度
        if (minCapacity - elementData.length > 0)
            grow(minCapacity); //扩容
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //新容量是原来容量的1.5倍
        if (newCapacity - minCapacity < 0)  //保证新容量不会低于传入的参数
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //保证新容量不能超过上限 Integer.MAX_VALUE - 8
            newCapacity = hugeCapacity(minCapacity);
        //使用新容量创建新数组,将原数组的数据复制过去,将新数组赋值给原数组的引用
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3.2那种场景应该用那种集合你知道吗?

大伙建议自己百度

3.3 说说你对list set map 的理解?

自己的理解(自己的口述)

4.反射

4.1 什么是反射(是什么)?

Java中的一种高级技术,能够在程序运行时能够解析出任意类和对象的属性和方法,可以动态调用类和对象的所有属性和方法。

可以用于开发非常灵活和通用的代码

应用场景:开发各种框架,如:Spring、MyBatis等

4.2 反射的优缺点(为什么)?

优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利

缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

4.3 反射的应用场景(怎么用)?

像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

5.redis

5.1 redis 在项目中做啥用的?

​ 主要做数据缓存

5.2怎么配置redis集群?

5.3redis的优缺点?为什么不用MongoDB?

https://blog.csdn.net/qq_42992919/article/details/96129147?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594659716781667889047%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594659716781667889047&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-96129147-null-null.142v20control,157v15new_3&utm_term=redis%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9%EF%BC%9F%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E7%94%A8MongoDB%EF%BC%9F&spm=1018.2226.3001.4187

5.4 redis 的5 种存储数据结构?

Redis的数据以key-value方式存储。

数据类型有:

  • string 字符串(简单的key-value数据,适合保存单个数据,如:用户数量、库存数、商品数)

  • hash 哈希(适合保存复杂类型数据,如:用户对象)

  • list 列表(链表结构,适合保存有序的、可重复的数据,如:商品列表、评论列表)

  • set 无序集合(适合保存无序的,不可重复的数据)

  • zset 有序集合(适合保存有序的,不可重复的数据)

    5.5 redis 雪崩,击穿,穿透?

1)缓存击穿

高并发的情况下,短时间内缓存会被穿过,请求直接打到数据库上,可能导致数据库压力过大。

解决方案:对代码上锁(双重检查锁)

2)缓存穿透

高并发的情况下,如果查询不存在的数据,因为缓存和数据库都不存在,请求都会打到数据库上,可能导致系统崩溃。

解决方案:

​ 1) 保存不存在的数据到缓存中,设置一定过期时间

​ 2) 布隆过滤器(直接过滤掉不存在数据的请求) 不能准确判断是否存在数据,能准确判断数据不存在

3)缓存雪崩

高并发的情况下,缓存服务器重启或热点数据同时过期,全部访问数据库,导致数据库宕机

解决方案:

​ 1)配置缓存集群

​ 2)尽量给热点数据设置不一样的过期时间,相对均匀

解决代码

 public Goods getGoodsById(Long id){
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        Object value = ops.get(TYPE + id);
        //外层先读缓存,缓存如果有,就不执行同步块
        if(value == null) {
            synchronized (this) {
                //1) 以id为键查询Redis缓存,如果能查到就返回数据,结束
                value = ops.get(TYPE + id);
                //2)如果查不到,就查询数据库
                if (value == null) {
                    System.out.println("缓存不存在,查询数据库");
                    // 数据库查到,缓存到Redis,返回
                    Goods goods = this.getById(id);
                    if (goods != null) {
                        System.out.println("数据库存在,保存到缓存");
                        ops.set(TYPE + id, goods);
                    } else {
                        System.out.println("数据库不存在,返回null");
                        //保存空数据到缓存中,设置过期时间
                        ops.set(TYPE + id,new Goods(),30, TimeUnit.SECONDS);
                    }
                    return goods;
                } else {
                    System.out.println("缓存存在,返回" + value);
                    //如果能查到就返回数据,结束
                    return (Goods) value;
                }
            }
        }
        System.out.println("缓存存在,返回" + value);
        return (Goods) value;
    }

5.6 redis 缓存数据过期了,你怎么处理?(类似于即将登录过期,然而用户还在网页或者某个功能点上)

做一个热点模块比如泡泡教育平台你那个广告模块(你进入网站就广告图片开始时,就把登录时的token携带进入微服务,可以解决这个问题啦,这个广告模块是实时运行的,每次刷新或者运行功能,就会刷新token的过期时间,不断刷新就可以一直携带有效的token)

5.7redis除了做缓存还能干嘛?

https://blog.csdn.net/wujialv/article/details/109325092?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594721916782246457908%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594721916782246457908&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-109325092-null-null.142v20control,157v15new_3&utm_term=redis%E9%99%A4%E4%BA%86%E5%81%9A%E7%BC%93%E5%AD%98%E8%BF%98%E8%83%BD%E5%B9%B2%E5%98%9B%E3%80%81&spm=1018.2226.3001.4187

5.8 了解过redis源码嘛?

https://blog.csdn.net/qqqwwweeerasd/article/details/124464919?ops_request_misc=&request_id=&biz_id=102&utm_term=%E4%BA%86%E8%A7%A3%E8%BF%87redis%E6%BA%90%E7%A0%81%E5%98%9B&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-124464919.142v20control,157v15new_3&spm=1018.2226.3001.4187

6 RabbitMQ

 6.1什么是mq?(干嘛用的)
   消息队列(Message Queue)简称MQ,简单来说就是一种消息的容器

   作用是采用FIFO(先入先出)的方式实现程序之间(服务、进程、线程)的消息通信。

 6.2 mq 怎么使用(如何在项目中配置)
     自己百度

 6.3 mq它的作用是干啥(保持缓存与数据库数据一致)?
      我们还是用了 RabbitMQ 来实现了 ES 和 Mysql 数据的同步。其中实现的 步骤为我们生成了一个 topic 主题类型的交换机,然后分别绑定了两条队列在该交换机上,两条队列分别设置对应 的 key,一个队列用于增改,一个队列用于删除。当我们的后台对 Mysql 的数据进行了增改或删除时就会向对应 key 的队列中发送消息,消息内容就是操作的数据,然后我们的 Es 服务方会对我们的 MQ 的两条队列进行实时的 监听。当在对应的队列中收到消息,就会执行操作,对我们的 es 执行增改或查询的操作。这样我们就实现了 ES 和 Mysql 的数据同步。
      
 6.4 mq 有几种消息队列?(5种)1:1((能者多劳),1 : n,工作,路由,主题
    RabbitMQ提供了多种消息模型,官网上第6种是RPC不属于常规的消息队列。
    属于消息模型的是前5种:

    1. 简单的一对一模型
    2. 工作队列模型 ,一个生产者将消息分发给多个消费者
    3. 发布/订阅模型 ,生产者发布消息,多个消费者同时收取
    4. 路由模型 ,生产者通过关键字发送消息给特定消费者
    5. 主题模型 ,路由模式基础上,在关键字里加入了通配符
 6.5 mq 消息丢失怎么处理?
    1. 生产者到交换机
    2. 交换机到队列
    3. 队列到消费者
    解决方案:

   1. 针对 生产者到交换机

   confirm 机制  确定生产者将消息投递到交换机

   2. 针对 交换机到队列

   return 机制  交换机发送消息到队列失败会执行回调

   3. 针对 队列到消费者

   消息没发 --> 消息持久化

   消息发了,不知道是否被消费 ---> 手动确认 ack
## Confirm&Return机制

```
rabbitmq:
  publisher-confirm-type: correlated  # 启动confirm机制
  publisher-returns: true # 启动return机制
```

配置类添加

```
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //设置confirm机制的回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack) {
                    log.info("消息{}发送到Exchange成功!!!",correlationData);
                } else {
                    log.error("消息{}发送到Exchange失败,原因:{}",correlationData,cause);
                }
            }
        });
        //设置return机制的回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
        	//交换机发送消息到队列失败回调
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.error("消息发送到Queue失败!{}",message);
            }
        });
        return rabbitTemplate;
    }
```

生产者发送消息,设置消息的id

```
rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
                RabbitMQConfig.KEY_COURSE_SAVE, JSON.toJSONString(course),
                new CorrelationData(UUID.randomUUID().toString()))
```


​ 6.6 消息幂等性?
​ 重复消费问题:

队列发送消息给消费者,因为网络原因,消费者没有收到,队列重复发送,网络恢复后会发送多个相同的消息给消费者

    可能导致问题:

    重复添加数据等非幂等性业务问题

    非幂等性业务(每次操作获得不同结果,如:添加)

    幂等性业务(每次操作结果相同,如:更新、删除)

    消息有唯一id,保存redis,手动确认

    解决方案:

    1. 给每个消息添加id
    2. 消费者获得消息后,通过id查询redis,判断该id是否存在
    3. 如果不存在,就修改该id的value为1,执行业务,进行手动确认
    4. 如果存在,就不执行业务



​ Redis的setnx命令

setnx key value

    如果该键不存在,就设置键和值,返回1

    如果该键存在,直接返回

7 项目问题

 7.1拿到需求你会怎么做?
    首先将需求和自己的领导对一下,自己要干嘛,写那几个接口,那个数据库里面那几张表
    然后分析表之间的关系,画出逻辑题以及要查询的有效字段。
    然后分析sql,分析业务。
    写完sql完成50%,开始写service层代码(各种各样的校验)。
    写完接口,自己测试,生成测试集合
    推送接口给前端或者Fuller对接口

 7.2你会独立处理自己写的bug嘛?
    debug 你必须会,不会建议重新学完debug在学java里面的idea

 7.3当你要用没有用的知识点来做项目(你没学)你准备几天完成工作?
     首先要分析是用啥东西来搞,类似于评估项目周期。然后给你自己定个目标(结合自己的学习能力看)。
     比如郭龙2天学完python(学到java中的ssm)就是把基础过完两天。然后找几个类似的项目看看,分析为什么这么做。如果是你,你会这么做。

8.mysql

8.1 mysql索引有了解嘛?

​ 是一种特殊的数据,保存实际数据的位置,通过索引能快速定位到实际的数据,提高查询速度。

​ 如:书籍目录、楼层索引

优缺点

优点:大大提高查询速度

缺点:

​ 1) 占用存储空间

​ 2) 降低增删改数据

​ 3) 创建时比较耗时

索引的分类

按功能分类
  1. 普通索引

    建在某一个列上,提高按该列查询速度,没有特别约束,表中可以建多个

    语法:

    create index 索引名 on 表名(列名);
    
  2. 主键索引

    建主键时,同时创建,约束是不能重复、不能为空、表只能有一个

  3. 唯一索引

    建在某一个列上,提高按该列查询速度同时该列不能重复,表中可以建多个

    create unique index 索引名 on 表名(列名);
    
  4. 全文索引

    用于长文本的列(text)

    create fulltext index 索引名 on 表名(列名);
    
  5. 组合索引

    用于多个列的查询

    create index 索引名 on 表名(列名1,列名2,列名3);
    

    最左前缀原则:把最重要的列放到左边

    查询时如果没有最左边的列,索引就失效

    列1生效

    列1,列2 生效

    列1,列2,列3 生效

    列2,列3 失效

索引的使用

explain 查询语句;

分析查询语句,是否能正常使用索引

最重要的:type、key、ref

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bCNDlB63-1656029061370)(C:\Users\Dinghao\AppData\Roaming\Typora\typora-user-images\1655950368758.png)]

使用场景

1. 数据量特别大(百万级别)
2. 列没有太多空值
3. 列没有很多重复
4. 列经常用于查询和排序

失效情况

  1. like ‘%xxx’ 模糊查询%在前面
  2. != 情况
  3. 使用or,or两边每个列都需要有索引,否则失效
  4. 使用组合索引,没有使用最左边的列
  5. 条件中出现表达式或函数 , 如: age - 1 = 19、year(birth_day) = 1990
  6. is null 或 is not null、in

8.2 它的数据结构是啥,为什么是b树,b+树?

B-Tree和B+Tree

B-Tree属于平衡搜索多叉树,分为根节点、枝节点、叶子节点,每个节点由:键、数据、指针组成

指针指向下面的子节点,搜索时采用二分查找,效率比较高。

查询效率和树的高度(高度越高、效率越低)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nM01ZYrj-1656029061371)(C:\Users\Dinghao\AppData\Roaming\Typora\typora-user-images\1655950383953.png)]

B+Tree是B-Tree升级版

将数据全部放到叶子节点,非叶子节点只保存键和指针,磁盘块就能保存更多节点,降低了树的高度,大大提升了查找效率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6EgEAeP-1656029061372)(C:\Users\Dinghao\AppData\Roaming\Typora\typora-user-images\1655950397103.png)]

8.3 b树和b+树的区别?

百度

8.4 mysql 事务(acid)

https://blog.csdn.net/l1394049664/article/details/81814090?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165595057616781432931223%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165595057616781432931223&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-81814090-null-null.142v20control,157v15new_3&utm_term=mysql%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%9B%9B%E5%A4%A7%E7%89%B9%E6%80%A7&spm=1018.2226.3001.4187

8.5mysql有哪些可以优化的?

架构优化

​ 系统规模大,用户量、数据量大

- MySQL集群,多台MySQL服务器

- 分库分表

​ 垂直分库

​ 将相关的表,放入不同的数据库,如将电商数据库分为:订单数据库、库存数据库

​ 水平分库

​ 将一个数据库的数据,分开存储到不同的服务器上

​ 垂直分表

​ 将表的字段分开,放到不同的表中

​ 如:商品表分为商品和详情表

​ 某些数据在分页中查看,某些大的数据text\blob放详情表

​ 水平分表

​ 数据量特别大,会严重影响查询

​ 将一个表的数据分到多张表中

  • 主从复制

    多台MYSQL服务器,分为主(master)和从(slave)服务器

    一主多从,多主多从

-  读写分离

​ 建立在MySQL主从复制的基础上,分为主数据库和从数据库

​ 主数据库负责写(增删改)从数据库负责读(查询)

​ 主数据库写入数据后,会记录到bin-log文件中,从数据库会将bin-log中的数据同步过来

​ 使用数据库中间件:Sharding-jdbc、myCat等

设计优化
  • 范式

    ​ 优点: 规范数据库设计,消除冗余,方便数据的修改

    ​ 缺点: 降低查询效率

    反范式 在表中加入冗余字段

    ​ 提高查询效率

    表的设计按具体情况范式和反范式结合使用

  • 选用合适的存储引擎

    存储引擎是数据库中存储数据的方式,如存储的位置、存储约束、数据结构等的结合

    不同的存储引擎有不同的功能和性能

    常用存储引擎:

    • InnoDB
    • MyIsam
    • Memory (不能持久化)
    • Blackhole
    • Performance Schema

    不同点:

    InnoDBMyIsam
    事务支持不支持
    查询性能以前低于MyIsam,目前差不多
    支持表锁和行锁,并发性能高支持表锁
    外键支持不支持
    行数保存不保存,需要用count(*)保存查询数据
    索引非聚簇索引(索引和实际数据不在一起)聚簇索引(索引和实际数据在一起)

    一般情况下使用InnoDB

  • 字段优化

    1. 主键

      必须给表设置主键id

      尽量不用业务字段,使用没有意义字段如(int自增)

      int自增效率高于UUID

    2. 数据类型

      字符串 尽量使用varchar,不使用char (varchar存储空间更小,数据少查询效率高)

      尽量使用小的数据类型,如:性别 varchar(1) 男 女 --> int 1 0 --> small int --> tiny int (1字节 -128~127)

    3. 尽量加not null约束

使用优化

​ 1) 索引

​ 数据量大,使用索引

​ 介绍索引的应用场景、分类

​ 2) 缓存

​ Redis缓存、MyBatis缓存

​ 3) 使用连接池

​ Druid\Hikari\c3p0\dbcp

​ 4) 分页查询

查询优化

​ 1. 查询之前,使用explain查看执行计划

​ 2. 尽量避免select *

​ 3. 尽量避免where中使用<>和!=

​ 4. 尽量避免where中使用is null和is not null

​ 5. 尽量避免where中列避免使用函数和表达式 where 1 + age = 20 where max(age) = 20

​ 6. 尽量避免模糊查询时,通配符在前面,如 name like ‘%xx’

​ 7. 尽量使用exists代替in

  1. 尽量避免where中使用or,union 代替or

    select name from student where age = 20 or address =‘武汉’

    ==>

    select name from student where age = 20

    union

    select name from student where address =‘武汉’

8.6 出现脏数据?你怎么解决?

​ 看具体的业务场景

8.7 sql 语句要了解

看自己能力

8.8 行转列你会吗?

drop table if exists student;
create table student(
	stu_id int primary key auto_increment,
	stu_name varchar(20),
	stu_gender varchar(20),
	stu_age int
);
drop table if exists course;
create table course(
	course_id int primary key auto_increment,
	course_name varchar(20)
);
drop table if exists score;
create table score(
	score_id int primary key auto_increment,
	stu_id int,
	course_id int,
	score int,
	constraint fk_stu_id foreign key (stu_id) references student(stu_id),
	constraint fk_course_id foreign key (course_id) references course(course_id)
);
insert into student(stu_name,stu_gender,stu_age)
values('张三','男',15),('李四','男',15),('王五','男',15),('赵六','男',15);
insert into course(course_name)
values('语文'),('数学'),('英语');
insert into score(stu_id,course_id,score)
values(1,1,80),(1,2,82),(1,3,84),(2,1,60),
(2,2,70),(2,3,86),(3,1,83),(3,2,77),(3,3,89);

原始数据:

学号 姓名 课程 成绩
001 张三 语文 60
001 张三 数学 89
001 张三 英语 88
002 李四 语文 88
002 李四 数学 66
002 李四 英语 90

行转列的效果:

学号 姓名 语文 数学 英语
001 张三 60 89 88
002 李四 88 66 90

1) 完成多表连接

-- 1. 三表连接
select s.stu_id 学号,s.stu_name 姓名,c.course_name 课程,sc.score
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;

2) 将课程名称作为列

使用Case End语句

语法:

case 列名
	when 值1 then 结果1
	when 值2 then 结果2
	else 缺省结果
end

case
	when 条件1 then 结果1
	when 条件2 then 结果2
	else 缺省结果
end

-- 2. 使用case-END
select s.stu_id 学号,s.stu_name 姓名,
case c.course_name
	when '语文' then sc.score 
	else 0
end 语文,
case c.course_name
	when '数学' then sc.score 
	else 0
end 数学,
case c.course_name
	when '英语' then sc.score 
	else 0
end 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
  1. 求最大值
-- 4. 求最大值或总和
select s.stu_id 学号,s.stu_name 姓名,
max(
case c.course_name
	when '语文' then sc.score 
	else 0
end) 语文,
max(case c.course_name
	when '数学' then sc.score 
	else 0
end) 数学,
max(case c.course_name
	when '英语' then sc.score 
	else 0
end) 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id
group by s.stu_id;

完成多表连接

-- 1. 三表连接
select s.stu_id 学号,s.stu_name 姓名,c.course_name 课程,sc.score
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;

2) 将课程名称作为列

使用Case End语句

语法:

case 列名
	when 值1 then 结果1
	when 值2 then 结果2
	else 缺省结果
end

case
	when 条件1 then 结果1
	when 条件2 then 结果2
	else 缺省结果
end

-- 2. 使用case-END
select s.stu_id 学号,s.stu_name 姓名,
case c.course_name
	when '语文' then sc.score 
	else 0
end 语文,
case c.course_name
	when '数学' then sc.score 
	else 0
end 数学,
case c.course_name
	when '英语' then sc.score 
	else 0
end 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
  1. 求最大值
-- 4. 求最大值或总和
select s.stu_id 学号,s.stu_name 姓名,
max(
case c.course_name
	when '语文' then sc.score 
	else 0
end) 语文,
max(case c.course_name
	when '数学' then sc.score 
	else 0
end) 数学,
max(case c.course_name
	when '英语' then sc.score 
	else 0
end) 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id
group by s.stu_id;
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值