透彻掌握Spring整合MyBatis的原理彻底征服面试官,建议收藏

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

LOGGER.debug(() -> {

return “Registered plugin: '” + plugin + “'”;

});

});

}

if (StringUtils.hasLength(this.typeHandlersPackage)) {

var24 = this.scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter((clazz) -> {

return !clazz.isAnonymousClass();

}).filter((clazz) -> {

return !clazz.isInterface();

}).filter((clazz) -> {

return !Modifier.isAbstract(clazz.getModifiers());

});

TypeHandlerRegistry var25 = targetConfiguration.getTypeHandlerRegistry();

Objects.requireNonNull(var25);

var24.forEach(var25::register);

}

if (!ObjectUtils.isEmpty(this.typeHandlers)) {

Stream.of(this.typeHandlers).forEach((typeHandler) -> {

targetConfiguration.getTypeHandlerRegistry().register(typeHandler);

LOGGER.debug(() -> {

return “Registered type handler: '” + typeHandler + “'”;

});

});

}

if (!ObjectUtils.isEmpty(this.scriptingLanguageDrivers)) {

Stream.of(this.scriptingLanguageDrivers).forEach((languageDriver) -> {

targetConfiguration.getLanguageRegistry().register(languageDriver);

LOGGER.debug(() -> {

return “Registered scripting language driver: '” + languageDriver + “'”;

});

});

}

var10000 = Optional.ofNullable(this.defaultScriptingLanguageDriver);

Objects.requireNonNull(targetConfiguration);

var10000.ifPresent(targetConfiguration::setDefaultScriptingLanguage);

if (this.databaseIdProvider != null) {

try {

targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));

} catch (SQLException var23) {

throw new NestedIOException(“Failed getting a databaseId”, var23);

}

}

var10000 = Optional.ofNullable(this.cache);

Objects.requireNonNull(targetConfiguration);

var10000.ifPresent(targetConfiguration::addCache); // 如果cache不为空就把cache 添加到 configuration对象中

if (xmlConfigBuilder != null) {

try {

xmlConfigBuilder.parse(); // 解析全局配置文件

LOGGER.debug(() -> {

return “Parsed configuration file: '” + this.configLocation + “'”;

});

} catch (Exception var21) {

throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);

} finally {

ErrorContext.instance().reset();

}

}

targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));

if (this.mapperLocations != null) {

if (this.mapperLocations.length == 0) {

LOGGER.warn(() -> {

return “Property ‘mapperLocations’ was specified but matching resources are not found.”;

});

} else {

Resource[] var3 = this.mapperLocations;

int var4 = var3.length;

for(int var5 = 0; var5 < var4; ++var5) {

Resource mapperLocation = var3[var5];

if (mapperLocation != null) {

try {

//创建了一个用来解析Mapper.xml的XMLMapperBuilder,调用了它的parse()方法。这个步骤我们之前了解过了,

//主要做了两件事情,一个是把增删改查标签注册成MappedStatement对象。

// 第二个是把接口和对应的MapperProxyFactory工厂类注册到MapperRegistry中

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

xmlMapperBuilder.parse();

} catch (Exception var19) {

throw new NestedIOException(“Failed to parse mapping resource: '” + mapperLocation + “'”, var19);

} finally {

ErrorContext.instance().reset();

}

LOGGER.debug(() -> {

return “Parsed mapper file: '” + mapperLocation + “'”;

});

}

}

}

} else {

LOGGER.debug(() -> {

return “Property ‘mapperLocations’ was not specified.”;

});

}

// 最后调用sqlSessionFactoryBuilder.build()返回了一个DefaultSqlSessionFactory。

return this.sqlSessionFactoryBuilder.build(targetConfiguration);

}

在afterPropertiesSet方法中完成了SqlSessionFactory对象的创建,已经相关配置文件和映射文件的解析操作。

方法小结一下:通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面有一个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用。Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。

1.2 getObject

另外SqlSessionFactoryBean实现了FactoryBean接口。

FactoryBean的作用是让用户可以自定义实例化Bean的逻辑。如果从BeanFactory中根据Bean的ID获取一个Bean,它获取的其实是FactoryBean的getObject()返回的对象。

也就是说,我们获取SqlSessionFactoryBean的时候,就会调用它的getObject()方法。

public SqlSessionFactory getObject() throws Exception {

if (this.sqlSessionFactory == null) {

this.afterPropertiesSet();

}

return this.sqlSessionFactory;

}

getObject方法中的逻辑就非常简单,返回SqlSessionFactory对象,如果SqlSessionFactory对象为空的话就又调用一次afterPropertiesSet来解析和创建一次。

1.3 onApplicationEvent

实现ApplicationListener接口让SqlSessionFactoryBean有能力监控应用发出的一些事件通知。比如这里监听了ContextRefreshedEvent(上下文刷新事件),会在Spring容器加载完之后执行。这里做的事情是检查ms是否加载完毕。

public void onApplicationEvent(ApplicationEvent event) {

if (this.failFast && event instanceof ContextRefreshedEvent) {

this.sqlSessionFactory.getConfiguration().getMappedStatementNames();

}

}

2 SqlSession


2.1 DefaultSqlSession的问题

在前面介绍MyBatis的使用的时候,通过SqlSessionFactory的open方法获取的是DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是线程不安全的。所以直接使用会存在数据安全问题,针对这个问题的,在整合的MyBatis-Spring的插件包中给我们提供了一个对应的工具SqlSessionTemplate。

img

https://mybatis.org/mybatis-3/zh/getting-started.html

img

也就是在我们使用SqlSession的时候都需要使用try catch 块来处理

try (SqlSession session = sqlSessionFactory.openSession()) {

// 你的应用逻辑代码

}

