面试题整理2

面试题整理2,第1张

面试题整理2 redis是什么,怎么持久化的

Redis是一个高性能的内存数据库,以key-value方式存储数据,可以作为缓存使用。

高并发:MySQL的连接数存在瓶颈,连接过大可能导致MySQL宕机

解决方法:部署多个MySQL服务,主从复制部署缓存,承担一部分的并发

高性能:基于内存,内存IO效率远远高于磁盘

Redis属于内存式数据库,程序关闭后数据会清空,有时候需要将内存中的数据长期在文件保存

持久化策略

        AOF:默认每秒对数据进行持久化

        RDB:按条件触发持久化 *** 作(任意一个)

                900 1 900秒中修改1次

                300 10 300秒中修改10次

                60 10000 60秒中修改10000次

配置方法        AOF        appendonly yes / no yes开启AOF

                                      appendfsync everysec 每秒保存

允许少量数据丢失,性能比较高----RDB

只允许很少数据丢失----AOF

不允许数据丢失----RDB + AOF

为什么使用elstisearch

Elasticsearch具备以下特点:

        分布式,无需人工搭建集群

        Restful风格,一切API都遵循Rest原则,容易上手

        近实时搜索,数据更新在Elasticsearch中几乎是完全同步的。

正排索引

        通过key找到value,如通过id找到文章

        查询内容时,需要逐条遍历,速度比较慢

倒排索引

        通过value找到key

        对内容进行分词,生成倒排索引

ribbitMQ的工作模式

工作队列,生产者将消息分发给多个消费者,如果生产者生产了100条消息,消费者1消费50条,消费者2消费50条,队列发送消息采用的是轮询方式,也就是先发给消费者1,再发给消费者2,依次往复。

因为队列默认采用是自动确认机制,消息发过去后就自动确认,队列不清楚每个消息具体什么时间处理完,所以平均分配消息数量。

实现能者多劳:

    channel.basicQos(1);限制队列一次发一个消息给消费者,等消费者有了反馈,再发下一条

    channel.basicAck 消费完消息后手动反馈,处理快的消费者就能处理更多消息

    basicConsume 中的参数改为false

mysql的连接方式左连接和右连接是什么概念

关键字:left join on / left outer join on

语句:SELECt  * FROM a_table a left join b_table b ON a.a_id = b.b_id;

说明: left join 是left outer join的简写,它的全称是左外连接,是外连接中的一种。 左(外)连接,

左表(a_table)的记录将会全部表示出来,而右表(b_table)只会显示符合搜索条件的记录。右表记录

不足的地方均为NULL。

关键字:left join on / left outer join on

语句:SELECt  * FROM a_table a left join b_table b ON a.a_id = b.b_id;

说明: left join 是left outer join的简写,它的全称是左外连接,是外连接中的一种。 左(外)连接,

左表(a_table)的记录将会全部表示出来,而右表(b_table)只会显示符合搜索条件的记录。右表记录

不足的地方均为NULL。

代码连接mysql的工具是什么,mybatis的sql语句放在哪

Navicat

1. 在mapper文件中配置SQL

2. 注解式的SQL定义

3.通过@SelectProvider来声明sql提供类

单点登录怎么实现的

添加用户服务,实现用户登录(Security)

登录成功后,将用户信息转换为JWT格式,保存到cookie中

在网关中添加全局过滤器,对请求进行拦截,获得其中的cookie,进行解码,成功就放行

gateway服务怎么跳转到别的服务的,普通用户登录后怎么访问的页面

客户端发送请求给Gateway网关,网关将请求发送给处理器映射(HandlerMapping)

网关通过路由的匹配,将请求发送给Web处理器处理,请求就需要经过一系列过滤器

过滤器分为“pre"前置和”post"后置两种,前置过滤器实现鉴权作用,后置过滤实现性能统计或日志收集

通过过滤器就到达需要的服务

webSocket怎么实现传到前台的,还有什么其他方式吗

  HTTP的生命周期通过Request来界定,也就是Request一个Response,那么在Http1.0协议中,这次Http请求就结束了。在Http1.1中进行了改进,是有一个connection:Keep-alive字段,也就是说,在一个Http连接中,可以发送多个Request,接收多个Response。但是必须记住,在Http中一个Request只能对应有一个Response,而且这个Response是被动的,不能主动发起。

        WebSocket是基于Http协议的,或者说借用了Http协议来完成一部分握手,在握手阶段与Http是相同的。我们来看一个websocket握手协议的实现,基本是2个属性,upgrade,connection。

        WebSocket与http协议一样都是基于TCP的,所以他们都是可靠的协议,调用的WebSocket的send函数在实现中最终都是通过TCP的系统接口进行传输的。WebSocket和Http协议一样都属于应用层的协议,WebSocket在建立握手连接时,数据是通过http协议传输的,但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的。

        WebSocket使用了自定义的协议,未加密的连接不再是http://,而是ws://,默认端口为80,加密的连接也不是https://,而是wss://,默认端口为443。



    
        
        
    
    
        
        
        
    

const WebSocketServer = require('ws').Server
wss = new WebSocketServer({port: 8001})
wss.on('connection', function (ws) {
    console.log('client connected')
    ws.on('message', function (message) {
        console.log(message)
    })

})
seata用到了吗,怎么实现的

