晨考题
晨考题
1. jdk8中,HashMap底层用到哪些数据结构?有哪些线程安全的map类型?
在JDK 8中,HashMap底层使用了数组和链表(或红黑树)这两种数据结构来实现。
具体来说,HashMap使用一个数组来存储元素,每个数组元素称为桶(bucket)。当元素被插入HashMap时,根据元素的哈希值将其放入对应的桶中。每个桶中可以存储多个元素,如果多个元素哈希值相同,它们会以链表的形式连接在同一个桶中。当链表中的元素数量超过一定阈值时,链表会被转换成平衡的红黑树,以提高查找的效率。
JDK 8中提供了以下几种线程安全的Map实现:
- ConcurrentHashMap:这是一个高效的线程安全的HashMap替代品。它通过将整个Map分割为多个部分(称为段)来实现并发访问。每个段都相当于一个小的HashMap,不同的线程可以同时访问不同的段,从而提高并发性能。
- ConcurrentSkipListMap:这是基于跳表(Skip List)数据结构实现的线程安全有序Map。它支持按键的自然顺序或自定义顺序进行排序,具有较高的并发性能。
- Collections.synchronizedMap:这是一个传统的基于锁的线程安全Map实现。它通过在每个公共方法上加锁来确保线程安全,但在高并发环境下性能可能较低。
2. 向HashSet中添加元素时,如何判断对象是否重复?
当向HashSet中添加元素时,HashSet会判断对象是否重复的依据是对象的hashCode和equals方法。
HashSet使用对象的hashCode方法确定元素在内部存储结构中的位置。首先,它会计算新元素的hashCode值,然后根据该值确定元素在存储结构中的桶位置。
如果两个对象的hashCode值不同,HashSet会认为它们是不同的元素,无需进一步比较。但是,如果两个对象的hashCode值相同,HashSet会调用它们的equals方法进行进一步的比较。
equals方法用于比较两个对象的内容是否相等。默认情况下,equals方法比较的是对象的引用是否相等,即是否指向同一内存地址。如果需要自定义判断两个对象内容是否相等的逻辑,需要重写equals方法。
如果equals方法返回true,HashSet会认为这两个对象相等,不会将新元素添加到HashSet中,因为HashSet要求元素不能重复。如果equals方法返回false,HashSet会将新元素添加到HashSet中。
因此,为了正确使用HashSet,需要保证重写了hashCode和equals方法,以便正确判断对象的重复性。
3. String/StringBuffer/StringBuilder有什么区别?String是否可以被继承?
String、StringBuffer和StringBuilder是Java中用于处理字符串的类,它们之间有以下区别:
- 不可变性:
- String类是不可变的,一旦创建,其值不能被修改。每次对字符串进行修改操作(如拼接、替换等),都会创建一个新的String对象。这种特性使得String在多线程环境下是线程安全的。
- StringBuffer和StringBuilder类是可变的,它们允许对字符串进行修改操作,而不会创建新的对象。StringBuilder是在JDK 1.5引入的,与StringBuffer功能类似,但是不是线程安全的。
- 线程安全性:
- String是线程安全的,因为它是不可变的。多个线程可以共享同一个String对象而无需担心数据的修改。
- StringBuffer是线程安全的,它的方法都使用了synchronized关键字进行同步,可以安全地在多线程环境中使用。
- StringBuilder不是线程安全的,它的方法没有进行同步,因此在多线程环境中使用可能会导致数据不一致的问题。
- 性能:
- 由于String是不可变的,每次对String进行修改操作都会创建新的对象,会产生额外的内存开销。如果需要频繁进行字符串拼接等操作,使用StringBuffer或StringBuilder会更高效,因为它们不会创建新的对象。
关于String是否可以被继承的问题,String类是final的,即不可被继承。这是为了保证String的不可变性和安全性,防止子类修改String的行为。
4. 简述自己知道的线程创建方式有哪些
- 继承Thread类: 创建线程的一种方式是通过继承Thread类,并重写其run()方法来定义线程的任务逻辑。然后可以创建Thread的子类对象,并调用其start()方法来启动线程。
class MyThread extends Thread {
@Override
public void run() {
// 线程的任务逻辑
}
}
// 创建线程对象并启动线程
Thread myThread = new MyThread();
myThread.start();
- 实现Runnable接口: 另一种创建线程的方式是实现Runnable接口,并将其作为参数传递给Thread类的构造方法。然后创建Thread对象,并调用start()方法启动线程。
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程的任务逻辑
}
}
// 创建Runnable实例和线程对象,然后启动线程
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
- 使用Callable和Future: Callable是一个带有返回值的接口,可以通过实现Callable接口来定义线程的任务逻辑,并返回计算结果。Future接口则用于获取线程执行的结果。
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程的任务逻辑,返回一个结果
return 42;
}
}
// 创建Callable实例和线程池
Callable<Integer> myCallable = new MyCallable();
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交Callable任务并获取Future对象
Future<Integer> future = executorService.submit(myCallable);
// 获取线程执行的结果
try {
Integer result = future.get();
// 处理结果
} catch (InterruptedException | ExecutionException e) {
// 处理异常
} finally {
executorService.shutdown();
}
- 使用线程池: 线程池是一种管理和复用线程的机制,可以避免频繁地创建和销毁线程,提高线程的执行效率。通过Java提供的Executor框架,可以使用线程池来创建和管理线程。
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务给线程池
executorService.execute(() -> {
// 线程的任务逻辑
});
// 关闭线程池
executorService.shutdown();
5. 简述java中,线程有哪些状态,状态之间是如何转换的?
在Java中,线程有以下几种状态:
-
新建(New):当线程对象被创建时,它处于新建状态。此时线程对象已经分配内存,但尚未启动线程。
-
运行(Runnable):一旦调用线程对象的start()方法,线程就进入了运行状态。此时线程处于可执行状态,但并不一定立即执行,需要等待系统调度。
-
阻塞(Blocked):当线程正在等待获取一个排它锁(synchronized关键字)时,如果锁已被其他线程占用,线程将进入阻塞状态。此外,线程也可能因为调用Thread.sleep()方法、等待I/O操作或等待其他线程的通知等原因进入阻塞状态。
-
等待(Waiting):线程进入等待状态是由于调用了Object.wait()、Thread.join()或LockSupport.park()等方法。在等待状态下,线程会释放已经获得的锁,并进入等待队列,直到其他线程唤醒它。
-
计时等待(Timed Waiting):与等待状态类似,线程调用带有超时参数的方法,如Thread.sleep()、Object.wait()、Thread.join()等,进入计时等待状态。在超过指定时间后,线程会自动唤醒并重新进入可运行状态。
-
终止(Terminated):线程执行完任务或发生异常时,线程将进入终止状态。一旦线程进入终止状态,它的生命周期便结束了。
线程的状态之间的转换如下:
- 新建状态(New)可以转换为可运行状态(Runnable)。
- 可运行状态(Runnable)可能转换为阻塞状态(Blocked)、等待状态(Waiting)或计时等待状态(Timed Waiting)。
- 阻塞状态(Blocked)可以转换为可运行状态(Runnable)。
- 等待状态(Waiting)和计时等待状态(Timed Waiting)可以转换为可运行状态(Runnable)或被唤醒后直接转换为阻塞状态(Blocked)。
- 可运行状态(Runnable)可能转换为终止状态(Terminated)。
这些状态之间的转换是由Java虚拟机的线程调度器和线程的执行情况控制的,具体的转换可能受到操作系统调度和其他因素的影响。
6. 线程池的7个参数是什么?线程池执行任务的流程是什么?
线程池的常用参数包括以下七个:
-
corePoolSize(核心线程数):线程池中始终保持的活动线程数,即使它们处于空闲状态。
-
maximumPoolSize(最大线程数):线程池中允许存在的最大线程数,包括活动线程和等待队列中的线程。
-
keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程的存活时间。
-
TimeUnit(时间单位):用于指定keepAliveTime的时间单位,如秒、毫秒等。
-
workQueue(工作队列):用于保存等待执行的任务的队列。常用的工作队列类型包括有界队列(如ArrayBlockingQueue)和无界队列(如LinkedBlockingQueue)。
-
threadFactory(线程工厂):用于创建新线程的工厂类。可以自定义线程工厂来定制线程的名称、优先级、线程组等属性。
-
handler(拒绝策略):当工作队列已满且线程池无法处理新提交的任务时,用于决定如何处理被拒绝的任务。
线程池执行任务的基本流程如下:
-
根据参数配置,创建一个线程池对象。
-
当有任务提交到线程池时,线程池会根据以下策略处理任务:
- 如果当前活动线程数小于核心线程数(corePoolSize),创建新的线程执行任务。
- 如果当前活动线程数大于等于核心线程数,但工作队列未满,则将任务添加到工作队列中等待执行。
- 如果工作队列已满且当前线程数小于最大线程数(maximumPoolSize),创建新的线程执行任务。
- 如果工作队列已满且当前线程数已达到最大线程数,根据指定的拒绝策略来处理无法处理的任务。
-
当一个线程空闲的时间超过keepAliveTime时,线程会被回收销毁,以保持线程池的线程数量在核心线程数范围内。
-
当线程池不再需要执行任务时,可以调用shutdown()方法来关闭线程池,这会使得线程池拒绝新任务并逐渐停止所有活动线程。
请注意,线程池的具体实现和行为可能会因编程语言、线程库和具体应用程序的需求而有所不同。上述流程仅为常见线程池的一般执行过程,实际情况中可能会有更多的细节和复杂性。
7. 数据库有哪些隔离级别,mysql默认使用的什么隔离级别
在关系型数据库中,常见的隔离级别有以下七种:
-
Read Uncommitted(读取未提交数据):最低的隔离级别,在该级别下,一个事务可以读取另一个事务未提交的数据,可能会出现脏读(Dirty Read)的问题。
-
Read Committed(读取已提交数据):在该级别下,一个事务只能读取已经提交的数据,避免了脏读的问题。但是,由于其他事务可能在读取过程中进行了提交,因此可能会出现不可重复读(Non-repeatable Read)的问题。
-
Repeatable Read(可重复读):在该级别下,一个事务在执行过程中,多次读取同一数据会得到相同的结果,避免了不可重复读的问题。但是,由于其他事务可能在读取过程中进行了插入、删除操作,因此可能会出现幻读(Phantom Read)的问题。
-
Serializable(串行化):最高的隔离级别,要求事务串行执行,即同一时间只能有一个事务在执行,确保了数据的完整性和一致性。在该级别下,不会出现脏读、不可重复读和幻读的问题,但会降低并发性能。
-
Snapshot Isolation(快照隔离):该级别在MySQL中被称为"REPEATABLE READ"。它通过在读取操作时使用数据的快照来实现可重复读,避免了不可重复读的问题。但是,仍然可能出现幻读的问题。
-
Read Committed Snapshot(读取已提交的快照):该级别在SQL Server中被称为"READ_COMMITTED_SNAPSHOT"。它通过在读取操作时使用数据的快照来实现读取已提交数据,避免了脏读的问题。同时,它也使用了快照来实现可重复读,避免了不可重复读的问题。但是,仍然可能出现幻读的问题。
-
Serializable Snapshot Isolation(可串行化的快照隔离):该级别在Oracle数据库中被称为"SERIALIZABLE"。它在快照隔离的基础上通过使用读取锁和写入锁来实现事务的串行化,避免了脏读、不可重复读和幻读的问题。
MySQL默认使用的隔离级别是Repeatable Read(可重复读)。
8. Mysql中char和varchar的区别?Mysql默认的存储引擎是什么?
在MySQL中,char和varchar是用于存储字符串数据的两种数据类型,它们之间有以下区别:
-
存储方式:char类型使用固定长度来存储字符串,而varchar类型使用可变长度存储。例如,如果定义一个char(10)的列,无论实际存储的字符串长度是多少,都会占用10个字符的存储空间;而如果定义一个varchar(10)的列,实际存储的字符串长度只占用所需的空间,不会占用额外的空间。
-
存储效率:由于char类型使用固定长度,所以在存储和检索数据时效率比较高。而varchar类型由于可变长度,需要额外的字节用于存储长度信息,因此在存储和检索数据时可能会稍微慢一些。
-
占用空间:由于char类型使用固定长度,所以在占用存储空间方面可能会浪费一些空间。而varchar类型根据实际存储的字符串长度来占用空间,可以更有效地利用存储空间。
-
字符串截断:如果将一个长度超过字段定义的字符串插入char类型的列中,MySQL会自动截断超出部分。而对于varchar类型,如果插入的字符串超过字段定义的长度,MySQL会产生错误。
MySQL默认的存储引擎是InnoDB。在MySQL 5.5.5之前的版本中,默认的存储引擎是MyISAM,但从MySQL 5.5.5开始,默认的存储引擎被更改为InnoDB。不过,需要注意的是,具体的默认存储引擎可能会因MySQL的版本和配置而有所不同。
9. 什么是聚簇索引和非聚簇索引
聚簇索引(Clustered Index)和非聚簇索引(Non-clustered Index)是关系型数据库中常见的两种索引类型,它们有以下特点和区别:
聚簇索引:
- 聚簇索引定义在表上,用于指定表中数据行的物理排序顺序。
- 每个表只能有一个聚簇索引。
- 聚簇索引决定了数据的存储方式,因此表的数据行实际上是按照聚簇索引的顺序存储的。
- 如果表按照主键创建了聚簇索引,则称之为主键聚簇索引。
- 聚簇索引的叶子节点包含了完整的数据行,因此使用聚簇索引可以快速定位到所需的数据。
非聚簇索引:
- 非聚簇索引是基于聚簇索引之外的列或组合列上创建的索引。
- 每个表可以有多个非聚簇索引。
- 非聚簇索引的叶子节点不包含完整的数据行,而是包含索引列的值和一个指向对应数据行的指针。
- 使用非聚簇索引进行查询时,数据库需要先在索引中定位到对应的记录,然后通过指针找到实际的数据行。
区别总结:
- 聚簇索引决定了数据的物理排序,而非聚簇索引只是提供了对数据的快速访问路径。
- 聚簇索引只能有一个,而非聚簇索引可以有多个。
- 聚簇索引的叶子节点包含完整的数据行,而非聚簇索引的叶子节点只包含索引列的值和指向数据行的指针。
10. 数据库查询时,什么是最左匹配原则
最左匹配原则(Leftmost Prefix Rule)是指在数据库查询中,如果建立了复合索引(多个列组合而成的索引),则查询条件中的列需要按照索引中列的顺序依次进行匹配,而不能跳过中间的列。
具体来说,当查询中的列与复合索引的列进行匹配时,最左匹配原则要求先使用索引中的最左边的列进行匹配,然后再考虑后续的列。
举个例子来说明最左匹配原则的作用:
假设有一个复合索引包含两列(A, B),查询条件为(A = 1 AND B = 2)。根据最左匹配原则,数据库引擎会先使用索引的最左边的列 A 进行匹配,然后再使用列 B 进行匹配。如果查询条件改为(B = 2),而没有指定 A 的值,那么最左匹配原则将无法使用索引进行匹配,因为索引的最左边的列 A 没有被提供。
最左匹配原则的原因在于复合索引的建立方式。复合索引是按照列的顺序进行排序和存储的,因此在进行查询时需要按照相同的顺序进行匹配才能有效利用索引的有序性。
使用最左匹配原则可以帮助数据库优化查询性能,尽可能地利用复合索引的有序性。因此,在设计复合索引时,需要考虑查询条件中的列的顺序,并将最常用的、最有选择性的列放在最左边。这样可以更好地满足最左匹配原则,提高查询的效率。
11. 简述对Spring中IOC/DI的理解
在Spring框架中,IOC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是两个核心概念,用于实现松耦合、可维护和可测试的应用程序。
IOC(控制反转)是一种设计原则,它将应用程序的控制权从应用程序代码中转移到框架或容器中。传统的应用程序通常由应用程序代码主动地创建和管理各种对象的生命周期,而在IOC中,框架或容器负责管理对象的创建、组装和生命周期。这种控制反转的机制使得应用程序更加灵活,可以更方便地配置和管理对象的依赖关系。
DI(依赖注入)是IOC的一种具体实现方式。它通过将对象所依赖的其他对象(依赖)注入到对象中,实现了对象之间的解耦。DI可以通过构造函数注入、Setter方法注入或接口注入等方式进行。通过依赖注入,对象不再需要自己创建或查找依赖的对象,而是由容器负责提供所需的依赖对象。这样可以简化对象的创建和管理,并且使得对象更加可复用和可测试。
在Spring框架中,IOC和DI是通过Spring容器实现的。Spring容器负责创建、组装和管理应用程序中的对象。它根据配置信息或注解来了解对象之间的依赖关系,并在运行时自动完成对象的创建和依赖注入。
总结来说,IOC和DI是Spring框架中用于实现控制反转和依赖注入的核心概念。它们使得应用程序更加灵活、可维护和可测试,并通过Spring容器提供了一种方便的方式来管理对象的生命周期和依赖关系。
12. 简述自己知道的依赖注入方式
依赖注入(Dependency Injection,DI)是一种实现控制反转(Inversion of Control,IOC)的方式,在应用程序中将对象的依赖关系从代码中解耦,使得对象的依赖关系由外部容器负责管理。以下是几种常见的依赖注入方式:
-
构造函数注入(Constructor Injection):通过在对象的构造函数中声明依赖的参数,容器在创建对象时将依赖的实例传递给构造函数。这种方式强制对象在创建时就提供必需的依赖,保证对象的完整性和一致性。
-
Setter方法注入(Setter Injection):通过在对象的Setter方法中设置依赖的属性,容器在创建对象后,通过调用Setter方法来注入依赖。这种方式灵活性较高,可以在任何时候设置依赖,甚至可以在运行时动态更改依赖。
-
接口注入(Interface Injection):通过在对象实现的接口中定义注入方法,容器在创建对象后,通过调用接口中的方法来注入依赖。这种方式相对较少使用,因为它依赖于对象实现的特定接口。
-
注解注入(Annotation Injection):使用注解(如@Autowired、@Inject等)标记对象的依赖属性或方法,容器在创建对象时,根据注解信息自动完成依赖的注入。这种方式简化了配置,使得依赖关系更加明确和可读。
以上是常见的依赖注入方式,它们可以单独或结合使用,根据具体的需求和框架的支持来选择适合的方式。依赖注入可以提高代码的可测试性、可维护性和可扩展性,使对象之间的耦合度降低,同时也提供了更灵活的配置和管理对象依赖的方式。
13. 简述对面向切面的编程的理解,spring的aop中有哪些通知类型?
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,用于在应用程序中解耦横切关注点(Cross-cutting Concerns)的实现和核心业务逻辑。横切关注点是指那些散布在应用程序各处、不属于特定模块或层的功能,例如日志记录、安全性检查、事务管理等。
AOP通过将横切关注点从核心业务逻辑中抽离出来,并将其模块化地定义为切面(Aspect),从而实现了关注点的集中管理和复用。切面是一个横跨多个对象的模块,它定义了要在目标对象的哪些方法执行前、执行后或执行异常时插入特定的代码逻辑。通过将切面织入到应用程序中,切面的代码将在特定的切点(Join Point)处被动态地执行。
Spring框架提供了强大的AOP支持,其中包括以下几种通知类型:
-
前置通知(Before Advice):在目标方法执行前执行的通知。可以用于执行一些预处理逻辑,例如安全性检查、参数验证等。
-
后置通知(After Advice):在目标方法执行后(无论是否发生异常)执行的通知。可以用于执行一些后处理逻辑,例如日志记录、资源释放等。
-
返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。可以获取目标方法的返回值并进行相应的处理。
-
异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。可以用于捕获和处理目标方法抛出的异常。
-
环绕通知(Around Advice):包围目标方法的通知,可以在目标方法执行前后自定义处理逻辑。环绕通知具有最大的灵活性,可以完全控制目标方法的执行。
这些通知类型可以根据需求进行组合和配置,通过在Spring的配置文件或使用注解进行声明,将切面与目标对象关联起来,从而实现对横切关注点的统一处理。Spring AOP基于动态代理技术实现,可以对接口或类进行代理,并在运行时动态地将切面逻辑织入到目标对象的方法中。
14. @Autowired与@Resource的区别
@Autowired和@Resource都是Java中用于进行依赖注入的注解,用于标记需要自动装配的依赖对象。它们在使用方式和功能上略有不同:
@Autowired:
- 来自Spring框架。它是根据类型进行自动装配的,默认按照byType的方式查找匹配的依赖对象。
- 可以标注在构造方法、字段、Setter方法和任意自定义方法上。
- 可以省略在某些情况下,例如只有一个与依赖类型匹配的Bean时,Spring会自动将其注入;如果有多个匹配的Bean,可以结合@Qualifier注解进行更精确的指定。
@Resource:
- 来自Java EE标准,也可以在Spring中使用。它是根据名称进行自动装配的,默认按照byName的方式查找匹配的依赖对象。
- 可以标注在字段、Setter方法上,不支持构造方法注入。
- 需要指定name属性,用于指定要注入的依赖对象的名称。
总结来说,@Autowired和@Resource都可以实现依赖注入,但它们在查找匹配依赖对象的方式上有所不同。@Autowired按照类型匹配,而@Resource按照名称匹配。在使用上,@Autowired更为灵活,支持多种注入方式和条件,而@Resource相对简单,主要用于按照名称注入依赖对象。根据具体的需求和使用场景,选择适合的注解来实现依赖注入。
15. spring中,声明式事务的注解是什么?什么情况下事务会失效
在Spring中,声明式事务可以通过注解来实现,其中最常用的注解是@Transactional
。
@Transactional
注解可以应用于类级别或方法级别。当应用于类级别时,表示整个类中的所有方法都具有事务性;当应用于方法级别时,表示该方法具有事务性。
使用@Transactional
注解时,可以配置一些属性来定义事务的行为,例如事务的传播行为(Propagation)、隔离级别(Isolation)、回滚规则(Rollback Rules)等。
事务会失效的情况包括:
-
异常未被捕获或处理:如果在事务方法内部抛出未被捕获或处理的异常,事务将被回滚。如果异常被捕获或处理,事务可能会继续进行,根据配置的回滚规则决定是否回滚。
-
非受检异常:默认情况下,只有
RuntimeException
及其子类会触发事务回滚。如果抛出的是受检异常,事务不会回滚,除非将其配置为回滚的异常。 -
事务方法内部调用其他事务方法:如果在事务方法内部调用另一个带有
@Transactional
注解的事务方法,事务可能会失效。这是因为Spring默认使用基于代理的事务管理,而基于代理的事务管理无法捕获同一类中的方法调用。 -
多线程:在多线程环境下,如果使用基于代理的事务管理,事务可能会失效。这是因为基于代理的事务管理是基于线程绑定的,每个线程拥有独立的事务上下文。
注意:在Spring中,声明式事务仅适用于通过Spring的AOP代理调用的方法。如果在同一个类中的方法直接调用另一个方法,事务可能会失效。要解决这个问题,可以使用编程式事务或将事务逻辑移动到另一个Bean中。
16. mybatis的映射配置中,”<” 是特殊符号,sql语句中如果使用”<”,需要如何处理
在MyBatis的映射配置中,"<"是一个特殊字符,用于标记XML中的标签。如果在SQL语句中需要使用"<"进行比较操作,需要进行特殊处理,以避免XML解析错误。
在MyBatis中,可以使用XML实体来表示"<"字符,使用如下两种方式进行转义:
-
使用XML实体表示:
- "<"表示为
<
- ">"表示为
>
- "&"表示为
&
例如,如果要在SQL语句中使用"<"进行比较,可以将其替换为
<
,如下所示:SELECT * FROM table WHERE column < 10
- "<"表示为
-
使用CDATA块:
另一种处理方式是使用CDATA块,将SQL语句包裹在<![CDATA[...]]>
中,CDATA块中的内容将被视为纯文本,不进行XML解析。这样,可以直接在SQL语句中使用"<"进行比较,如下所示:<![CDATA[ SELECT * FROM table WHERE column < 10 ]]>
通过上述方式处理,MyBatis将能够正确解析包含"<"字符的SQL语句,避免解析错误。
17. mybatis中#{}和${}的区别?举例说明什么情况下必须使用${}
在MyBatis中,#{}
和${}
都是用于在SQL语句中插入参数的占位符,但它们在处理方式和安全性上有所不同。
-
#{}
(预编译):#{}
是使用预编译方式处理参数,可以防止SQL注入攻击。#{}
会将传入的参数值作为一个整体,自动进行参数类型转换和SQL语句的占位符替换。#{}
可以确保参数值在SQL语句中的正确引用,适用于大部分的情况。
例如:
SELECT * FROM table WHERE column = #{param}
-
${}
(字符串拼接):${}
是直接将参数值拼接到SQL语句中,不进行预编译,存在SQL注入的风险。${}
不会自动进行参数类型转换,参数值直接替换占位符。${}
可以用于拼接动态的表名、列名等。
例如:
SELECT * FROM ${tableName} WHERE column = '${param}'
在使用${}
时,需要注意潜在的安全风险,因为它直接将参数值拼接到SQL语句中,没有进行预编译和参数类型转换。因此,必须确保参数值的安全性,避免可能的SQL注入攻击。
一种常见的情况下必须使用${}
是在动态拼接表名或列名时,因为#{}
不支持在这种情况下使用。在这种情况下,需要自行确保参数值的安全性,例如使用白名单验证、输入校验等措施来避免安全风险。
总结来说,推荐在大部分情况下使用#{}
,它具有更好的安全性和可维护性。只有在需要动态拼接表名或列名的情况下,才使用${}
,并且要确保参数值的安全性。
18. springmvc的工作原理
Spring MVC是基于Spring框架的Web框架,用于开发Web应用程序。下面是Spring MVC的工作原理概述:
-
客户端发送请求:
客户端(例如浏览器)发送HTTP请求到Spring MVC应用程序。 -
DispatcherServlet接收请求:
请求首先被DispatcherServlet(前端控制器)接收,它是Spring MVC的核心组件之一。 -
HandlerMapping选择处理器:
DispatcherServlet通过HandlerMapping(处理器映射器)来确定请求对应的处理器(Handler)。 -
Handler执行请求处理:
选定的处理器(Handler)被执行,进行请求处理。处理器可以是一个Controller类或者一个带有@RequestMapping注解的方法。 -
Handler执行结果返回:
处理器执行后,会生成一个ModelAndView对象,其中包含处理结果的数据以及用于渲染的视图名称。 -
ViewResolver解析视图:
ViewResolver(视图解析器)根据视图名称解析出真正的视图对象。 -
View渲染:
视图对象负责渲染模型数据,生成响应内容。 -
响应返回客户端:
渲染后的视图生成响应内容,并发送回客户端。
整个过程中,DispatcherServlet起到了核心的作用,它充当了前端控制器的角色,负责协调和管理各个组件的工作流程。它通过HandlerMapping选择适当的处理器,然后将处理结果交给ViewResolver解析视图并渲染最终的响应。
通过这样的流程,Spring MVC框架将请求的处理逻辑与具体的实现分离,提供了一种基于MVC模式的Web开发方式,使得开发人员能够更加专注于业务逻辑的实现,同时提供了灵活性和可扩展性。
19. @RequestBody和@ResponseBody的作用
@RequestBody
和@ResponseBody
是Spring MVC框架中用于处理请求和响应的注解。
-
@RequestBody
:@RequestBody
注解用于将HTTP请求的内容(例如JSON或XML)绑定到方法的参数上。- 当一个请求到达控制器方法时,Spring MVC会尝试将请求体的内容转换为方法参数所声明的对象类型。
@RequestBody
常用于处理POST请求,将请求体的内容反序列化为Java对象。
示例:
@PostMapping("/users") public void createUser(@RequestBody User user) { // 处理创建用户的逻辑 }
-
@ResponseBody
:@ResponseBody
注解用于将方法的返回值直接作为HTTP响应体的内容返回给客户端。- 方法的返回值可以是任意类型,Spring MVC会将其转换为合适的HTTP响应格式(例如JSON、XML等)。
@ResponseBody
常用于RESTful风格的控制器,将方法的返回值序列化为客户端需要的格式。
示例:
@GetMapping("/users/{id}") @ResponseBody public User getUser(@PathVariable Long id) { // 查询用户信息 return userService.getUserById(id); }
通过使用@RequestBody
和@ResponseBody
注解,Spring MVC可以方便地处理请求和响应的内容转换,使开发者能够更加灵活地处理不同类型的数据,并实现面向资源的Web服务。
20. @DateTimeFormat和@JsonFormat注解的作用
@DateTimeFormat
和@JsonFormat
是Spring框架中用于处理日期和时间格式化的注解。
-
@DateTimeFormat
:@DateTimeFormat
注解用于指定日期和时间类型的格式化模式。- 在接收请求参数时,可以将字符串类型的日期和时间转换为对应的Java日期和时间对象。
@DateTimeFormat
通常与@RequestParam
、@PathVariable
等注解一起使用。
示例:
@GetMapping("/users") public void getUsers(@RequestParam("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate) { // 处理逻辑 }
-
@JsonFormat
:@JsonFormat
注解用于指定日期和时间类型在序列化为JSON时的格式化规则。- 在将Java对象转换为JSON响应时,可以按照指定的格式将日期和时间类型格式化为字符串。
@JsonFormat
通常与@ResponseBody
注解一起使用。
示例:
@GetMapping("/users/{id}") @ResponseBody public User getUser(@PathVariable Long id) { // 查询用户信息 User user = userService.getUserById(id); return user; }
public class User { private Long id; private String name; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createdAt; // getter/setter方法省略 }
通过使用@DateTimeFormat
和@JsonFormat
注解,可以在Spring应用程序中方便地处理日期和时间的格式化。@DateTimeFormat
用于请求参数的格式化转换,而@JsonFormat
用于JSON响应的格式化转换,使得日期和时间的处理更加灵活和易于配置。
21. @Value、@PostConstruct注解的作用
@Value
和@PostConstruct
是Spring框架中常用的注解,具有以下作用:
-
@Value
注解:@Value
注解用于将值注入到Spring管理的Bean中。- 可以用于注入配置文件中的属性值、系统环境变量等。
- 通过
@Value
注解,可以在类的字段、方法参数、构造函数参数等位置使用。
示例:
@Component public class MyComponent { @Value("${my.property}") private String myProperty; // getter/setter省略 }
上述示例中,通过
@Value
注解将配置文件中名为my.property
的属性值注入到myProperty
字段中。 -
@PostConstruct
注解:@PostConstruct
注解用于指定在Bean实例化之后,初始化方法执行之前调用的方法。- 该方法用于执行一些初始化操作,例如数据加载、资源分配等。
@PostConstruct
注解的方法可以在任意类中使用,但要确保该类由Spring容器进行管理。
示例:
@Component public class MyComponent { @PostConstruct public void init() { // 执行初始化操作 } }
上述示例中,
init()
方法使用@PostConstruct
注解标记,该方法会在MyComponent
被实例化后执行。
通过使用@Value
和@PostConstruct
注解,可以方便地实现属性值的注入和Bean的初始化操作。@Value
注解使得属性值的注入更加简洁,而@PostConstruct
注解可以在Bean初始化之前执行自定义的初始化方法。这些注解可以帮助简化开发过程,并与Spring容器紧密集成。
22. @RestController、@RestControllerAdvice的作用
@RestController
和@RestControllerAdvice
是Spring MVC框架中用于创建RESTful风格的控制器和全局异常处理的注解。
-
@RestController
注解:@RestController
注解是@Controller
和@ResponseBody
的组合注解。- 用于标记一个类为RESTful风格的控制器,简化了编写RESTful API的代码。
- 控制器中的方法可以直接返回响应体对象,而不需要额外的
@ResponseBody
注解。
示例:
@RestController @RequestMapping("/users") public class UserController { @GetMapping("/{id}") public User getUser(@PathVariable Long id) { // 查询用户信息 User user = userService.getUserById(id); return user; } }
-
@RestControllerAdvice
注解:@RestControllerAdvice
注解用于全局异常处理和全局数据绑定。- 可以定义全局的异常处理器和通用的数据绑定逻辑,减少代码重复。
- 在
@ExceptionHandler
、@InitBinder
和@ModelAttribute
等注解的方法上使用。
示例:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception ex) { // 处理异常并构建响应 ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } }
通过使用@RestController
和@RestControllerAdvice
注解,可以方便地创建RESTful风格的控制器,并实现全局异常处理和数据绑定逻辑。@RestController
注解简化了控制器的编写,而@RestControllerAdvice
注解使得全局异常处理和数据绑定更加集中和统一,提高了代码的可维护性和复用性。
23. maven的作用
Maven是一个Java项目管理工具,它提供了一种结构化的方式来管理项目的构建、依赖管理和项目信息管理。Maven的主要作用包括:
-
项目构建:Maven使用一组预定义的生命周期和插件来管理项目的构建过程。通过配置
pom.xml
文件中的构建插件和目标,可以定义项目的编译、测试、打包、部署等构建步骤。 -
依赖管理:Maven使用中央仓库和本地仓库来管理项目的依赖关系。在
pom.xml
文件中,可以指定项目的依赖项和版本号,Maven将自动下载并解析这些依赖项,确保项目能够正确地构建和运行。 -
项目信息管理:Maven使用
pom.xml
文件来管理项目的元数据和配置信息。在pom.xml
中,可以指定项目的坐标、名称、版本号、开发者信息、许可证信息等,这些信息可以用于项目的识别、发布和分发。 -
模块化管理:Maven支持多模块项目,可以将一个大型项目拆分成多个独立的模块,并通过父子模块的关系来管理它们之间的依赖关系。这种模块化的管理方式使得项目的结构更清晰、可维护性更高。
-
构建生命周期管理:Maven定义了一组标准的构建生命周期,包括
clean
、compile
、test
、package
、install
、deploy
等阶段。开发人员可以根据项目的需求,在这些生命周期的不同阶段执行自定义的插件和目标。
总而言之,Maven简化了Java项目的构建和依赖管理过程,提供了一种标准化的方式来管理和构建项目,减少了开发人员的工作量,提高了项目的可维护性和可重复性。它是Java开发中广泛使用的项目管理工具之一。
24. Redis的数据持久化策略有哪些?
Redis提供了两种数据持久化策略:RDB持久化和AOF持久化。
-
RDB持久化:
- RDB持久化是将Redis的数据以快照的形式保存到磁盘上的一个二进制文件中。
- 当配置的条件满足时,Redis会自动执行RDB持久化操作,将内存中的数据快照保存到磁盘。
- RDB持久化是通过fork子进程来完成的,可以最大程度地减少对主进程的影响。
- RDB文件是紧凑且压缩的,适合用于备份和恢复数据。
-
AOF持久化:
- AOF(Append Only File)持久化是通过将Redis的操作命令追加到一个日志文件中来实现数据持久化。
- AOF文件以文本格式保存,记录了Redis服务器收到的每个写操作命令。
- 当Redis重新启动时,它会重新执行AOF文件中的所有命令来恢复数据。
- AOF持久化可以选择不同的同步策略,如每个命令都同步、每秒同步一次或者不同步。
- AOF持久化可以提供更高的数据安全性,但相比RDB持久化会占用更多的磁盘空间和写入操作。
在Redis的配置文件中,可以选择使用RDB持久化、AOF持久化或者同时启用两者。可以根据具体的需求和应用场景选择适合的持久化策略。例如,如果对数据的完整性要求较高,可以选择AOF持久化;如果对数据的实时性要求较高,可以选择RDB持久化;如果需要更高的数据安全性,可以同时启用RDB和AOF持久化。
25. Redis中有哪些数据类型?zset可以用于什么场景?
Redis中有以下常用的数据类型:
- String(字符串):用于存储字符串值。
- Hash(哈希):存储字段和值的映射关系。
- List(列表):按照插入顺序存储的字符串列表。
- Set(集合):无序且唯一的字符串集合。
- ZSet 或 Sorted Set(有序集合):有序且唯一的字符串集合,每个成员都关联一个分数,通过分数进行排序。
其中,ZSet(有序集合)可以用于以下场景:
- 排行榜:通过ZSet存储用户的得分和排名,可以方便地获取排名靠前的用户。
- 带权重的任务队列:将任务和对应的权重存储在ZSet中,可以根据权重进行有序地取出任务。
- 范围查询:ZSet支持按照分数范围进行查询,可以快速获取分数在某个范围内的成员。
- 实时统计:ZSet可以用于实时统计某个时间段内的用户行为,如每日活跃用户、热门商品等。
总之,ZSet是一种有序且唯一的数据结构,适用于需要排序和按范围查询的场景,特别适合处理实时排行榜、带权重的任务队列和实时统计等应用场景。
26. 手写sql语句实现如下需求:
有员工表部门表
员工表(Employee):
- id (int)
- emp_name (varchar)
- age (int)
- sex (varchar)
- dept_id (int)
部门表(Department):
- id (int)
- dept_name (varchar)
(1)查询姓“张”的员工的总数
(2)查询”研发部”所有员工的信息,按照年龄降序排序
(3)统计每个部门的员工的人数
(4)删除id是12,15,23的员工数据
(5)批量插入两条员工信息,字段值自定
(1)查询姓“张”的员工的总数
SELECT COUNT(*) AS total_count
FROM Employee
WHERE emp_name LIKE '张%';
(2)查询”研发部”所有员工的信息,按照年龄降序排序
SELECT e.*
FROM Employee e
JOIN Department d ON e.dept_id = d.id
WHERE d.dept_name = '研发部'
ORDER BY e.age DESC;
(3)统计每个部门的员工的人数
SELECT d.dept_name, COUNT(*) AS employee_count
FROM Employee e
JOIN Department d ON e.dept_id = d.id
GROUP BY d.dept_name;
(4)删除id是12,15,23的员工数据
DELETE FROM Employee
WHERE id IN (12, 15, 23);
(5)批量插入两条员工信息,字段值自定
INSERT INTO Employee (id, emp_name, age, sex, dept_id)
VALUES
(24, 'John', 30, 'Male', 1),
(25, 'Jane', 28, 'Female', 2);