// 或者

SqlSession session = null;

try {

session = sqlSessionFactory.openSession();

// 你的应用逻辑代码

}finally{

session.close();

}

在整合Spring中通过提供的SqlSessionTemplate来简化了操作,提供了安全处理。

2.2 SqlSessionTemplate

在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是单例的)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYxyfLIh-1622729398252)(https://img2.tuicool.com/rQfQFvV.png!web)]

SqlSessionTemplate虽然跟DefaultSqlSession一样定义了操作数据的selectOne()、selectList()、insert()、update()、delete()等所有方法,但是没有自己的实现,全部调用了一个代理对象的方法。

img

那么SqlSessionProxy是怎么来的呢?在SqlSessionTemplate的构造方法中有答案

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,

PersistenceExceptionTranslator exceptionTranslator) {

notNull(sqlSessionFactory, “Property ‘sqlSessionFactory’ is required”);

notNull(executorType, “Property ‘executorType’ is required”);

this.sqlSessionFactory = sqlSessionFactory;

this.executorType = executorType;

this.exceptionTranslator = exceptionTranslator;

// 创建了一个 SqlSession 接口的代理对象, 调用SqlSessionTemplate中的 selectOne() 方法,其实就是调用

// SqlSessionProxy的 selectOne() 方法,然后执行的是 SqlSessionInterceptor里面的 invoke方法

this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),

new Class[] {

SqlSession.class }, new SqlSessionInterceptor());

}

通过上面的介绍那么我们应该进入到 SqlSessionInterceptor 的 invoke 方法中。

img

上面的代码虽然看着比较复杂,但是本质上就是下面的操作

SqlSession session = null;

try {

session = sqlSessionFactory.openSession();

// 你的应用逻辑代码

}finally{

session.close();

}

getSqlSession方法中的关键代码:

img

执行流程

img

总结一下:因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建一个DefaultSqlSession实例,再调用被代理对象的相应方法。

MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成到Spring要保证线程安全,就用SqlSessionManager。

2.3 SqlSessionDaoSupport

通过上面的介绍我们清楚了在Spring项目中我们应该通过SqlSessionTemplate来执行数据库操作,那么我们就应该首先将SqlSessionTemplate添加到IoC容器中,然后我们在Dao通过@Autowired来获取具体步骤参考官网:http://mybatis.org/spring/zh/sqlsession.html

img

然后我们可以看看SqlSessionDaoSupport中的代码

img

如此一来在Dao层我们就只需要继承 SqlSessionDaoSupport就可以通过getSqlSession方法来直接操作了。

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSessionTemplate sqlSessionTemplate;

public SqlSession getSqlSession() {

return this.sqlSessionTemplate;

}

// 其他代码省略

也就是说我们让DAO层(实现类)继承抽象类SqlSessionDaoSupport,就自动拥有了getSqlSession()方法。调用getSqlSession()就能拿到共享的SqlSessionTemplate。

在DAO层执行SQL格式如下:

getSqlSession().selectOne(statement, parameter);

getSqlSession().insert(statement);

getSqlSession().update(statement);

getSqlSession().delete(statement);

还是不够简洁。为了减少重复的代码,我们通常不会让我们的实现类直接去继承SqlSessionDaoSupport,而是先创建一个BaseDao继承SqlSessionDaoSupport。在BaseDao里面封装对数据库的操作,包括selectOne()、selectList()、insert()、delete()这些方法,子类就可以直接调用。

public class BaseDao extends SqlSessionDaoSupport {

//使用sqlSessionFactory

@Autowired

private SqlSessionFactory sqlSessionFactory;

@Autowired

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

super.setSqlSessionFactory(sqlSessionFactory);

}

public Object selectOne(String statement, Object parameter) {

return getSqlSession().selectOne(statement, parameter);

}

// 后面省略

然后让我们的DAO层实现类继承BaseDao并且实现我们的Mapper接口。实现类需要加上@Repository的注解。

在实现类的方法里面,我们可以直接调用父类(BaseDao)封装的selectOne()方法,那么它最终会调用sqlSessionTemplate的selectOne()方法。

@Repository

public class EmployeeDaoImpl extends BaseDao implements EmployeeMapper {

@Override

public Employee selectByPrimaryKey(Integer empId) {

Employee emp = (Employee) this.selectOne(“com.gupaoedu.crud.dao.EmployeeMapper.selectByPrimaryKey”,empId);

return emp;

}

// 后面省略

然后在需要使用的地方,比如Service层,注入我们的实现类,调用实现类的方法就行了。我们这里直接在单元测试类DaoSupportTest.java里面注入:

@Autowired

EmployeeDaoImpl employeeDao;

@Test

public void EmployeeDaoSupportTest() {

System.out.println(employeeDao.selectByPrimaryKey(1));

}

最终会调用到DefaultSqlSession的方法。

2.4 MapperScannerConfigurer

上面我们介绍了SqlSessionTemplate和SqlSessionDaoSupport,也清楚了他们的作用,但是我们在实际开发的时候,还是能够直接获取到 Mapper 的代理对象,并没有创建Mapper的实现类,这个到底是怎么实现的呢?这个我们就要注意在整合MyBatis的配置文件中除了SqlSessionFactoryBean以外我们还设置了一个MapperScannerConfigurer,我们来分析下这个类

首先是MapperScannerConfigurer的继承结构img  MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor的子类,里面有一个postProcessBeanDefinitionRegistry()方法。

实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义。Spring创建Bean之前会调用这个方法。

@Override

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
5c60a180b68ad8dbab8.png)  MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor的子类,里面有一个postProcessBeanDefinitionRegistry()方法。

实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义。Spring创建Bean之前会调用这个方法。

@Override

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-il9p70Ac-1713366260379)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值