Seata的分布式事务解决方案是业务层面的解决方案,只依赖于单台数据库的事务能力。Seata框架中一个分布式事务包含3中角色:

Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

其中,TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。

首先TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

XID 在微服务调用链路的上下文中传播。

RM 开始执行这个分支事务,RM首先解析这条SQL语句,生成对应的UNDO_LOG记录。下面是一条UNDO_LOG中的记录

RM在同一个本地事务中执行业务SQL和UNDO_LOG数据的插入。在提交这个本地事务前,RM会向TC申请关于这条记录的全局锁。如果申请不到,则说明有其他事务也在对这条记录进行 *** 作,因此它会在一段时间内重试,重试失败则回滚本地事务,并向TC汇报本地事务执行失败

RM在事务提交前,申请到了相关记录的全局锁,因此直接提交本地事务,并向TC汇报本地事务执行成功。此时全局锁并没有释放,全局锁的释放取决于二阶段是提交命令还是回滚命令。

TC根据所有的分支事务执行结果,向RM下发提交或回滚命令。

RM如果收到TC的提交命令,首先立即释放相关记录的全局锁,然后把提交请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。异步队列中的提交请求真正执行时,只是删除相应 UNDO LOG 记录而已。

在at模式中需要在客户端的数据库中建如下的表,用来保存回滚的数据

seata的数据源代理通过对业务sql的解析,在业务sql执行前后生成前置镜像和后置镜像保存在undo_log中,业务sql的执行和undo_log的插入在一个本地事务中,这样就可以保证任何对业务数据的更新都有相应的回滚日志存在。

在本地事务提交之前还会构建lockKey(本次事务提交影响的数据,lockKey的构建规则之前的文章已经说过了哈),向TC对涉及到的数据加锁,当加锁成功后,才会执行本地事务的commit过程,如果加锁失败,RM会根据重试策略进行重试,如果重试也失败,那么回滚本地事务

当TM向TC发起全局提交请求时,此时分支事务实际上以及提交了,TC立即释放该全局事务的锁,然后异步调用RM清理回滚日志

spring security的使用流程,具体到代码。

安全框架,能够帮助实现权限控制,不同的用户能查看或 *** 作不同的资源。

主流的安全框架:

    Shiro SSM配置少,容易上手

    SpringSecurity SpringBoot整合配置较少,强大

SpringSecurity是一个强大且高效的安全框架,能够提供用户验证和访问控制服务,能够很好地整合到以Spring为基础的项目中。 SpringBoot对SpringSecurity进行了大量的自动配置,使开发者通过少量的代码和配置就能完成很强大的验证和授权功能。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中创建用户
        auth.inMemoryAuthentication()
                //账号
                .withUser("admin")
                //密码,需要加密
                .password(new BCryptPasswordEncoder().encode("123"))
                //添加角色
                .roles("ADMIN","USER")
                //创建另一个用户
                .and()
                .withUser("user")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("USER");
    }

    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //用户请求授权
        http.authorizeRequests()
                //指定登录相关的请求,permitAll是不需要验证
                .antMatchers("/login").permitAll()
                //指定/user/** 需要USER角色
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                //其它所有URL都需要验证
                .anyRequest().authenticated()
                .and()
                //配置登录URL为login,登录成功后跳转main
                .formLogin().loginPage("/login").defaultSuccessUrl("/main")
                .and()
                //配置注销url,注销后到登录页面
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login");
    }
}
springboot自动配置原理

使用SpringBoot项目后,不需要进行大量的配置就能实现开发,为什么?

手动配置 ---> 自动配置

自动配置是用Java实现的

    在spring-boot-autoconfigure包下保存大量的自动配置类

    类名都保存 meta-INFO/spring.factotiries文件下

    自动配置类生效需要一定条件,如@ConditionalOnClass,引入某些类时(导入了对应的框架依赖)

自动配置类是什么时候导入内存的

启动类上的注解:@SpringBootApplication

包含三个注解:

    @ComponentScan 包的扫描

    @SpringBootConfiguration 定义配置类

    @EnableAutoConfiguration 启动自动配置(将自动配置类加载到内存)

怎么加载类到内存中? Class.forName("完整类名")

@EnableAutoConfiguration --> AutoConfigurationimportSelector --> loadFactoryNames ---> loadSpringFactories --> classLoader.getResources("meta-INF/spring.factories")

spring MVC的执行流程,

1) 用户发送请求

2)前端控制器获得用户请求的URL,发送URL给处理器映射

3)处理器映射将Handler(包装方法信息)返回给前端控制器

4)前端控制器发送Handler给处理器适配器,适配器执行Handler方法

5)执行Handler方法后,返回ModelAndView(逻辑视图)给前端控制器

6)前端控制器将ModelAndView发送给视图解析器,解析出物理视图返回给前端控制器

7)前端控制器渲染视图,发送视图给用户

单点登录除了用JWT和RSA之外,还有什么方法实现?为什么要用JWT和RSA?

cookie 的作用域由 domain 属性和 path 属性共同决定。

domain 属性的有效值为当前域或其父域的域名/IP地址,在 Tomcat 中,domain 属性默认为当前域的域名/IP地址。path 属性的有效值是以“/”开头的路径,在 Tomcat 中,path 属性默认为当前 Web 应用的上下文路径。

如果将 cookie 的 domain 属性设置为当前域的父域,那么就认为它是父域 cookie。cookie 有一个特点,即父域中的 cookie 被子域所共享,换言之,子域会自动继承父域中的cookie。

