经典框架源码面试题
Lecture:波哥
1.谈谈你对框架的理解
1.1 框架的作用
JavaWeb中的框架是一种开发工具或者平台,它提供了一系列的功能和组件,用于简化和加速Web应用的开发过程。框架可以提供一些基础设施,如数据库访问、用户认证和授权、日志记录等,以及一些高级功能,如MVC模式、依赖注入、AOP等。
对于我来说,JavaWEB中的框架是一个可重用的代码库,它提供了一些现成的解决方案,可以帮助开发人员更快地构建稳定、高效的应用程序。框架可以帮助我们遵循最佳实践,减少重复劳动,提高代码的可维护性和可扩展性。同时,框架还可以提供一些标准化的API和约定,使得不同开发人员之间更容易合作和交流。通过使用框架,我们可以更专注于业务逻辑的实现,而不需要过多地关注底层的技术细节。
1.2 常见用框架有哪些
列举常用的开源框架及作用:
- Spring:提供了一个轻量级的容器,用于管理对象之间的依赖关系
- SpringMVC:提供了一个MVC模式的实现,用于处理用户请求和生成响应
- SpringBoot:基于Spring的脚手架工具。更进一步简化了项目的构建过程和依赖管理
- MyBatis:经典的ORM框架,现在使用更多的是MyBatisPlus
- SpringSecurity:基于Spring容器的一个权限管理框架
- Shiro:Apache提供的一个权限管理框架。相比SpringSecurity更轻量
- SpringCloud:是一个基于Spring Boot的开发工具包,用于构建分布式系统的微服务架构。它提供了一系列的开发工具和库,帮助开发者快速构建、部署和扩展分布式系统。
- Redis:是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据结构。
- Dubbo:是一种高性能、轻量级的开源分布式服务框架,由阿里巴巴集团开源。它提供了服务治理、负载均衡、容错、可视化运维等功能,能够帮助开发者快速构建可扩展的分布式应用。
- …
2.Spring的轻量体现在哪些方面?
Spring框架之所以被称为轻量级的框架,体现在以下几个方面:
- 配置简单:Spring使用基于XML或注解的配置方式,使得配置文件简洁明了。相比其他框架,Spring的配置文件通常更加简单和易于维护。
- 无侵入性:Spring框架的设计理念是非侵入式的,即应用程序的代码不需要继承框架特定的类或实现特定的接口。这使得开发者可以更加自由地编写业务逻辑,减少对框架的依赖。
- 松耦合:Spring框架通过控制反转(IoC)和依赖注入(DI)来实现松耦合的设计。通过IoC容器管理对象的生命周期和依赖关系,使得对象之间的耦合度降低,提高了代码的可维护性和可测试性。
- 模块化:Spring框架提供了丰富的模块化功能,开发者可以根据项目需要选择使用相应的模块,减少了不必要的开销。同时,Spring框架的模块之间也是相互独立的,可以根据具体需求进行组合和扩展。
- 高度可扩展:Spring框架的设计非常灵活,提供了大量的扩展接口和插件机制,开发者可以根据自己的需求进行定制和扩展。同时,Spring也与其他流行的开源框架和技术(如Hibernate、MyBatis、JUnit等)紧密集成,使得开发者可以更加方便地使用这些工具和技术。
总的来说,Spring框架的轻量级体现在其简单的配置、非侵入式的设计、松耦合的架构、模块化的功能和高度可扩展的特性上,使得开发者可以更加轻松地开发和维护应用程序。
3.Spring中是如何管理对象的依赖关系的?
Spring中通过IoC来管理Bean对象。然后通过DI来管理Bean之间的依赖关系。
3.1 IoC的介绍
Spring中的IoC(Inversion of Control)是一种设计原则,通过该原则,对象的创建和依赖关系的管理被转移到了容器中,从而降低了对象之间的耦合性。
从比较粗的角度介绍写IoC中的核心对象的作用:
BeanDefinition
:BeanDefinition是Spring中定义Bean的元数据信息的接口。它包含了Bean的类名、属性、构造函数参数等信息,通过BeanDefinition可以告诉容器如何创建和配置Bean。BeanDefinitionRegistry
:BeanDefinitionRegistry提供了对BeanDefinition的管理和操作功能,是Spring IoC容器中用于注册和管理Bean的核心接口。BeanFactory
:BeanFactory是Spring IoC容器的核心接口,负责实例化、配置和管理应用中的对象(Bean)。它是IoC容器的基础,提供了一种获取Bean的机制,可以通过Bean的名称或类型来获取Bean的实例。ApplicationContext
:ApplicationContext是BeanFactory的子接口,它是Spring中更高级的IoC容器。除了提供BeanFactory的功能外,它还提供了更多的企业级功能,例如国际化支持、事件发布、资源加载等。BeanPostProcessor
:BeanPostProcessor是Spring中的一个扩展接口,用于在Bean实例化和依赖注入的过程中对Bean进行增强处理。通过实现BeanPostProcessor接口,可以在Bean的初始化前后对Bean进行自定义操作。BeanWrapper
:BeanWrapper是Spring中对Bean对象的一种封装,提供了对Bean属性的访问和设置的方法。BeanWrapper可以对Bean的属性进行类型转换和验证等操作。
更细节的初始化的过程:https://www.processon.com/view/link/64b8ac2c10ce753b813fc371
3.2 DI的介绍
通过上面的IoC的原理分析我们也可以看到DI的实现过程。其实就是在Bean的实例化过程中会处理对象的依赖关系。
4.怎么解决依赖关系中的循环问题?
4.1 普通的循环依赖问题
循环依赖问题不只是在Spring中有。平常的代码环境中也会存在。
public class CircularTest {
public static void main(String[] args) {
new CircularTest1();
}
}
class CircularTest1{
private CircularTest2 circularTest2 ;
public CircularTest1(){
this.circularTest2 = new CircularTest2();
}
}
class CircularTest2{
private CircularTest1 circularTest1 = new CircularTest1();
public CircularTest2(CircularTest1 circularTest1){
this.circularTest1=new CircularTest1();
}
}
解决方案就是:
解决的本质为:
4.2 Spring中的循环依赖
Spring的核心是IoC和DI也就是对Bean的管理。这里肯定会涉及到对象之间关联关系的维护。那么就有可能会产生循环依赖的问题。在Spring中针对循环依赖的支持是:
- 单例模式:构造注入不被支持
- 原型模式:都不支持—为什么设置注入的方式。循环依赖也不支持? – User对象。10W个对象
Spring中解决循环依赖问题的关键是:
- 提前暴露
- 三级缓存
Spring中为了提供更加灵活的扩展和提高耦合性。在Bean对象的生命周期中提供了各种的后置处理器以及代理模式的应用。所以在处理循环依赖问题的时候也会比上面单纯的循环依赖问题的解决要更加的复杂些。这里需要弄清楚三级缓存中的每一级缓存的作用
- 三级缓存:singletonFactories 存放的是一个Lambda表达式
- 二级缓存:存放的是不完整的Bean
- 一级缓存:最终完成实例化的Bean— Spring中的Bean对象 单例对象 都保存在一级缓存中
5.Spring中AOP介绍下
5.1 AOP的概念
AOP
(面向切面编程)是一种软件设计思想,旨在通过将横切关注点与主要业务逻辑分离,提高代码的可维护性和可重用性。是 OOP
(面向对象)的有效补充。
在传统的面向对象编程中,代码的功能通常被分散在多个类中,这种分散导致了代码的重复和散乱。AOP的目标是通过将这些横切关注点(如日志记录、权限验证、事务管理等)从核心业务逻辑中分离出来,并将其封装成可复用的模块,从而实现代码的解耦和重用。
AOP的关键概念是切面(Aspect),切面是横切关注点的模块化,它定义了在何处(连接点)以及何时(切点)应用特定的横切关注点。切面可以通过通知(Advice)来实现具体的横切功能,通常包括前置通知(Before)、后置通知(After)、异常通知(AfterThrowing)和返回通知(AfterReturning)等。
5.2 AOP的实现方式
https://blog.csdn.net/qq_38526573/article/details/86441916
5.3 AOP源码分析
https://dpb-bobokaoya-sm.blog.csdn.net/article/details/127263992
5.4 AOP的实际应用
- 日志记录:通过AOP可以在方法执行前后记录日志,包括方法名、参数、返回值等信息,方便系统运维和故障排查。
- 安全验证:通过AOP可以在方法执行前进行安全验证,例如用户身份验证、权限控制等。
- 事务管理:通过AOP可以在方法执行前后开启、提交或回滚事务,确保数据一致性。
- 缓存管理:通过AOP可以在方法执行前检查缓存中是否存在结果,并在方法执行后将结果缓存起来,提高系统性能。
- 性能监控:通过AOP可以在方法执行前后统计方法的执行时间,并记录到日志中,方便性能优化和系统调优。
- 异常处理:通过AOP可以在方法执行过程中捕获异常,并进行统一的异常处理,例如返回特定的错误码或错误页面。
- 校验和验证:通过AOP可以在方法执行前对参数进行校验和验证,确保参数的合法性。
- 重试机制:通过AOP可以在方法执行失败时进行重试,以提高系统的容错能力。
总之,Spring的AOP在实际工作中可以应用于各种场景,通过将通用的横切关注点集中处理,提高了系统的可维护性、可扩展性和代码的复用性。
6.Spring中的事务介绍
6.1 事务的介绍
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。
一个逻辑工作单元要成为事务,必须满足所谓的 ACID
(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
6.2 Spring中事务的实现
Spring中的事务实现有两种方式:
- 基于配置文件
- 基于注解:@Transactionl
6.3 Spring中事务的原理
6.3.1 事务的设计
事务管理器:
事务管理器(PlatformTransactionManager).
事务的定义:TransactionDefinition
事务的开启 TransactionStatus
核心方法演示:
getTransaction()
方法
/**
* This implementation handles propagation behavior. Delegates to
* {@code doGetTransaction}, {@code isExistingTransaction}
* and {@code doBegin}.
* @see #doGetTransaction
* @see #isExistingTransaction
* @see #doBegin
*/
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
// 如果没有事务定义信息则使用默认的事务管理器定义信息
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 获取事务
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
// 判断当前线程是否存在事务,判断依据为当前线程记录的连接不为空且连接中的transactionActive属性不为空
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// 当前线程已经存在事务
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
// 事务超时设置验证
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
// 如果当前线程不存在事务,但是PropagationBehavior却被声明为PROPAGATION_MANDATORY抛出异常
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED都需要新建事务
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//没有当前事务的话,REQUIRED,REQUIRES_NEW,NESTED挂起的是空事务,然后创建一个新事务
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
// 恢复挂起的事务
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
// 创建一个空的事务
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
关键的方法:doGetTransaction()方法
/**
* 创建一个DataSourceTransactionObject当作事务,设置是否允许保存点,然后获取连接持有器ConnectionHolder
* 里面会存放JDBC的连接,设置给DataSourceTransactionObject,当然第一次是空的
*
* @return
*/
@Override
protected Object doGetTransaction() {
// 创建一个数据源事务对象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
// 是否允许当前事务设置保持点
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/**
* TransactionSynchronizationManager 事务同步管理器对象(该类中都是局部线程变量)
* 用来保存当前事务的信息,我们第一次从这里去线程变量中获取 事务连接持有器对象 通过数据源为key去获取
* 由于第一次进来开始事务 我们的事务同步管理器中没有被存放.所以此时获取出来的conHolder为null
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
// 非新创建连接则写false
txObject.setConnectionHolder(conHolder, false);
// 返回事务对象
return txObject;
}
然后事务管理的代码
/**
* Create a TransactionStatus for an existing transaction.
*/
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
/**
* 判断当前的事务行为是不是PROPAGATION_NEVER的
* 表示为不支持事务,但是当前又存在一个事务,所以抛出异常
*/
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
/**
* 判断当前的事务属性不支持事务,PROPAGATION_NOT_SUPPORTED,所以需要先挂起已经存在的事务
*/
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
// 挂起当前事务
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 创建一个新的非事务状态(保存了上一个存在事务状态的属性)
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
/**
* 当前的事务属性状态是PROPAGATION_REQUIRES_NEW表示需要新开启一个事务状态
*/
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
// 挂起当前事务并返回挂起的资源持有器
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
// 创建一个新的非事务状态(保存了上一个存在事务状态的属性)
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// 嵌套事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 不允许就报异常
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
// 嵌套事务的处理
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
// 如果没有可以使用保存点的方式控制事务回滚,那么在嵌入式事务的建立初始简历保存点
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
// 为事务设置一个回退点
status.createAndHoldSavepoint();
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
// 有些情况是不能使用保存点操作
return startTransaction(definition, transaction, debugEnabled, null);
}
}
// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
"(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
最后来看看 startTransaction() 方法
/**
* Start a new transaction.
*/
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
// 是否需要新同步
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 创建新的事务
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 开启事务和连接
doBegin(transaction, definition);
// 新同步事务的设置,针对于当前线程的设置
prepareSynchronization(status, definition);
return status;
}
doBegin方法开启和连接事务
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 强制转化事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 判断事务对象没有数据库连接持有器
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 通过数据源获取一个数据库连接对象
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
// 把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 标记当前的连接是一个同步事务
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 为当前的事务设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
// 设置先前隔离级别
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// 设置是否只读
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
// 关闭自动提交
if (con.getAutoCommit()) {
//设置需要恢复自动提交
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
// 关闭自动提交
con.setAutoCommit(false);
}
// 判断事务是否需要设置为只读事务
prepareTransactionalConnection(con, definition);
// 标记激活事务
txObject.getConnectionHolder().setTransactionActive(true);
// 设置事务超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
// 绑定我们的数据源和连接到我们的同步管理器上,把数据源作为key,数据库连接作为value 设置到线程变量中
if (txObject.isNewConnectionHolder()) {
// 将当前获取到的连接绑定到当前线程
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
// 释放数据库连接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
在doBegin方法中核心的关闭了自动提交
同时把连接绑定到本地线程中bindResource方法
6.3.2 AOP串联
编程式事务:
@Autowired
private UserDao userDao;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private LogService logService;
@Transactional
public void insertUser(User u) {
// 1、创建事务定义
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 2、根据定义开启事务
TransactionStatus status = txManager.getTransaction(definition);
try {
this.userDao.insert(u);
Log log = new Log(System.currentTimeMillis() + "", System.currentTimeMillis() + "-" + u.getUserName());
// this.doAddUser(u);
this.logService.insertLog(log);
// 3、提交事务
txManager.commit(status);
} catch (Exception e) {
// 4、异常了,回滚事务
txManager.rollback(status);
throw e;
}
}
AOP事务
上面的案例代码我们可以看到在Service中我们通过事务处理的代码实现了事务管理,同时结合我们前面学习的AOP的内容,我们发现我们完全可以把事务的代码抽取出来,然后我们来看看Spring中这块是如何处理的。
我们可以通过Debug的方式看到处理的关键流程 TransactionInterceptor
就是事务处理的 advice
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
进入到invokeWithinTransaction方法中
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
// 获取我们的事务属性源对象
TransactionAttributeSource tas = getTransactionAttributeSource();
// 通过事务属性源对象获取到当前方法的事务属性信息
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 获取我们配置的事务管理器对象
final TransactionManager tm = determineTransactionManager(txAttr);
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new TransactionUsageException(
"Unsupported annotated transaction on suspending function detected: " + method +
". Use TransactionalOperator.transactional extensions instead.");
}
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
if (adapter == null) {
throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
method.getReturnType());
}
return new ReactiveTransactionSupport(adapter);
});
return txSupport.invokeWithinTransaction(
method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// 获取连接点的唯一标识 类名+方法名
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 声明式事务处理
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 创建TransactionInfo
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 执行被增强方法,调用具体的处理逻辑
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清除事务信息,恢复线程私有的老的事务信息
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
//成功后提交,会进行资源储量,连接释放,恢复挂起事务等操作
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// 编程式事务处理
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
}
然后进入到createTransactionIfNecessary方法中
核心的是doBegin方法。完成 自动提交的关闭和 本地线程 对象的存储
TransactionInterceptor
接下来看看TransactionInterceptor是如何注入到容器中的,首先来看看事务的开启
一步步进入
可以看到对应的拦截器的注入
然后可以看到拦截器关联到了Advisor中了
7.Spring中应用了哪些设计模式?
列举几种讲解:
- 单例模式(Singleton Pattern):Spring中的Bean默认是单例的,通过Spring容器管理的Bean都是单例的,保证了对象的唯一性。
- 工厂模式(Factory Pattern):Spring中的Bean工厂(BeanFactory)和应用上下文(ApplicationContext)都是工厂模式的实现,通过工厂模式可以统一管理对象的创建。
- 代理模式(Proxy Pattern):Spring中的AOP(面向切面编程)就是通过代理模式来实现的,通过动态代理可以实现对目标对象的增强。
- 观察者模式(Observer Pattern):Spring中的事件驱动机制就是基于观察者模式实现的,通过ApplicationEvent和ApplicationListener来实现事件的发布和监听。
- 适配器模式(Adapter Pattern):Spring中的适配器模式主要体现在MVC框架中,通过HandlerAdapter来适配不同类型的处理器。
- 模板方法模式(Template Method Pattern):Spring中的JdbcTemplate就是模板方法模式的应用,通过定义好的模板方法来实现数据库操作的基本流程,具体的实现由子类来完成。
- 策略模式(Strategy Pattern):Spring中的事务管理机制就是基于策略模式实现的,通过不同的事务策略来实现不同的事务管理方式。
8. 谈谈你对SpringMVC的理解
这是个相对比较简单的问题。我们可以从这几个角度来回答
8.1 什么是MVC
8.2 SpringMVC原理
SpringMVC是基于Java的开源框架,用于构建Web应用程序。它是Spring框架的一部分,采用了MVC(模型-视图-控制器)架构模式。
核心对象:
- DispatcherServlet:DispatcherServlet是SpringMVC的核心组件,负责接收HTTP请求并将请求分发给相应的控制器处理。它充当了前端控制器的角色,负责协调请求处理过程中的各个组件。
- HandlerMapping:HandlerMapping负责将HTTP请求映射到相应的处理器(Controller)上。它根据请求的URL和其他条件来确定具体的处理器。
- HandlerAdapter:HandlerAdapter负责将请求分发给相应的处理器进行处理,并将处理结果封装成ModelAndView对象返回给DispatcherServlet。不同类型的处理器可能需要不同的HandlerAdapter进行适配。
- HandlerInterceptor:HandlerInterceptor是一个拦截器接口,可以在请求处理的前后执行一些额外的处理逻辑。它可以用来实现权限验证、日志记录等功能。
- ViewResolver:ViewResolver负责根据视图名称解析出具体的视图对象。它可以根据不同的视图解析策略来确定最终的视图对象。
- View:View负责将模型数据渲染成最终的响应内容。它可以是JSP、HTML、JSON等不同类型的视图。
- Model:Model用于封装处理结果数据,供视图层进行渲染。它可以是一个简单的POJO对象,也可以是一个Map或者ModelAndView对象。
9.如果对响应数据做通用处理?
这个问题针对的是SpringMVC控制器处理完业务请求后给前端服务做相关的响应。这块我们可以对 HandlerMethodReturnValueHandler
做相关的真强。
/**
* 对Controller响应请求的数据做装饰增强
*/
@Configuration
public class InitializingAdviceDecorator implements InitializingBean {
private final RequestMappingHandlerAdapter adapter;
public InitializingAdviceDecorator(RequestMappingHandlerAdapter adapter) {
this.adapter = adapter;
}
@Override
public void afterPropertiesSet() {
//获取所有的handler对象
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
//因为上面返回的是unmodifiableList,所以需要新建list处理
assert returnValueHandlers != null;
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);
this.decorateHandlers(handlers); // 对 对应的 handler 做装饰增强
//将增强的返回值回写回去
adapter.setReturnValueHandlers(handlers);
}
/**
* 使用自定义的返回值控制类
*
* @param handlers
*/
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
//找到返回值的handler并将起包装成自定义的handler
ControllerReturnValueHandler decorator = new ControllerReturnValueHandler((RequestResponseBodyMethodProcessor) handler);
int index = handlers.indexOf(handler);
handlers.set(index, decorator);
break;
}
}
}
/**
* 自定义返回值的Handler
* 采用装饰者模式
*/
private static class ControllerReturnValueHandler implements HandlerMethodReturnValueHandler {
//持有一个被装饰者对象
private final HandlerMethodReturnValueHandler handler;
ControllerReturnValueHandler(RequestResponseBodyMethodProcessor handler) {
this.handler = handler;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return true;
}
/**
* 增强被装饰者的功能
*
* @param returnValue 返回值
* @param returnType 返回类型
* @param mavContainer view
* @param webRequest 请求对象
* @throws Exception 抛出异常
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果是下载文件跳过包装
IgnoredResultWrapper ignoredResultWrapper = returnType.getMethodAnnotation(IgnoredResultWrapper.class);
if (ignoredResultWrapper != null) {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return;
}
if (returnValue == null) {
Optional<String> contentType = Optional.of(webRequest)
.map(nativeWebRequest -> ((ServletWebRequest) webRequest))
.map(ServletRequestAttributes::getResponse)
.map(ServletResponse::getContentType);
if (contentType.isPresent() && contentType.get().contains("application/vnd.openxmlformats-officedocument")) {
return;
}
}
//如果已经封装了结构体就直接放行
if (returnValue instanceof ResultWrapper) {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return;
}
//正常返回success
ResultWrapper<Object> success = ResultWrapper.success(returnValue);
handler.handleReturnValue(success, returnType, mavContainer, webRequest);
}
}
}
10.谈谈对SpringBoot的理解
10.1 回答的关键
- 约定优于配置
- 自动扫描
- 本质就是Spring的脚手架
- @EnableAutoConfiguration
- spring.factories
- 条件注解
10.2 源码的梳理
源码梳理从四个方面介绍
- run方法:Spring容器的初始化
- @SpringBootApplication注解:自动扫描、自动装配
- 两者之间是怎么串联的: @Configuration BFPP --> import bean
- 自定义starter
https://www.processon.com/view/link/64c06b8938c1420369f6958a
11.谈谈你对SpringSecurity的理解
11.1 SpringSecurity的介绍
Spring Security是一个基于Spring框架的开源安全框架,它提供了一套全面的安全解决方案,用于保护Java应用程序的安全性。
Spring Security的主要功能包括 身份验证
、授权
、密码加密
、会话管理
和 安全事件处理
等。它可以集成到各种类型的应用程序中,包括Web应用程序、RESTful服务和微服务等。
11.2 SpringSecurity的实现原理
https://www.processon.com/view/link/5f708e967d9c08039fbe2110
12.介绍下JWT和Token的关系
https://jwt.io/
JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519)。它是一种轻量级的安全凭证,以JSON格式存储信息,并使用数字签名或加密进行验证。
Token是一种在身份验证和授权中用于识别用户的凭证。它可以是任何形式的字符串,通常由服务器生成并返回给客户端。Token可以用来验证用户身份、授权用户访问特定资源或服务,并在一定时间内保持有效。
JWT是一种特定类型的Token,它使用JSON格式存储信息,并使用数字签名或加密来验证其完整性和真实性。JWT由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。头部和负载以Base64编码的形式存储,并用点号连接起来,形成JWT的第一部分。签名是通过对头部、负载和密钥进行加密生成的,用于验证JWT的真实性。
Token可以是任何形式的字符串,包括JWT。JWT是一种更安全和可扩展的Token,它可以包含更多的信息,并且通过数字签名或加密来验证其真实性。与传统的Token相比,JWT具有更好的灵活性和安全性,并且可以在不同的应用程序中共享和验证。
13.谈谈Shiro的实现原理
Shiro的认证和授权的实现本质上是通过各种过滤器来实现的。具体如下图:
14.谈谈你对MyBatis的理解
14.1 MyBatis的基本介绍
MyBatis是一种持久层框架的 ORM
框架,用于在Java应用程序中与关系型数据库进行交互。它提供了一种简单且灵活的方式来处理数据库操作,同时也允许开发人员使用原生的SQL语句。
具有如下的相关的特点:
- 易于配置和使用:MyBatis使用XML或注解配置数据库操作,使得配置和使用变得简单而直观。开发人员只需要定义SQL语句和参数映射,MyBatis就能够自动执行这些操作。
- 灵活的SQL编写:相比其他ORM框架,MyBatis更加灵活,允许开发人员直接编写原生SQL语句。这使得开发人员可以更好地优化查询,处理复杂的数据库操作,并且可以使用数据库特定的功能。
- 提供了对象关系映射(ORM)功能:MyBatis可以将查询结果映射到Java对象上,简化了数据的处理和操作。开发人员只需要定义对象与数据库表之间的映射关系,MyBatis就能够自动将查询结果转换为Java对象。
- 支持动态SQL:MyBatis支持动态SQL语句的构建,可以根据不同的条件生成不同的SQL语句。这使得开发人员可以根据实际情况构建灵活的查询条件,提高了查询的效率和灵活性。
MyBatis和JDBC哪个性能效率高?
14.2 MyBaits的架构设计
MyBatis的整体架构非常简洁,分别是:接口层,核心处理层和基础模块:
14.3 和MyBatisPlus的关系
MyBatisPlus是当下持久层中使用频率非常高的一个开源框架。和MyBatis的对比就非常多,这块也可以介绍下:
- MyBatis是一个开源的持久层框架,它提供了简单的SQL映射和灵活的结果集映射功能,可以方便地与各种数据库进行交互。MyBatis通过XML文件或注解方式配置SQL语句和映射关系。
- MyBatis Plus是在MyBatis基础上进行了扩展的框架,它提供了更多的便捷功能和增强特性,使得开发人员能够更快速地进行数据库操作。MyBatis Plus提供了一些常用的CRUD方法,简化了开发过程,并支持更复杂的条件查询和分页功能。
- MyBatis Plus是基于MyBatis的,它使用了MyBatis的核心功能,同时扩展了更多的功能。因此,可以说MyBatis Plus是MyBatis的增强版。
总的来说,MyBatis是一个轻量级的持久层框架,而MyBatis Plus是在MyBatis基础上进行了扩展,提供了更多的便捷功能和增强特性.
15.谈谈MyBatis的启动过程
这个题目的主要目的是考察大家对MyBatis的理解深度,如果单纯的是做 CRUD
的开发,可能就忽略了。首先我们需要介绍清楚MyBatis执行启动的操作代码
@Test
public void start() throws Exception{
// 1. 加载全局配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.获取SqlSessionFactory
// DefaultSqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.获取SqlSession对象 DefaultSqlSession Executor--> SimpleExecutor --> CachingExecutor --> 插件的逻辑植入
SqlSession sqlSession = factory.openSession();
// 4.通过sqlSession的API接口实现数据库操作
List<User> list = sqlSession.selectList("com.bobo.vip.mapper.UserMapper.selectUserById",2);
for (User user : list) {
System.out.println(user);
}
// 关闭会话
sqlSession.close();
}
具体的操作过程如下:
- 加载配置文件:MyBatis的配置文件是一个XML文件,包含了数据库连接信息、映射文件的位置等配置信息。在启动过程中,MyBatis会读取并解析这个配置文件。
- 创建SqlSessionFactory对象:SqlSessionFactory是MyBatis的核心对象,用于创建SqlSession对象。在启动过程中,MyBatis会根据配置文件中的信息,创建一个SqlSessionFactory对象。
- 创建SqlSession对象:SqlSession是MyBatis的会话对象,用于执行数据库操作。在启动过程中,MyBatis会根据SqlSessionFactory对象,创建一个SqlSession对象。
- 加载映射文件:映射文件是MyBatis的另一个重要配置,用于定义SQL语句与Java方法之间的映射关系。在启动过程中,MyBatis会根据配置文件中的信息,加载映射文件。
- 初始化Mapper接口:Mapper接口是用于执行SQL语句的Java接口,在启动过程中,MyBatis会根据映射文件中的信息,动态生成Mapper接口的实现类。
- 完成启动:启动过程完成后,就可以使用SqlSession对象执行数据库操作了。
当然这块的过程相对还是比较简单的。面试官可能会在这个基础上做相关的扩展。可以结合下面的图分析
也就是 SqlSessionFactory
对象的构建和 SqlSession
对象创建的核心过程。已经具体的数据库操作的请求是如何实现的。这块也是面试官比较感兴趣的内容。
- SqlSessionFactory:全局配置文件的加载解析和映射文件的加载解析
- SqlSession:相关的核心Executor和拦截器的实例化
- Executor:处理具体的请求涉及到缓存处理。分页扩展以及Sql解析和参数解析等
16.谈谈MyBatis中的缓存设计
16.1 缓存的作用
MyBatis缓存的作用是提高数据库访问性能。它将查询结果缓存到内存中,当下次有相同的查询请求时,直接从缓存中取出结果,避免了再次访问数据库,从而提高了查询的响应速度。
16.2 一级缓存
一级缓存是SqlSession级别的缓存,它默认是开启的。当同一个SqlSession执行相同的SQL语句时,会先从缓存中查找,如果找到了对应的结果,则直接返回缓存中的结果,而不会再次访问数据库。
要关闭一级缓存:
<setting name="localCacheScope" value="STATEMENT"/>
16.3 二级缓存
二级缓存是Mapper级别的缓存,它默认是关闭的。当不同的SqlSession执行相同的SQL语句时,如果开启了二级缓存,则会先从缓存中查找,如果找到了对应的结果,则直接返回缓存中的结果,而不会再次访问数据库。
要开启二级缓存:
首先是在全局配置文件中设置:
<!-- 控制全局缓存(二级缓存),默认 true-->
<setting name="cacheEnabled" value="true"/>
然后需要在具体的映射文件中设置 cache
标签
<cache />
<!--<cache type="org.mybatis.caches.redis.RedisCache"
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>-->
如果映射文件中的某个方法不想开启缓存可以设置 useCache="false"
处理
当我们开启了二级缓存后。查询操作是先走二级缓存还是先走一级缓存?
16.4 三级缓存
三级缓存是在分布式环境下把存储在内存中的数据持久化到第三方数据源的过程。比如Redis中。这块需要重写 Cache
接口
17.MyBatis中的拦截器的理解
17.1 拦截器的定义
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: https://mybatis.net.cn/configuration.html#plugins
- Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler(getParameterObject, setParameters)
- ResultSetHandler(handleResultSets, handleOutputParameters)
- StatementHandler(prepare, parameterize, batch, update, query)
17.2 拦截器的应用
MyBatis拦截器是MyBatis提供的一种插件机制,可以在SQL执行过程中拦截SQL语句并进行相关操作。
拦截器可以用于实现一些通用的功能,如日志记录、权限校验、性能监控等。它可以拦截SQL的执行、参数的设置、结果的处理等环节。
要实现一个拦截器,需要实现MyBatis提供的Interceptor接口,并重写其中的方法。Interceptor接口中定义了3个方法:
- intercept:拦截方法,用于在SQL执行前后进行一些操作。在该方法中可以通过Invocation.proceed()方法调用下一个拦截器或执行目标方法。
- plugin:用于包装目标对象,返回一个代理对象。可以通过该方法为目标对象生成一个代理对象,以便拦截对目标对象的方法调用。
- setProperties:用于设置拦截器的属性。可以通过该方法获取配置文件中的属性,并进行相应的初始化操作。
拦截器在MyBatis的配置文件中进行配置,可以通过标签将拦截器添加到MyBatis的拦截链中。
使用拦截器可以方便地扩展MyBatis的功能,实现一些通用的需求,并且可以灵活地控制拦截器的顺序。
使用的步骤:
先定义
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
再注册
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
实际的应用:分页,SQL检查。黑白名单。分库分表等
18.SqlSession有数据安全问题?
18.1 SqlSession为什么是数据不安全的?
在MyBatis中,SqlSession是一个线程不安全的对象,主要原因如下:
- SqlSession的底层实现是基于JDBC的Connection对象,而Connection对象是非线程安全的,因此SqlSession也是非线程安全的。
- SqlSession中包含了数据库连接和事务相关的操作,如果多个线程共享同一个SqlSession实例,可能会导致数据的
不一致性
或者事务的混乱
。 - SqlSession中的
缓存机制
也是基于当前线程的,如果多个线程共享同一个SqlSession实例,可能会导致缓存的数据混乱或者不一致。
18.2 如何解决这个问题?
为了保证数据的安全性和一致性,通常建议在每个线程中使用独立的SqlSession实例,可以通过工厂模式创建新的SqlSession对象,或者使用MyBatis提供的线程安全的SqlSessionFactory实例来创建SqlSession。另外,可以使用ThreadLocal来保证每个线程中使用的SqlSession对象是唯一的。
18.3 Spring整合MyBatis的解决方案
https://dpb-bobokaoya-sm.blog.csdn.net/article/details/117415102
在Spring中,可以通过使用 SqlSessionTemplate
来解决SqlSession数据不安全的问题。SqlSessionTemplate
是MyBatis-Spring提供的一个实现了 SqlSession
接口的类,它会自动管理SqlSession的生命周期,并保证每个线程都有自己的SqlSession实例。使用 SqlSessionTemplate
时,只需要将其配置为Spring的Bean,并注入到需要使用SqlSession的地方即可,Spring会自动为每个线程提供一个独立的SqlSession实例。
19.一个xml都会对应一个Mapper接口。这个Mapper接口的工作原理是什么?
我们在定义的时候需要满足相关的规则: User.xml User.java com.msb.entity.User
User proxy = SqlSessin.getMapper(User.class);
- 接口名称和xml文件的名称要一致
- 接口中定义的方法需要在xml有同名的标签id
- xml中的namespace必须是接口的全限定名
在执行的时候我们会调用SqlSession的getMapper方法。通过getMapper方法来获取接口的代理对象。执行相关操作的时候会通过代理对象的invoke 方法来找到对应的SQL执行。并返回结果。
20.上个问题中的方法能重载吗?
User proxy = SqlSessin.getMapper(User.class);
proxy.insertUser(user); --> insert …
不可以。因为根据接口中的方法来获取对应的xml中的标签是通过 全限定名+方法名的方式来获取的。如果重载会找到多个。
21. MyBatis是否支持延迟加载?如果支持原理是什么呢?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。
22.MyBatis中是如何记录JDBC相关的日志的?
日志模块中提供了一个jdbc模块。通过代理的方式记录相关的操作日志信息。
23.MyBatis的数据源模块是怎么设计的?
提供了两种方式:
- 非连接池的方式
- 连接池的方式
24.MyBatis中是如何处理事务的?
- JDBC处理
- Managed:交给当前的WEB容器处理
25.1000W条数据如何高效的插入到数据库中
从文件中读取:从哪来的?
- 估算文件大小:根据每条记录的字段信息。最直观的把这1000w写入文件中查看大小
- 如何批量插入:文件比较大的情况下。需要分批次的处理 insert into()values(),
- 数据完整性:1000W的数据来源我们需要保存每条数据都是完整的
- 数据库是否支持批次数据:mysql(max_allowed_packet)
- 中途出错的问题:如果中途出现错误,比如数据刚好插入到900w的时候,数据库连接失败,这种情况不可能重新来插一遍,所有需要记录每次插入数据的位置,并且需要保证和批次插入的数据在同一个事务中,这样恢复之后可以从记录的位置开始继续插入
26.项目中你们是怎么处理异常的?
在我们的项目中,我们采用以下方式实现异常的统一处理:
- 异常捕获:在代码的关键位置,使用try-catch语句捕获可能发生的异常。
- 异常分类:根据异常的类型进行分类,例如业务异常、系统异常等。
- 统一封装:针对不同类型的异常,我们会封装统一的异常类,例如BusinessException、SystemException等,这些异常类继承自通用的Exception类。
- 异常处理器:在项目中定义一个全局的异常处理器,用于捕获和处理所有未被try-catch语句捕获的异常。
- 异常处理逻辑:在异常处理器中,我们会根据异常的类型进行相应的处理逻辑,例如记录异常日志、返回友好的错误信息等。
- 统一返回结果:无论是业务异常还是系统异常,我们都会将异常信息封装成统一的返回结果对象,用于给前端或其他调用方返回异常信息。
- 异常信息国际化:为了支持多语言环境,我们会将异常信息进行国际化处理,根据请求的语言环境返回相应的异常信息。
通过以上的方式,我们能够实现异常的统一处理,提高代码的可维护性和可读性,并且能够更好地进行日志记录和错误信息返回。