前言
因为某些个人原因,或者其他不可控因素,IT行业跳槽肯定在所难免。
这也意味着,你会离开一个熟悉的环境,想方设法的去融入另一个陌生的环境,接触新的同事。希望你的同事人都超级棒~
这篇博客可能只适用于一些2~3年,每天勤勤恳恳写代码,按时完成日常任务的中级程序猿。
毕竟,技术宅男都是大佬,随便换工作,公司都是抢着要。
准备工作
求职简历:简历里面,我觉得有一点很重要,就是如果你对某个框架底层原理并不熟悉,只是项目中使用到了,那么,建议在简历里面这样写:了解并使用xxx框架/技术。
求职App:目前,App主要推荐:猎聘,Boss直聘。 诈骗公司,培训机构相对而言较少,HR回复也比较快(个人感触),app上面的信息都一一完善,包括个人信息,工作经验,项目经验等,尽可能的多写,不要嫌麻烦~~~ 朋友内推也是一种途径
小小建议
1、所谓不打无准备的仗,复习几天,再开始投简历!!不然,你一点没准备,突然通知你面试,又是你很想去的公司,那可能就错过了~
2、准备得差不多了,就可以试着投简历。投简历时仔细看公司招聘要求,有选择性,目的性的投(至少他写的技术你要会一大半吧)。
3、多面试,积累经验。一次两次没面上无所谓,总结失败原因(下来看技术面试时没答上来,或者自己一知半解的知识点,要有针对性的去学习,实践),为下一次面试做准备。
如何复习
工作这几年,每次面试之后,把面试中问到的技术包括一些题做了汇总。分享给大家,大体知识点如下:
一、Java基础
1.显示转换和隐式转换
2.== 和 equals()区别
3.集合源码
4.位运算(源码里面经常用到)
5.多线程
众多设计模式中常用的几种
7.反射机制
8.封装,继承,多态,抽象
的八大基本数据类型你得记得吧?
的自动拆箱和装箱
的作用和源码
@SpringBootTest
class JavaStudyTest {
@Autowired
// @Qualifier("student")
// @Resource
private StudentService studentService;
public static void main(String[] args) {
("ss");
Person person = new Student();
();
Integer i = 12;
Integer ii = (129);
(4);
}
/**
* 1.显示转换和隐式转换
* <p>
* byte -> char -> short -> int -> long ->float -> double -> boolean
* 隐式转换,计算时会编译器自动转换,从小到大
* 可查看target文件夹下面对应的字节码文件,编译之后的代码
*/
@Test
public void test1() {
try {
short s = 1;
//s += 1 == s=(short)(s+1)
s += 1.1D;
("s........." + s);
("这里的s从double强转成short,会确实精度,结果不是2.1,而是2");
// s = s + 1;
String str = "abcd";
boolean equals = ("ddd");
} catch (Exception e) {
}
}
/**
* 2.== 和 equals()区别
* <p>
* getClass(),equals(),hashcode(),toString(),wait(),notify(),notifyAll,finalize(),
*
* Integer 能用==判断相等吗?-128 ~ 127 ,为什么?主要要有个缓存概念
*/
@Test
public void test2() {
String str1 = new String("abc");//这个过程是运行时在堆内存中实时创建一个对象。
String str2 = new String("abc");//这个过程是运行时在堆内存中实时创建一个对象。
((str2));//比较的是两个对象的值,是true
(str1 == str2);//比较的是两个对象地址,是false
}
/**
* 3.集合
* <p>
* Collection -> List -> ArrayList(初始化大小10,扩容为为之前的1.5倍),LinkedList,Vector(线程安全,扩容为之前的2倍) Set -> HashSet,
* Map -> HashMap (阈值16,2的倍数,负载因子为0.75,当map的size超过0.75就扩容为之前的2倍),TreeMap,HashTable(线程安全,key不能为null) ->CurrentHashMap (高并发,保证线程安全)
*
* 重点:HashMap,CurrentHashMap(线程安全)的源码,包括默认的一些参数,put(),get()时大致流程,什么时候扩容,扩容过程,尽可能记清楚。
*/
@Test
public void test3() {
List list = new ArrayList();
List list1 = new LinkedList();
("");
Map map = new HashMap();
("number", "");
Set set = new HashSet();
("e");
if (list != null & "e".equals("d")) {
("");
}
int[] intArray = new int[10];
intArray[0] = 1;
String[] strArray = {};
strArray[0] = "ll";
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
("test", "xjc");
("test");
(("test"));
/**
*
* ------
* 一. HashMap put过程
*
*
*
* 二. ConcurrentHashMap: put()过程 cas + synchronized
* /p/5dbaa6707017 (很详细)
*
* 1.计算key的hash值,
*
* 插入过程使用无条件自循环
* table Node<Key,Value>[] table;
* 3.如果table为空,还没初始化,先调用initTable()初始化, cas, sizeCtl = -1:说明有其他线程在初始化
*
* 如果新node的hash=-1, 帮助扩容??扩容很复杂
*
* 4.如果索引所在位置为null,就直接使用cas(())插入数组
*
* 5.否则,hash冲突,A.判断当前为链表, B.当前节点为红黑树,将新的键值插入到红黑树
* 6. 插入完 再判断是否需要转换成红黑树
* 7。检查是否需要扩容,如果超过了 实际大小*加载因子 就需要扩容 addCount
*
*
* HashMap、Hashtable、ConccurentHashMap三者的区别?
* HashMap线程不安全,数组+链表+红黑树
* Hashtable线程安全,锁住整个对象,数组+链表
* ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
* HashMap的key,value均可为null,其他两个不行
*/
}
/**
* 4.位运算
* <p>
* >> 右移 / 2
* << 左移 * 2
* >>> 无符号右移
* & 与 两个操作数中位都为1,结果才为1,否则结果为0,
* | 或 两个位只要有一个为1,那么结果就是1,否则就为0,
* ^ 异或 两个操作数的位中,相同则结果为0,不同则结果为1。 不使用中间变量,交换两个变量的值
* <p>
* /shuaiding/p/
* /weixin_37490221/article/details/90905087
*/
@Test
public void test4() {
int a = 1;
int b = 2;
/*
* 1.任何数和自己进行异或操作结果都为0
* 2.异或符合交换律,即a ^ b = b ^ a
* 3.任何数和0进行异或都是任何数本身
* a = a ^ b;
* b = b ^ a = b ^ (a ^ b) = a;
* a = a ^ b = (a ^ b) ^ (b ^ (a ^ b)) = (a ^ b) ^ a = b;
*/
a = a ^ b;
b = b ^ a;
a = a ^ b;
}
/**
* 5.多线程
* /wxd0108/p/
* /zsql/p/
*
* 实现方式:Runnable接口(run()),Thread类,Callable接口(call())
*
* 多线程: 联想到线程安全
*
* <p>
* synchronized 原子性、可见性和有序性 --> 阻塞锁,也叫悲观锁,锁住资源,其他使用该资源的线程全部进入阻塞队列,直到资源被释放
* volatile 可见性和有序性
* lock ---> 也叫非阻塞锁, 主要实现方式 cas算法, aqs, 也叫乐观锁 ---> cas -- 又有公平锁和非公平锁,源码在ReentrantLock相关类里面
*
* 搞清楚悲观锁和乐观锁的定义?对应实现方式?
*
* 多线程 ---> 线程池 --->
* 关于线程池的源码和使用,看ThreadPoolExecutor源码,一定要搞清楚这个类里面的几个参数是什么意思,
* 通过Executors创建线程池存在的弊端,如何使用ThreadPoolExecutor创建线程池?(可能会问,答案是通过Executors创建的线程池,对应参数都设置的max)
*/
@Test
public void test5() {
VolatileTest thread1 = new VolatileTest("thread1");
();
try {
(10000);
("Now stop thead1.");
();
} catch (InterruptedException e) {
();
}
/* ExecutorService executorService = (4);
Future future = (new Runnable() {
@Override
public void run() {
("ss");
}
});
Future<Object> callFuture = (new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
try {
Object o = (1000, );
} catch (InterruptedException | ExecutionException | TimeoutException e) {
();
}*/
}
/**
* 众多设计模式中常用的几种
*/
public void test6() {
/**
* 动态代理模式(AOP)
* 单例模式 (懒汉/饿汉)
* 工厂模式(beanFactory)
* 装饰器模式
* 观察者模式
* 生产者-消费者模式
*
*/
// 需要你能举例,最好是把每种模式的代码码一遍。其次,动态代理用到的类,大致方法(Proxy类,InvocationHandle接口 --> invoke())
}
/**
* 7.反射机制
* 程序运行过程中,根据类名,class 动态调用类的属性和方法,
*/
public void test7() throws Exception {
Class<?> cl = ("");
Object o = ();
Field name = ("name");
(cl);
}
/**
* 8.封装,继承,多态,抽象
*
* java的多态。什么叫多态?
*
* 注意:在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
*/
/**
* 9.
* java的八大基本数据类型你得记得吧?
* 每种基本数据类型对应得包装类型?
/
/**
* 10.
* java自动装箱:jvm编译时自动将基本类型转换成对应的对象(包装类型) 拆箱
* 要有缓存的概念,提高程序的效率和减少内存的占用
* /haitaofeiyang/p/
* /p/0ce2279c5691 (讲得很好)
*
* Integer i = 1; ----> jvm编译时会将1转换成Integer对象,valueOf() intValues()
*
* int i=1;Integer j = 2; i + j ----> 这种在进行计算操作时,jvm编译会将Integer对象转换为int类型,这种操作叫自动拆箱
*/
}
二、Spring 相关(有点杂乱)
mvc的工作流程?
生命周期 (单实例,多线程)
3.分布式是什么?分布式锁?Mysql,redis,zookeeper如何实现分布式锁?
4.接口改为异步,提高系统的吞吐量 使用DeferredResult和CompletableFuture
九大内置对象?
aop,ioc (重点,一定要去看源码,我下面贴的一篇视频讲得很明白)
调优 和 GC (重点,必问)
8.@Autowired + @Qualifier = @Resource 注入bean(type,name)
9.@Value(${: default}) value注解,:后面跟默认值, 表达式中#和$ 区别
/**
* 生命周期 (单实例,多线程)
*
* 用户第一次请求的时候,servlet容器会调用init()进行初始化,生成一个servlet实例。真正执行请求的方法service() -> doGet(),doPost(),doDelete()
* 最后,当服务器关闭(tomcat stop),会调用destroy(),该方法也只会调用一次
*
* 问:servlet实例是在什么时候初始化的,又是什么时候销毁的??
*/
/**
* 2.分布式
*
*/
public void test8() {
/**
* 分布式技术: 当一个系统的业务量原来越大,代码越来越多,就需要考虑将一个庞大的系统拆分为多个子系统(多个微服务),每个子系统也不能是单机,需要做集群。
* 通过这样的方式,来提高我们系统的吞吐量,保证系统的稳健性。
*
* 分布式用微服务架构实现,目前主流的有两种,一种基于Http协议(spring cloud ), 一种基于Rpc(阿里的dubbo)
*
*
* 每天100w 搜索访问量,qps 100w / (8 * 3600) = 35 QPS平时一般是50左右,高峰期 80 - 120, 平均响应时间3s。 并发量 = QPS * 平均响应时间 = 50 * 3 = 150
*/
/**
* 分布式锁:(mysql redis(setnx(key,value)) zookeeper)
* /xlgen157387/article/details/79036337
*/
// /qq_25026989/article/details/89103843
}
/**
* 3.接口改为异步,提高系统的吞吐量 使用DeferredResult 和 CompletableFuture thenApply,thenApplyAsync
* Spring基础学习-SpringMVC异步处理模式分析(DeferredResult/SseEmitter等)
*/
public void test12() {
// tomcat容器的连接线程池的数量是有限制的。每一个请求都会占用一个连接数。如果接口耗时很长,会长时间的占用连接,一直不被释放,那么一但连接线程池里都被占用了,就无法继续对外提供服务了。
// Java8 异步编程: /p/63734729
// /icarusliu/article/details/79539105
// 为什么使用DeferredResult? /theRhyme/p/
}
/**
* ---------------------------------
* 4.
* Jsp 九大内置对象?
* request response session(用户第一次请求的时候,服务器会记录一个session) application(应用不关闭,一直存在) page pageContent out config exception
* <p>
* <p>
* ---------------------------------
* 重定向:redirect 转发:forward ??
* 区别:是在浏览器端跳转,forward是在服务器端跳转,所以redirect的时候,浏览器的地址会变,而forward不会;2.重定向,请求域(request)里面的数据会丢失, 转发不会;
* <p>
* <p>
* ---------------------------------
* jsp 动作include与指令的区别?
* 编译时,page,会编译到同一个jsp文件中,可以使用变量
* 动作include则是编译两个jsp,不可以使用变量,会报错,编译不过
*/
public void test10() {
}
/**
* 5.
* spring aop
* <p>
* 看这个视频,足以:/video/av81268629?from=search&seid=4974281176459987245
* <p>
* 面向切面编程,常用的使用场景:1.日志监控(执行controller之前,之后,记录) 执行耗时
* <p>
* aop的实现,底层原理: 动态代理 spring aop: 如果去看源码 , JdkDynamicAopProxy(被代理对象实现了接口) 和 CglibAopProxy(对没有实现接口类的提供代理),两种
* <p>
* 再往下看源码, InvocationHandler 接口 -> invoke()方法,在这里面,可以代理对象,并做一些其他的事情 和 Proxy类 -> new ProxyClass 得到一个代理对象
* <p>
* 用到了java的反射
* Class,Method,Filed
* <p>
* P7面试题: 为什么我们java动态代理被代理的对象必须实现了接口?
* 答:因为java是单继承,多实现。而生成的代理对象默认实现了Proxy对象。
*/
@Test
public void test11() throws Exception {
// 动态代理
// AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
// // StudentService studentService1 = ();
// StudentService studentService1 = (StudentService) ("studentService");
// ();
// ("Test proxy...jdkDym..Proxy");
// 代理对象字节码
byte[] $proxies = ("$proxy", new Class[]{});
FileOutputStream fileOutputStream = new FileOutputStream("E:\\");
($proxies);
();
();
}
/**
* 6.
* spring ioc:
*
* 依赖注入,是spring的一种设计思想,项目启动时,将需要交给spring管理的bean装配到spring容器中,交给spring容器统一管理。使用的时候,直接从spring容器里面取,而不用反复创建
*
* BeanFactory 和ClassPathXmlApplicationContent
*/
/**
* 7.
* JVM调优 和 GC
*/
public void test14() {
// /p/5d899b0dcc8d
// https://blog./zero01/2150696 (博客很棒,可以跟着试试jvm调优)
// /question/56206572
/**
* java 里面的gc,垃圾回收器。jvm回收时,会调用finalize()方法
*
* 回收的对象,垃圾对象:长时间或以后都不可能被使用的对象,
*
* 有两种算法:1种是计数器,每个对象都有一个计数器,如果这个对象有引用,+1 如果计数器为0,则标记为垃圾对象 2:可达性算法,GC Roots
*
* 垃圾回收算法: 1. 标记-清除 (扫描内存里面的对象,对存活的对象进行标记,标记完毕之后,对未标记的对象进行回收,, 会造成内存碎片)
* 2.标记-整理 (跟标记-清除类似,标记完之后,将存活的地下往空闲空间移动,再清除 解决内存碎片问题)
* 3.复制算法
* 4.内存回收算法
* 新生代,老年代,永久代
* Full gc..
* jvm调优,没有调过。一般代码里面遇到内存溢出,out of memory,都是从代码层面去解决,所以要试着去调优
*/
}
三、关系型数据库相关
最常用的Mysql,还有Orcale,SQL Server等,这里主要是Mysql,中小型企业用得最多吧;
1.基本的CURD你得记住(哪怕你平时开发很少写sql)
by, group by , where , having的用法,in, not in, is null等等…
3.函数:sum() avg() count() min() max() limit distinct(去重)
4.触发器,储存过程(了解,尽量能写,有些公司要考)
5.最重要的Mysql如何调优
6.什么是脏数据? --> 事务,事务的4大特性,4大隔离级别,
7.索引有哪些?mysql对应的引擎?Innodb,Myisam等,引擎各自的优缺点 (多了解,有些公司会问)
…
/**
* Mysql 调优 - 脏数据,事务
*/
public void test1() {
// 优化Mysql: 优化sql,加索引
// limit 1 : 比如我们要查找是否有未成年用户,limit 1,如果发现有,会立即停止搜索效率高
// 经常where的字段添加索引
// 避免 SELECT *
// 尽量避免where时使用!=, ,否则将引擎放弃使用索引而进行全表扫描
// like %test 势必会进行全表扫描
// 分区,分表,分库
// mysql count(1)和count(*)区别,mysql5.5效率一样.之前很多都说count(1)效率更快
// 事务: 多个增删改查操作的集合。原子性,一致性,隔离性,持久性
// 4大隔离级别, Read uncommitted,Read committed, (脏读 不可重复读 幻读)
// /wang_keng/article/details/77741451 (讲得很清晰明了)
问1、 not in / not exists (区别)
问2、 java中,mysbatis和hibernate是否都支持批量插入?如果我有10w条数据,应该考虑如何批量插入??
答案:这里,你应该能考虑到10w条数据同时批量插入,肯定耗时很长,可以考虑分页,一页100条,每次批量插入一页,开启事务。(个人看法)
}
/**
* 经典sql:
* 表名:购物信息
* 购物人 商品名称 数量
* A 甲 2
* B 乙 4
* C 丙 1
* A 丁 2
* B 丙 5
* 给出所有购入商品为两种或两种以上的购物人记录;
*/
public void testMysql() {
/*
* 表:shop
* 字段:id shopper goods_name number
*
* select * from shop where in (select shopper from shop s group by having count() >= 2)
*
*
*/
/**
* create table(
* 'id' int,
* 'name' varchar(255)
* )
*/
}
三、Nosql相关
常用的应该就Redis,MongoDb,尽可能多的了解。
这个就需要考你平时开发中的积累了,有些公司可能会问:
1.讲讲Redis和MongoDb的优缺点?实际使用场景?
2.缓存穿透、缓存击穿、缓存雪崩区别和解决方案??(redis)
/**
* Redis:
*
* 1.基本的数据类型 :string, list, set, zset, hash
* 2.使用场景: 电商系统里面的排行榜,网站的访问量统计,分布式锁(setnx(key,value) -- > 成功返回1,失败返回0)
*
*
*
* MongoDb:
* 1. database(数据库) --> collection (表) --> document --> index
* 2. find(),sort(),limit(),skip(),match(),aggregate()聚合操作。
*/
四、前端
熟练掌握js、Jquery等前端技术,包括javaScript,jquey,html,css,bootstrap等,会得越多越好…vue,react啊
简单的说,开发后台管理系统,写jsp页面你要会。朝着全栈发展就对了。
五、MQ
1.消息队列的消费方式?JMS?点对点,多对点?
2.消息丢失和重复消费?如何解决
3.常用的MQ优缺点?RebbitMq,Kafka,ActiveMq? 这里,要深入了解其中一种才行
/**
* zookeeper + kafka /liyiming2017/article/details/82805479 (很棒的博客)
*
* leader 控制消息分发 分区,多个消费者同时消费,大大提高了kafka的吞吐量
*
* zookeeper: 配置管理,分布式系统协调服务, 存储结构,Znode:树型,跟linux的文件系统路径相似;znode,可以往这个节点存储或获取数据,
* 基本命令: create (-s/-e) /node_1 "data" //创建节点 ls / //查看节点 set node_1 //赋值 get /node_1 //查询该节点下面的值
* znode节点类型: 4种,持久节点,持久顺序节点,临时节点,临时顺序节点
*
*
* zookeeper kafka -- topic消费方式,点对点,多对点。
*
* 消息消费模型(JMS,区别就在于会不会被重复消费):2种 1. 点对点 queue,每个消息只会被一个消费者消费 2:发布/订阅 topic: 如果topic被多个消费者订阅,就会重复消费
*
* 问:如何解决kafka数据丢失和重复消费?
* 答:数据丢失主要考虑生产者,kafkaProducer ack = all/1 ;只要保证集群稳定的情况下,可以保证数据不丢失 (at least once)
* 重复消费主要考虑消费者,kafkaConsumer = true .消费者消费完消息之后,再自动提交offset。(at least once)
* kafka事务指kafka一系列 生产、消费消息等操作组成一个原子操作
*
* 幂等操作和事务,保证生产者和消费者都是原子操作。
*
*
*/
六、Spring boot + Spring cloud
一些面试题:
Spring boot:
boot自动配置怎么实现的?@EnableAutoConfiguration,@Import,@ConfigurationProperties
boot中,yml和properties的区别,加载顺序,优先级?
Spring cloud:
...待补充,spring cloud微服务,我也只是使用过,里面的大部分组件,原理都不清楚。
// 相关博客
/**
* springboot自动配置的原理
* /u014745069/article/details/83820511
* /developer/article/1442150 (实践,写得很棒)
*
*/
17
七、Linux Shell
会常用的命令和简答的脚本编写。。例如服务的打包,运行
如果,你还会Jenkins持续集成,那就更棒了
总结
1.上面提到的六点,如果你都没问题了,那么在成都找个10-13k的工作应该是没问题的(中级-3年)。(3-5年,18k,应该算高级吧)
2.最近面试,我发现,很多公司或多或少都会问spring cloud微服务相关的。看样子,目前spring boot,spring cloud全家桶在IT界的使用越来越普遍了。
3.在复习的时候,建议多敲多写。比如一个知识点,你看完了,默想一遍,然后从头到尾,把自己的理解在在本子上写一遍(提升记忆)。
4.面试的时候,举一反三。。。这个就是考验自己掌握知识点的广度了。
比如,面试官问,一个后台管理系统,他想记录每个操作对应的操作人,操作时间,该怎么实现?这个就是AOP常用的使用场景,日志监控。 @Pointcut,@Around…紧接着底层使用了动态代理模式实现,继续动态代理涉及到的两个类(Proxy,InvocationHandler -> invoke()方法) ,动态代理里面有用到了java的反射机制,Class,Method,Field等类。。
最后 – 成都多点公司面试
/**
*
* 电话面试 -- 高级 -----------------
* 成都多点公司(要求较高,对相关开源框架的底层原理,jvm,gc,集合,等问得特别详细):
*
* jvm如何调优,一些调优参数了解吗? -xmx GC: 内存主要有哪几块,新生代,老生代,永久代,(标记算法,回收算法), 哪块内存用那种算法,
*
* zookeeper kafka -- 1. jms消费模型 topic消费方式,点对点,多对点。 数据丢失和重复消费?如何解决?的存储结构, 4,kafka里面的broker,consumer group,offset。。
*
* 数据结构:queue(队列)(先进先出) 栈(heap):桶,先进后出 java内存: 堆 栈 常量池 程序计数器
* 数据结构&算法(一)_堆、栈(桶)(堆栈)、队列、链表 /hedeyong/p/
*
* 多线程:1.乐观锁,悲观锁, 如何加锁(原理) ,join,wait 区别, 释放锁(资源)? 为什么? 3. lock CAS(CAS算法) aqs
* CAS: /s?__biz=MzI3NzE0NjcwMg==&mid=2650122072&idx=1&sn=63690ad2cbf2b5390c3d8e1953ffbacf&chksm=f36bba79c41c336fbea8b56289fc2a71e829042f6c3616e3ba051c2542b48f0a3936e3d852f6&mpshare=1&scene=1&srcid=0225xcUOCP6bBS8aCrcd1jBd#rd
*
* 集合:1. concurrentHashMap (线程安全hashMap) 原理 【重点】 源码, 扩容,
*
* 数据库: 1. mysql 引擎有哪几种(5种,Innodb(支持事务), mylsam(不支持事务)),各自优点?
* 2.索引类型 普通索引(normal),唯一索引(unique),全文索引(FullText), 索引方法:btree hash?
*
* spring cloud 微服务架构 /spring-cloud-learning/
*
* redis: 基本数据类型?使用场景?如何备份???缓存雪崩?
*/
如果你在求职中,工作经验2~3年,可以花点时间,认认真真将文中提到的知识点过一遍,敲一遍,或许会对你有些帮助。如果能够帮助到你,我很开心。【付出和收获总是成正比的】
找工作不急,多面几家,对比。面试如果有戏,面试官问你还有什么问题的时候,建议问一下他们项目组目前你去了之后负责的项目是什么?用到了什么框架?项目组人员结构,后端几个?这些都问一下,不然去了之后,和自己想像的不太一致。 我有个朋友就是,入职第一天,才晓得他们那个项目就两个人开发,ssm和ssh都没用到。持久层用的原生jdbc开发,没有分MVC。
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
咱们下期见!答案获取方式:已赞 已评 已关~
学习更多知识与技巧,关注与私信博主(03)