利用 cookie 的这个特点,不难想到,将 Session ID(或 Token)保存到父域中不就行了。没错,我们只需要将 cookie 的 domain 属性设置为父域的域名(主域名),同时将 cookie 的 path 属性设置为根路径,这样所有的子域应用就都可以访问到这个 cookie 了。

不过这要求应用系统的域名需建立在一个共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它们都建立在 baidu.com 这个主域名之下,那么它们就可以通过这种方式来实现单点登录。

总结:此种实现方式比较简单,但不支持跨主域名。

实现方式二:认证中心

我们可以部署一个认证中心,认证中心就是一个专门负责处理登录请求的独立的 Web 服务。

用户统一在认证中心进行登录,登录成功后,认证中心记录用户的登录状态,并将 Token 写入 cookie。(注意这个 cookie 是认证中心的,应用系统是访问不到的。)

应用系统检查当前请求有没有 Token,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心。由于这个 *** 作会将认证中心的 cookie 自动带过去,因此,认证中心能够根据 cookie 知道用户是否已经登录过了。

如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录,如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标 URL ,并在跳转前生成一个 Token,拼接在目标 URL 的后面,回传给目标应用系统。

应用系统拿到 Token 之后,还需要向认证中心确认下 Token 的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,并将 Token 写入 cookie,然后给本次访问放行。(注意这个 cookie 是当前应用系统的,其他应用系统是访问不到的。)当用户再次访问当前应用系统时,就会自动带上这个 Token,应用系统验证 Token 发现用户已登录,于是就不会有认证中心什么事了。

这里顺便介绍两款认证中心的开源实现:

Apereo CAS 是一个企业级单点登录系统,其中 CAS 的意思是”Central Authentication Service“。它最初是耶鲁大学实验室的项目,后来转让给了 JASIG 组织,项目更名为 JASIG CAS,后来该组织并入了Apereo 基金会,项目也随之更名为 Apereo CAS。

XXL-SSO 是一个简易的单点登录系统,由大众点评工程师许雪里个人开发,代码比较简单,没有做安全控制,因而不推荐直接应用在项目中,这里列出来仅供参考。

总结:此种实现方式相对复杂,支持跨域,扩展性好,是单点登录的标准做法。

实现方式三:LocalStorage 跨域

前面,我们说实现单点登录的关键在于,如何让 Session ID(或 Token)在多个域中共享。

父域 cookie 确实是一种不错的解决方案,但是不支持跨域。那么有没有什么奇淫技巧能够让 cookie 跨域传递呢?

很遗憾,浏览器对 cookie 的跨域限制越来越严格。Chrome 浏览器还给 cookie 新增了一个 SameSite 属性,此举几乎禁止了一切跨域请求的 cookie 传递(超链接除外),并且只有当使用 HTTPs 协议时,才有可能被允许在 AJAX 跨域请求中接受服务器传来的 cookie。

不过,在前后端分离的情况下,完全可以不使用 cookie,我们可以选择将 Session ID (或 Token )保存到浏览器的 LocalStorage 中,让前端在每次向后端发送请求时,主动将 LocalStorage 的数据传递给服务端。这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将 Session ID (或 Token )放在响应体中传递给前端。

在这样的场景下,单点登录完全可以在前端实现。前端拿到 Session ID (或 Token )后,除了将它写入自己的 LocalStorage 中之外,还可以通过特殊手段将它写入多个其他域下的 LocalStorage 中。

使用JWT&RSA实现单点登录,通过RSA对公钥和私钥字符串进一步处理,JWT生成Token时,使用公钥进行加密,解析时,JWT通过私钥对Token进行解析(token可以设置过期时间,如果token过期,会自动解析失败)

springboot的事务怎么配置? 

在Service层的方法上配置

分为两种:

    编程式事务

    编写Java代码,实现启动事务、提交事务或回滚事务

    编程麻烦、代码侵入性高,不推荐

    声明式事务

    通过配置+注解实现

    代码简洁,侵入性低,推荐

声明式事务

1)在spring配置文件中添加事务管理的配置


    
    


2) 在需要事务的Service方法上添加注解:

@Transactional

soringcloud的组件了解多少? 

主要的组件

注册中心,服务的注册和发现机制,Eureka、Nacos、Zookeeper等

配置中心,集中管理服务的配置文件,Config、Nacos

API网关,实现路由和权限管理,Zuul、Gateway

服务通信,实现Restful的服务调用,Openfeign、Dubbo

负载均衡,平衡每个服务的负载量,Ribbon

熔断器,提高系统的可靠性,Hystrix、Sentinel

分布式事务,全局事务管理多个服务,Seata

mybatis中#和$的区别

#{xx}应用PreparedStatement的占位符?插入,能防止SQL注入

${xx}应用字符串的拼接,不能防止SQL注入

事务是不是只要出现异常就一定会回滚?

Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚。 
如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚。 

ES用的什么分词器 

Lucene的IK分词器早在2012年已经没有维护了,现在我们要使用的是在其基础上维护升级的版本,并且开发为Elasticsearch的集成插件了,与Elasticsearch一起维护升级,版本也保持一致

说说你的这个权限功能怎么实现的,用了哪些技术

通过SpringSecurity进行登录验证,查询该用户对应的权限,管理员可以给不同的用户赋予不同的角色,角色对应相应的权限。

Spring  SpringMVC  Mybatis  SpringBoot  SpringSecurity

string常用方法有哪些

1)获得字符串长度

int length()

2) 对字符串进行查找

int indexOf(子字符串) 在字符串中查找字符串出现的位置,没有返回-1

int lastIndexOf(子字符串) 在字符串中查找字符串最后出现的位置,没有返回-1

3)字符串的比较

boolean equals(Object obj) 比较字符串是否相等

boolean equalsIgnoreCase(Object obj) 忽略大小写,比较字符串是否相等

boolean startsWith(String str) 判断字符串是否以该字符串开头

boolean endsWith(String str) 判断字符串是否以该字符串结尾

int compareTo(String str) 将当前字符串和其它字符串比大小,0相等,正数当前字符串大,负数其它字符串大

4)字符串的截取

String substring(int start,int end) 对字符串进行截取

String substring(int start) 从某个位置截取到末尾

char charAt(int index) 获得某个位置的字符

5) 字符串的替换

String replace(String oldString,String newString) 将字符串中某些字符串替换为新的字符串

6) 字符串的分割

String[] split(String c) 按某个字符对字符串进行分割

注意:分割的字符串可以添加\ 避免特殊符号出现错误

7) 字符串的转换

String toUpperCase() 将字符串转为大写

String toLowerCase() 将字符串转为小写

String trim() 去掉字符串前后所有的空格

8)字符串的连接 String ss = String.join(" ", strs); System.out.println("连接后的字符串:" + ss);

消息队列的消息出现异常怎么处理的

由于网络、软硬件等问题,消息是存在丢失问题

在哪几个方面存在丢失:

生产者到交换机        交换机到队列        队列到消费者

解决方案:

针对 生产者到交换机

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

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

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

消息发了,不知道是否被消费 ---> 手动确认 ack

    针对 交换机到队列

    针对 队列到消费者

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

重复消费问题:

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

可能导致问题:

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

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

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

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

解决方案:

    给每个消息添加id

    消费者获得消息后,通过id查询redis,判断该id是否存在

    如果不存在,就修改该id的value为1,执行业务,进行手动确认

    如果存在,就不执行业务

Redis的setnx命令

setnx key value

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

如果该键存在,直接返回

什么是字段和属性?

字段又称为:“成员变量”,一般在类的内部做数据交互使用
字段命名规范:camel命名法(首单词字母小写)

私有化: 字段就好比我们的个人财产,仅供个人使用,所以一般是private修饰。
添加标准: 根据程序的功能需求,具体来添加需要的不同类型的字段。

硬编码: 类中的静态字段,外部无法修改
比如使用 const关键字

const: 静态常量,是编译时常量,const较高效
readonly: 动态常量,是运行时常量,readonly较灵活

1.const默认是静态的,只能由类型来访问,不能与static同时使用;readonly默认是非静态的,由实例对象来访问,可以显示使用static定义为静态成员;

2.const只能应用在值类型和string类型上,其他引用类型常量只能定义为null,否则以new为const引用类型常量赋值,;readonly只读字段,可以使任意类型,但是对于引用类型字段来说,readonly不能限制对该对象实例成员的读写控制;编译器会引发“只能用null对引用类型(字符串除外)的常量进行初始化“的错误提示;

3.const必须在字段声明时初始化;readonly可以再声明时,或者构造函数中进行初始化,不同的构造函数可以为readonly常量实现不同的初始值;

4.const可以定义字段和局部变量;而readonly则只能定义字段;static readonly的初始化,必须在定义时,或者静态无参构造函数中进行;

5.数组和结构体不能被声明为const常量,string类型可以被声明为常量,因为string类型的字符串恒定特性,使得string的值具有只读特性;

项目的开发阶段有几个,作用是什么?

    可行性分析(市场、技术、社会)

    项目立项(启动大会,确定项目周期、团队、管理者)

    需求分析(确定项目功能、界面等)

    概要设计(架构设计、技术选型、数据库设计)

    数据库设计使用E-R图

    详细设计(具体功能设计、接口设计、数据库设计)

    表的设计(表名、字段名、类型、约束、主外键关系)

    数据库字典

    三大范式,保证表的设计规范

    一般要求表的设计符合第三范式

    第一范式,保证原子性,每一个列不能再分

    第二范式,每个字段都和主键相关,(学号、姓名、年龄、性别、系名、系主任、系电话)

    第三范式,每个字段都和主键直接相关,消除传递依赖 学生(学号、姓名、年龄、性别、系id) 系(id、系名、系主任、系电话)

    项目开发(数据库表的创建、代码的编写)

    测试

    上线

     9. 运维

序列化和反序列化的应用场景?

当你想把的内存中的对象状态保存到一个文件中或者数据库中时候。当你想用套接字在网络上传送对象的时候。当你想通过RMI传输对象的时候。

1、当一个父类实现序列化,子类就会自动实现序列化,不需要显式实现Serializable接口。

2、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。

3、并非所有的对象都可以进行序列化,比如:

      安全方面的原因,比如一个对象拥有private,public等成员变量,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;

     资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。

4、声明为static和transient类型的成员变量不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

5、序列化运行时会使用一个称为 serialVersionUID 的版本号,并与每个可序列化的类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID。

      如果序列化的类未显式的声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

6、Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的。

7、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因。

你熟悉的页面传值的方式?

1.参数直接绑定 直接定义和表单元素name相同的参数

2.@RequestParam注解绑定参数 如果名称不一样,可以通过@RequestParam注解配置参数

3.对象绑定参数 通过对象也可以进行参数绑定

4.@PathVariable绑定参数 /login/{username}/{password} @PathVariable("username")String username,……

通过URL的路径也可以实现参数绑定 在URL中可以使用{name}来定位参数的位置,name是@PathVariable注解中配置的名称

5.通过Map集合进行参数绑定

6.通过List集合进行参数绑定

7.HttpServlet

什么是事件委托?

事件代理又称事件委托,是javascript中绑定事件的常用技巧。顾名思义,‘事件代理’就是把原本需要绑定的事件委托给父元素,让父元素负责事件监听。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能

事件委托的好处:
1.减少事件数量,提高性能
2.预测未来元素,新添加的元素仍然可以触发该事件
3.避免内存外泄,在低版本的IE中,防止删除元素而没有移除事件而造成的内存溢出

什么是存储过程?有什么优点?

Stored Procedure 封装一系列SQL指令,编译后保存在数据库中,可以传递参数并进行调用。

优点:

    执行效率高(存储过程已经编译过,直接调用)

    安全性高(网络中不需要传输SQL语句,避免被拦截和破解)

    减少网络流量(SQL保存在存储过程中,网络只要发送调用命令)

缺点:

    存储过程和开发程序分开,后期开发和维护以及调试比较麻烦

    不支持缓存

    不支持分布式

什么是feign?

SpringCloud提供的声明式的REST客户端,实现远程的服务的调用,只需要编写接口和SpringMVC的注解就能完成调用。

Feign = RestTemplate + Ribbon + Hystrix

实现步骤:

    在启动类上加@EnableFeignClients启动Feign的功能,就会启动对Feign接口的扫描

    扫描到@FeignClient注解接口,读取接口上的服务名、URL,包装后发给IOC容器

    由IOC容器通过动态代理机制创建RequestTemplate对象,发送Http请求,交给Http客户端处理

    Http客户端被封装到LoadBalanceClient负载均衡客户端中,交给Ribbon处理

如何理解单点登录中的单点

单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。单点登录在大型网站里使用得非常频繁,例如像阿里巴巴这样的网站,在网站的背后是成百上千的子系统,用户一次 *** 作或交易可能涉及到几十个子系统的协作,如果每个子系统都需要用户认证,不仅用户会疯掉,各子系统也会为这种重复认证授权的逻辑搞疯掉。实现单点登录说到底就是要解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性。

int和Integer的区别

Integer是int的包装类;int是基本数据类型;Integer变量必须实例化后才能使用;int变量不需要;Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;Integer的默认值是null;int的默认值是0。 项目中是数据库的压力大还是服务器的压力大

服务器压力大的解决方案

优化代码

1.减少数据库的访问次数。数据库连接是很重要且很代价昂贵的资源,尽量避免每调用一次方法就读取一次数据库的情况。别小看这一条,有的人写的代码在一个for循环中每次都读取数据库,这是不对的,正常应该是一次把所有数据取回来放到Java对象中再循环。

2.合理正确地使用缓存,通过全局变量来保存一些设定,或是页面级缓存、站点级别缓存来减少服务器压力。如果是大内存服务器,比如96G内存的服务器,就可以把一些重要数据放在内存中,利用NoSQL做成内存缓存。

3.采用性能好的数据结构和算法。HASH类的性能最好,查找的复杂度是O(1)。二叉树的查找复杂度是O(logn),排序是O(n*logn)。List和ArrayList自带的排序速度接近O(n*longn),数组类的线性查找O(n),如果你用冒泡排序O(n*n),那么你就不是优秀的程序员。StringBuilder性能好于String,不过提升得有限,不是本质的数量级别的提升。

4.及时关闭非托管资源。除了上面提到的数据库连接,文件IO等也要注意。

优化数据库

1.尽可能地使用SP,而不是让SQL语句裸奔。

2.表结构设计要合理,起码要遵守数据库的三个范式。当然,有些场景稍微打破三个范式是可以获得更高的访问性能的,比如在B表中对A表中某个常查询的、却不会变动的字段的冗余,就不需要每次联表去查。

3.合理地设计索引,正确地使用索引。比如对常查询的字段建立索引。

4.对于超级大表(千万级别)最好采用分区表的方式存放,Oracle、SQL Server和MySQL都支持的。

数据库压力

在代码层面可以通过创建索引和转移压力两种方式给数据库减压。

创建索引
索引是MySQL和Oracle等数据库本身提供的功能,合理创建索引可以提高数据的检索效率,降低数据库服务器IO和CPU的消耗。

但由于索引也会降低更新表的速度,经常增删改的表或字段不适合创建索引,所以在开发初期,我们就应该根据数据库模型表和字段的作用来决定是否为该表建立索引,为数据记录较多的表中,频繁作为查询条件的字段建立索引。

转移压力
在代码层把数据库压力转移到服务器上,要求我们在编写代码的时候,时刻留意代码中是否有过多与数据库进行交互的行为,是否可以通过缓存或计算,来减少与数据库交互的次数。

例如一个功能模块的代码写下来,发现多次连接数据库,可以调整为一次性取出所有需要的数据,减少对数据库的查询次数。又例如模块中的某一个值,既可以通过逻辑运算得出,也可以通过数据库读取,在为减轻数据库压力的场景下,我们会选择前者。

能否合理使用中间件是考量一个开发技术经理能力的标准之一,利用各种中间件的优势,可以有效提高产品性能,减少资源消耗。在数据读取压力较大的场景中,往往会引入Redis和MQ中间件。

Redis
Redis缓存数据库是将数据以键值对的形式缓存在内存中的高效数据库。在开发中,我们可以将一些频繁读取的数据临时存放到Redis,例如中签公告、人员名单、产品清单等,用户在访问这些数据的时候,如果发现缓存中有数据,则无需调用数据库,直接从Redis获取。同时,由于内存的读写速率是普通机械硬盘的几百倍,使用Redis作为数据缓存不仅减轻了数据库的压力,数据的存取速度还特别快,可以有效提高数据的调取速率。

MQ
MQ消息队列中间件常用于流量消峰和消息分发。利用MQ将同一时刻的大量请求分散成一段时间来处理,可以有效减轻数据库负担;另外把消息发布到MQ中供多个客户端监听,也能达到减少数据查询次数的效果。

spring启动类和Tomcat的区别(内置Tomcat和Tomcat的区别)

spring整合springmvc中web.xml配置如下,tomcat在启动过程中会加载web.xml中的内容,ContextLoaderListener实现了tomcat里面的ServletContextListener接口,所以在tomcat容器启动过程通过ContextLoaderListener来进行spring容器的初始化 *** 作,并将
classpath:spring/applicationContext-*.xml指定下的spring配置文件加载,该配置文件我只配置了,代表通过扫描org.com.yp包下的类,包含@Component @Controller@Service等注解等类,进行bean注册。
bean注册是通过AbstractXmlApplicationContext.loadBeanDefinitions该类的方法进行bean定义加载的。
spring中加载bean定义是在org.springframework.context.ConfigurableApplicationContext#refresh方法中的ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()方法加载bean的,该方法之后会调用org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory方法创建bean工厂,并加载的bean定义。
spring整合springmvc中web.xml配置如下,tomcat在启动过程中会加载web.xml中的内容,ContextLoaderListener实现了tomcat里面的ServletContextListener接口,所以在tomcat容器启动过程通过ContextLoaderListener来进行spring容器的初始化 *** 作,并将
classpath:spring/applicationContext-*.xml指定下的spring配置文件加载,该配置文件我只配置了,代表通过扫描org.com.yp包下的类,包含@Component @Controller@Service等注解等类,进行bean注册。
bean注册是通过AbstractXmlApplicationContext.loadBeanDefinitions该类的方法进行bean定义加载的。
spring中加载bean定义是在org.springframework.context.ConfigurableApplicationContext#refresh方法中的ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()方法加载bean的,该方法之后会调用org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory方法创建bean工厂,并加载的bean定义。

springboot启动的方式则是先在springboot的org.springframework.boot.SpringApplication#run(java.lang.String…)方法中就初始化了spring的上下文环境(里面包含bean工厂),之后通过org.springframework.boot.SpringApplication#refreshContext方法调用Spring容器中的ConfigurableApplicationContext#refresh方法初始化bean.
在spring与springmvc整合的环境中,bean定义的加载是在org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory方法,而springboot中是在
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法,该方法中通过ConfigurationClassPostProcessor类去加载bean定义,该类实现了BeanDefinitionRegistryPostProcessor接口,这个接口允许对bean定义进行加工处理。

访问修饰符Protected的作用

介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。

nginx的作用

Nginx是一个轻量级、高性能、稳定性高、并发性好的HTTP和反向代理服务器。也是由于其的特性,其应用非常广。

 正向代理:某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。
反向代理:是用来代理服务器的,代理我们要访问的目标服务器。
   代理服务器接受请求,然后将请求转发给内部网络的服务器(集群化),
   并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
Nginx在反向代理上,提供灵活的功能,可以根据不同的正则采用不同的转发策略,如图设置好后不同的请求就可以走不同的服务器。

 负载均衡:多在高并发情况下需要使用。其原理就是将数据流量分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。

   Nginx可使用的负载均衡策略有:轮询(默认)、权重、ip_hash、url_hash(第三方)、fair(第三方)

ginx提供的动静分离是指把动态请求和静态请求分离开,合适的服务器处理相应的请求,使整个服务器系统的性能、效率更高。

Nginx可以根据配置对不同的请求做不同转发,这是动态分离的基础。静态请求对应的静态资源可以直接放在Nginx上做缓冲,更好的做法是放在相应的缓冲服务器上。动态请求由相应的后端服务器处理。

主方法里面的那个参数有什么用

  字符串类型的数组

  在执行主方法的时候,可以传入一些参数,将来在主方法中,可以接收到这些参数

  Args是一个参数名称,任意修改

  String[]的中括号也可以写到args后面

static的作用

static 意思是静态,静态成员属于类,非静态的成员属于对象

    用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;

    用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;

    静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;

    静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便。

JWT组成

头部:一个json字符串,包含当前令牌名称,以及加密算法,
载荷:一个json字符创,包含一些自定义的信息,
签名
由头部信息使用base64加密之后,拼接上载荷使用base64加密之后的部分,在加上当前的密钥,进行头部中的加密算法进行加密
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
将这三部分用.连接成一个完整的字符串,构成了最终的jwt

hashmap 与hashtable区别

    Hashtable是线程安全的,HashMap是非线程安全的

    HashMap的性能高于Hashtable

    Hashtable不能接收null作为键和值,HashMap可以

    HashMap可以存null的键值对,hashtable不能,

    HashMap没有多线程机制,所以多线程不安全,hashtable和con有线程锁,是线程安全的,hashtable多线程下,会锁住整个code表,效率很低,为了提高效率,将code表分成了十二个桶,每次锁时只会锁一个桶,提高了效率

linkedlist 与arraylist区别

    数据结构:ArrayList是数组,linkedList是双向链表

    ArrayList查找性能高(因为通过下标快速定位),插入和删除性能低(移动大量数据)

    linkedList查找性能低(因为要向前或向后依次查找),插入和删除姓高(只需要修改前后指针,不用移动)

数组、链表

为什么能在常数时间内访问数组元素? 

为了访问一个数组元素,该元素的内存地址需要计算其距离数组基地址的偏移量。需要用一个乘法计算偏移量,再加上基地址,就可以获得某个元素的内存地址。首先计算元素数据类型的存储大小,然后将它乘以元素在数组中的索引,最后加上基地址,就可以计算出该索引位置元素的地址了;整个过程可以看到需要一次乘法和一次加法就完成了,而这两个运算的执行时间都是常数时间,所以可以认为数组访问 *** 作能在常数时间内完成;

数组的优点

简单且易用;访问元素快(常数时间);

数组的缺点

大小固定:数组的大小是静态的(在使用前必须制定数组的大小);分配一个连续空间块:数组初始分配空间时,有时候无法分配能存储整个数组的内存空间(当数组规模太大时);基于位置的插入 *** 作实现复杂:如果要在数组中的给定位置插入元素,那么可能就会需要移动存储在数组中的其他元素,这样才能腾出指定的位置来放插入的新元素;而如果在数组的开始位置插入元素,那么这样的移动 *** 作开销就会很大

链表是一种用于存储数据集合的数据结构,它是最简单的动态数据结构,我们在上面虽然实现了动态数组,但这仅仅是对于用户而言,其实底层还是维护的一个静态的数组,它之所以是动态的是因为我们在add和remove的时候进行了相应判断动态扩容或缩容而已,而链表则是真正意义上动态的数据结构;

链表的优点

真正的动态,不需要处理固定容量的问题;

能够在常数时间内扩展容量;

对比我们的数组,当创建数组时,我们必须分配能存储一定数量元素的内存,如果向数组中添加更多的元素,那么必须创建一个新的数组,然后把原数组中的元素复制到新数组中去,这将花费大量的时间;当然也可以通过给数组预先设定一个足够大的空间来防止上述时间的发生,但是这个方法可能会因为分配超过用户需要的空间而造成很大的内存浪费;而对于链表,初始时仅需要分配一个元素的存储空间,并且添加新的元素也很容易,不需要做任何内存复制和重新分配的 *** 作;

链表的缺点

丧失了随机访问的能力;

链表中的额外指针引用需要浪费内存;

链表有许多不足。链表的主要缺点在于访问单个元素的时间开销问题;数组是随时存取的,即存取数组中任一元素的时间开销为O(1),而链表在最差情况下访问一个元素的开销为O(n);数组在存取时间方面的另一个优点是内存的空间局部性,由于数组定义为连续的内存块,所以任何数组元素与其邻居是物理相邻的,这极大得益于现代CPU的缓存模式;

数组最好用于索引有语意的情况,最大的优点:支持快速查询;链表不适用于索引有语意的情况,最大的优点:动态; springcloud 与springboot区别

1. spring boot使用了默认大于配置的理念,集成了快速开发的spring多个插件,同时自动过滤不需要配置的多余的插件,简化了项目的开发配置流程,一定程度上取消xml配置,是一套快速配置开发的脚手架,能快速开发单个微服务;

2. spring cloud大部分的功能插件都是基于springBoot去实现的,springCloud关注于全局的微服务整合和管理,将多个springBoot单体微服务进行整合以及管理;  springCloud依赖于springBoot开发,而springBoot可以独立开发;

springcloud的组件及作用

主要的组件

注册中心,服务的注册和发现机制,Eureka、Nacos、Zookeeper等

配置中心,集中管理服务的配置文件,Config、Nacos

API网关,实现路由和权限管理,Zuul、Gateway

服务通信,实现Restful的服务调用,Openfeign、Dubbo

负载均衡,平衡每个服务的负载量,Ribbon

熔断器,提高系统的可靠性,Hystrix、Sentinel

分布式事务,全局事务管理多个服务,Seata

Eureka是如何发现服务的(原理)

1提供者将IP和端口注册到注册中心
2.注册中心和提供者每隔一段时间( 60s )发送心跳包
3.如果一段时间( 90s )没有收到心跳,注册中心就会将服务剔除
4.消费者需要调用提供者时,会到注册中心上查找,注册中提供服务清单
5.消费者通过服务清单中的IP和端口来调用提供者
6.服务清单每隔一段时间(30s)更新一次

负载均衡是什么,它的作用

(1) 服务器进行硬件升级:采用高性能服务器替换现有低性能服务器。 该方案的弊端:

高成本:高性能服务器价格昂贵,需要高额成本投入,而原有低性能服务器被闲置,造成资 源浪费。

可扩展性差:每一次业务量的提升,都将导致再一次硬件升级的高额成本投入,性能再卓越 的设备也无法满足当前业务量的发展趋势。

无法完全解决现在网络中面临的问题:如单点故障问题,服务器资源不够用问题等。

(2) 组建服务器集群,利用负载均衡技术在服务器集群间进行业务均衡。

该方案的优势: 低成本

可扩展性:当业务量增长时,系统可通过增加服务器来满足需求,且不影响已有业务,不降 低服务质量

高可靠性:单台服务器故障时,由负载均衡设备将后续业务转向其他服务器,不影响后续业 务提供,保证业务不中断。

负载均衡的作用:

1.解决并发压力,提高应用处理性能(增加吞吐量,加强网络处理能力);

2.提供故障转移,实现高可用;

3.通过添加或减少服务器数量,提供网站伸缩性(扩展性);

4.安全防护;(负载均衡设备上做一些过滤,黑白名单等处理)

断路器的作用,谈谈雪崩效应

hystrix断路器它默认状态是关闭的,当请求的次数达到阈值,他就会开启断路器,默认5秒之内拒绝所有请求,当5秒之后,就会转换到半开状态,并尝试性的去放一个请求去请求服务,如果成功的次数达到阈值,就关闭hystrix断路器。如果失败就会继续处于半开状态,以此循环下去;

微服务架构中多个服务之间的调用,【基础服务】故障【级联】导致故障,进而造成整个系统不可用的情况,被称为服务雪崩,一般是“服务提供者”的不可用导致“服务消费者”的不可用,后逐渐放大;

一、隔离:
具体问题场景:
由于线程池的数量是有限的,线程池的总量为200个,所有的请求都是去服务a,然后服务a的线程一直没有归还到线程池,导致线程池的线程没有了,那么其他的请求无法获取线程就会报错,导致基础服务故障然后级联故障,造成雪崩;
1、线程池隔离:
线程池总量如为200个,
则均衡分布每个服务自己建立属于自己的线程池即可,分配40个线程,这样,一个服务挂掉了,其他服务不会影响;
2、信号量隔离:
加一个阀门设置C服务支持的请求最多40个,多余请求拒绝

二、降级策略
服务提供者和服务消费者都进行降级策略,可运用hystrix来 *** 作哦

面试问:hystrix断路器是怎么工作的,怎么转换状态的?【hystrix断路器开始就会拒绝所有请求】断自己的后路

说一下关系型数据库MySQL与非关系型数据库Redis的区别,

关系型数据库存储结构是二维表结构,类似依据x,y可以定位一个数据;

基于行式存储,存储结构化数据,一行代表一条完整的信息。

关系型数据库遵循ACID特性(原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)),支持事务处理能力。

但容量扩展性有限。

数据库的ACID & 3种隔离级别 脏读、不可重复读、幻读

适用场景:

需要事务支持;

基于sql的结构化查询存储,处理复杂的关系。

缓存型数据库
(1)Redis
key -> value(String、list、set、hash(field-value)、zset(score-value))

内存中数据有时间限制,也提供RDB和AOF两种持久化方式。

(2)Memcached
区别是不会持久化,完全基于内存,基本被Redis替代了。

适用场景:

对数据高并发的读写(mysql数据库存储在磁盘上,高并发会产生大量IO)

对海量数据的读写(mysql数据库不支持海量数据)

对数据高可扩展性的(例如key-value,redis中支持5种类型的value)

内连接与左连接的区别

eft join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。
right join(右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。
inner join(等值连接或者叫内连接):只返回两个表中连接字段相等的行。
full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。

讲讲你最熟悉的项目,项目的重点,难点

项目描述 :

聪优课堂是迎合当今互联网教育市场,让相关专业学生系统的学习知识体系,系统采用分布式架构设计,主要模块有搜索,广告、登录、课程、订单、支付、评论等模块。

项目职责:

1.搜索功能:调用课程服务查询到数据库的信息导入到Elasticsearch中,通过用户选择的过滤条件和当前参数,通过布尔查询对多个条件进行过滤,处理结果查询。

2.解决数据同步问题:当对数据库进行增删改 *** 作,使用RabbitMQ消息队列同步到Elasticsearch中。

3.登录模块:用户认证通过后,采用JWT和RSA加密算法,生成token,做到每次请求都携带JWT token,实现单点登录。

4.订单模块:使用Sharding-Sphere进行分库分表,通过Seata解决分库分表带来分布式事务问题。

分布式锁怎么实现的

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

实现思想:

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

类与对象

一、类的概念:
类是具有相同属性和服务的一组对象的集合。它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性说明和服务说明两个主要部分。

二、对象的概念:
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行 *** 作的一组服务组成。从更抽象的角 度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行 *** 作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。

三、类与对象的关系:
类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类.类描述了一组有相同特性(属性)和相同行为(方法)的对象。

四、类与对象的区别:
1,类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。

2,对象是类的一个具体。它是一个实实在在存在的东西。

3,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。

4,对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。

Java中有那些集合   数据库左链接和右链接

(1)左连接:只要左边表中有记录,数据就能检索出来,而右边有

     的记录必要在左边表中有的记录才能被检索出来

(2)右连接:右连接是只要右边表中有记录,数据就能检索出来

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5716107.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存