MyBatis【进阶详解】
发布日期:2021-07-27 04:56:16 浏览次数:5 分类:技术文章

本文共 15605 字,大约阅读时间需要 52 分钟。

在这里插入图片描述

1.简介

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录

2. 背景介绍

MyBatis是支持普通SQL查询,和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手共设置以及结果集的检索,MyBatis使用简单的XML或者注解用于配置和原始映射,将接口和Java的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。

每个MyBatis应用程序主要利用SqlSessionFactory实例操作数据库,而SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类实例获得。

用xml文件构建SqlSessionFactory实例是非常简单的事情,推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或者f://开头的url创建实例。MyBatis有一个实用类(Resources),它有很多方法,可以方便地从类路径以及其他位置加载资源。

3.框架特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
  • 解除sql与程序代码的耦合:通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql

4. 功能架构

Mybatis的功能架构分为三层:

  • API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

    在这里插入图片描述

5. 体系结构

在这里插入图片描述

6. 使用传统的MyBatis API

​ 通过调用MyBatis中SqlSession对象的方法从而达到与数据库交互的方式,有一些类似DBUtils的操作!

在这里插入图片描述

...
delete from tt2 where id=#{id}
...

7.MyBatis类型解析

第一:mybatis自动处理java.util.Date

第二:mybatis自动识别1:0为true/false。数据库中存1/0,java中直接收true/false

第三:parameterType 和 resultType 解析

<--    parameterType类型:jdk8中基本类型+String :double,float,int,short,boolean,long,date,string -->

原理: 1>会类型字符转小写,TypeAliasRegistry初始化构造方法中注册了很多类型,先从中检查

2>如果如上没有,则用Resources.classForName(string)通过反射得到类型(核心原理是 Class.forName()类加载)

8.MyBatis事务控制

8.1 起点 :获取SqlSession对象

// ================ 起点 ===============sqlSessionFactory.openSession();

8.2 DefaultSqlSessionFactory

// 类:DefaultSqlSessionFactory = SqlSessionFactory的实际类型public SqlSession openSession() {
// ================ openSession()方法实现 ================ // Executor 隔离级别 自动提交 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null; try {
// 获得数据库相关配置 final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // ============== 获得事务对象,(此处需要重点关注,继续跟进 ================ tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 执行器 final Executor executor = configuration.newExecutor(tx, execType); // 返回构建好的SqlSession,实际类型为DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally {
ErrorContext.instance().reset(); } }

8.3 JdbcTransactionFactory

//类:JdbcTransactionFactorypublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// =============== 构建事务对象,实际类型:JdbcTransaction ============== return new JdbcTransaction(ds, level, autoCommit);}

8.4 JdbcTransaction

//类:JdbcTransaction,事务对象// 构造方法:以上传来的各项参数:【数据源+事务隔离级别+是否自动提交事务】public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
// 在自己的属性中 保持了如上3项参数 dataSource = ds; level = desiredLevel; autoCommmit = desiredAutoCommit;}// 事务对象中,会打开一个 Connection,其中设置了如上的参数protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection"); } // 1.通过数据源,获得一个Connection对象 connection = dataSource.getConnection(); if (level != null) {
// 2.设置事务隔离级别 connection.setTransactionIsolation(level.getLevel()); } // 3.设置事务的自动提交属性,默认为false,即mybatis中默认是不自动提交事务的 setDesiredAutoCommit(autoCommmit);}// 事务对象中设置 自动提交属性protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
.... connection.setAutoCommit(desiredAutoCommit); ....}// 事务对象中 提交事务public void commit() throws SQLException {
.... connection.commit(); .... }//事务对象中 回滚事务public void rollback() throws SQLException {
... connection.rollback(); ....}//事务对象中 关闭资源public void close() throws SQLException {
... connection.close();}

综上,即,在创建sqlSession时,便会创建一个Transaction对象。

Transaction对象负责创建Connection对象,并借之实现事务管理:事务属性设置(隔离级别,自动提交)

事务行为控制(提交,回滚)

9.开发规范

  • mapper接口的全限定名要和mapper映射文件的namespace的值相同。
  • mapper接口的方法名称要和mapper映射文件中的statement的id相同;
  • mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致。
  • mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;

10. 方法参数细节

如果DAO方法中只有一个参数,则在mapper文件中【#{随意}】:如下的#{abc},#{def},#{hig}如是。

public List
xx(Integer id);public List
xx(String name);public List
xx(Date createTime);

当方法中有多个参数时,不能再向如上那样随意,且也不能如常规课程中直接使用参数名;有两种解决方案:

1> 使用#{arg0} #{arg1} #{arg2}

2> 使用 @Param 注解

public List
test1(@Param("id")Integer id, @Param("name") String name,@Param("gender") Boolean gender);public List
test2(Integer id, String name,Boolean gender);

11. # 和 $ 区分

//如果用$,则必须用 @Param注解,否则${name}会认为是要从参数中取名为name的属性public List
test3(@Param("name") String a);

必须使用$场景:

userDAO.test3("desc");userDAO.test3("asc");userDAO.test4("t_user");userDAO.test4("t_admin");

12. Statement 选型

发生在初始加载过程中,每个MappedStatement中存储自己的 StatementType

MyBatis中,除非明确设置 statementType="STATEMENT" 否则默认都是用 PreparedStatement

可参见 类:org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode()

{
//在mapper标签中获取’statementType‘属性值,如果没有设置则默认取’StatementType.PREPARED.toString()‘ StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));}

真正执行一个MappedStatement时:

SimpleExecutor中:   //doQuery() 或 doUpdate() 方法中   public 
List
doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null; try {
Configuration configuration = ms.getConfiguration(); // 通过当前MappedStatement=ms 中的StatementType属性,创建一个StatementHandler // 会创建一个RoutingStatementHandler,其构造方法中会根据ms的StatementType构建: // SimpleStatementHandler 或 PreparedStatementHandler.他们各自负责创建一种Statement // 如下hanlder属性中存储的是一个RoutingStatementHandler对象,可实际工作时,是其内部的 // SimpleStatementHandler 或 PreparedStatementHandler完成执行。 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 此方法中的handler会负责创建Statement,即,决定用哪种Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.
query(stmt, resultHandler); }....

13. 关联关系映射多种方式

//namespace = com.zhj.dao.UserDAO

14. 细节参数

1. 连接池

连接池
在这里插入图片描述
增设参数

2. 延迟加载细节

延迟加载_Mapper映射
[外链图片转存失败(img-q4daYOBT-1569149138740)(mdpic/mapper_懒加载细节.jpg)]

15. 缓存细节

缓存必须要序列化pojo对象

1. 一级缓存

一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。

一级缓存
[外链图片转存失败(img-Clvhuefj-1569149138740)(mdpic/一级缓存.jpg)]

2. 二级缓存开关

二级缓存的范围是 mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓

存数据结构,结构是 map。mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor
其实是 Executor 的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存
在,不存在则查询数据库。

二级缓存开关 和 存储位置
在这里插入图片描述

3. 二级缓存存储位置

二级缓存,存于SqlSessionFactory中
[外链图片转存失败(img-iLmG6OPk-1569149138741)(mdpic/二级缓存细节.jpg)]

4.二级缓存存储结构

二级缓存存储结构 ,以mapper(dao).xml文件为一个大map,对应的value是一个缓存,缓存中还有小map,存储的key是sql语句,存储的value是数据
[外链图片转存失败(img-8P62t2cN-1569149138741)(mdpic/二级缓存存储结构.jpg)]

5. 二级缓存生效时间

二级缓存需要提交事务,才会生效。

原因:用于全局共享的数据,还是在操作数据的事务结束后再生效为好。

实现:见下图:

二级缓存细节
在这里插入图片描述

注意:

查询时,数据存入一级缓存中。

如果sqlsession在commit或close后,数据存入进入二级缓存。

如果sqlsession执行rollback,则数据不会进入二级缓存。

6. 二级缓存-cache-ref

和关系属性相关

注意如果<collection>中没有使用select关联查询,则不存在此问题。

UserDAO userDAO = sqlSession1.getMapper(UserDao.class);userDAO.queryOne(1);//User 和 关系属性Order 都被缓存sqlSession.commit();OrderDAO orderDAO = sqlSession2.getMapper(OrderDAO.lass);orderDAO.queryOrderOfUser(1); //有二级缓存数据可用 (OrderDAO中必须有 
)
UserDAO userDAO = sqlSession1.getMapper(UserDao.class);userDAO.queryOne(1);//数据改动,此时会清空User缓存,并清空Order缓存(因为order中是 
,如果是
则此处只会清空User缓存)//除非使用的是连表查询,比如inner join,详细可以参考《MyBatis高级应用》userDAO.insertUser(new User(null,"new_user",true,new Date()));sqlSession.commit();OrderDAO orderDAO = sqlSession2.getMapper(OrderDAO.lass);orderDAO.queryOrderOfUser(1); //重新查询数据库,
会由于A的改动而被清空缓存

16. idea设置sql方言

设置方言后,mapper文件中的sql语句就不会报黄啦

[外链图片转存失败(img-1IX5lPYA-1569149138108)(mdpic/dialect.jpg)]

17. bind

<bind name="name_pattern" value="'%'+name+'%'"/> 创建一个变量,并绑定在当前上下文

18. Foreach-Map(了解)

public int insertUsers(Map
> users);
insert into t_user2 (name,gender,regist_time) values
(#{user.name},#{user.gender},#{user.registTime,jdbcType=DATE})
HashMap
> userMaps = new HashMap<>();userMaps.put("users",users);userDAO.insertUsers(userMaps);

19. 配置文件加载

SqlSessionFactoryBuilder().build(reader);

​ |-- XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);//封装解析器

​ return build(parser.parse());//解析配置,返回一个SqlSessionFactory.则SSF是由mybatis的全部配置支撑的。

​ |-- XMLConfigBuilder .parse()//解析配置文件

​ |-- XMLConfigBuilder .parseConfiguration(parser.evalNode("/configuration"));

​ //解析配置,从configuration标签开始,解析整个配置文件

​ //其中加载配置个各个节点都是用了单独的方法

​ //加载properties标签,加载typealias标签等,可以在每个加载方法中看到标签解析的细节。

​ //比如properties标签加载时,会解析 resource属性 或 url属性,以及会如何判断用户用的是哪个。

20. 映射文件加载

XMLConfigBuilder .parseConfiguration(parser.evalNode("/configuration"));//其中有一项是加载mapper文件

​ |-- XMLConfigBuilder .mapperElement(root.evalNode(“mappers”)); //加载mapper文件

​ |-- XMLMapperBuilder.

buildStatementFromContext(context.evalNodes("select|insert|update|delete"))

​ //其中会加载所有的 select,update,delete,insert标签, 返回List<XNode>,然后遍历该List,执行下一步

​ |-- XMLStatementBuilder.parseStatementNode()//每遍历一个XNode执行一次该方法,解析出一个MappedStatement

​ //其中会解析上一步加载的每个标签(XNode),和其中的属性(resultTeype,id,paremeterType…),

​ //其中还有很重要的一步,是决定当前mapstatement使用的Statement类型。

​ //解析完毕后,有一步:

​ |-- parseStatementNode()#builderAssistant.addMappedStatement(XNode中的所有属性 id,resultTpe,...)

​ |-- MapperBuilderAssistant.addMappedStatement()

​ |-- 其中会根据传入的(id,resultType,…)构建一个MappedStatement.Builder statementBuilder然后:

​ |-- statementBuilder.build() 返回一个MappedStatement

​ |-- configuration.addMappedStatement(statement);//将MappedStatement 存入Configuration对象

21. 动态sql解析

在映射文件加载过程中,还会有sql语句的解析,主要是针对,动态sql的解析

|-- XMLStatementBuilder.parseStatementNode() 中:

​ |-- langDriver.createSqlSource(configuration, context, parameterTypeClass);

​ |-- 如上方法实现中:

​ XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);

​ return builder.parseScriptNode();

​ |-- XMLScriptBuilder.parseScriptNode()

​ |-- 如上方法中:

​ List contents = parseDynamicTags(context);//解析动态sql

​ if (isDynamic) {// 如果有 $ 或 动态标签

sqlSource = new DynamicSqlSource(configuration, rootSqlNode);  						   } else {  										sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); //其中解析 #{ }  						   }

​ //得到一个SqlSource,其中存储了 : sql语句;sql中的参数名(#{xx})

​ //还持有当前SqlSessionFactory中的configuration对象( 缓存信息,延迟加载信息等配置都可以在其中获)

【parseDynamicTags方法中调用nodeHandlers()//获取所有的动态sql标签处理的 handler,每种动态标签都对应一种handler

handler会解析动态标签】

22. SqlSession执行

UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

|-- userDAO.queryOne(1); //mapper类型:MapperProxy,基于jdk动态代理构建

​ |-- MapperProxy.invoke

​ //其中获得的MapperMethod对象( 其中封装了:当前方法接口,当前方法对象, Configuration)

​ |-MapperMethod.execute()

​ //其中会真正执行对应方法,执行的真正实现者是sqlSession.xxxx();[insert,udate,delete,selectOne,…]

​ |- 在传统API中调用Excutor(CachingExcutor=>SimpleExcutor)

​ |- PrepareStatementHandler => 获取PrepareStatement.execute()

23. PageHelper

1. 使用过程

com.github.pagehelper
pagehelper
RELEASE
com.github.pagehelper
pagehelper
4.2.1
com.github.pagehelper
pagehelper
5.1.6
//使用:PageHelper.ofList
(list,navigatePages);//这里可以设置步距PageHelper.offsetPage(2,3);//这里传入的是limit和偏移量offsetPageHelper.startPage(2,3);// 第2页,每页3条数据PageHelper.orderBy("regist_time");//可以选择设置排序,如果是按照某个id升序排序可以写【id asc】List
users = mapper.queryAllUsers();//PageHelper后的第一个查询语句,会被PageHelp增强处理(可观测mysql日志)for (User user : users) {
// users中已经是分页数据 System.out.println(user);}//包装一个PageInfo,其中会持有所有分页会用到的信息:当前页号,每页多少条,共多少页,是否为第一页/最后一页,是否有下一页等。PageInfo
pageInfo=new PageInfo
(users);
PageInfo对象 概览
[外链图片转存失败(img-Ozi7ThAx-1569149138742)(mdpic/pageInfo.jpg)]
注意:如果是多表查询,则会有如下效果:select t_user.id,name,gender,regist_time,            t_order.id orderId,price,note,create_time        from t_user JOIN t_order                ON t_user.id = t_order.user_id LIMIT 2, 2 #是对大表做了分页,此时数据可能不完整,比如用户有10个订单,却只能查到2个,或部分。

2. 重要提示

PageHelper.startPage方法重要提示

只有紧跟在PageHelper.startPage方法后的第一个Mybatis的**查询(Select)**方法会被分页,查询语句和设置分页调节语句顺序不能反。

请不要配置多个分页插件

请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xmlSpring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!

分页插件不支持带有for update语句的分页

对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

分页插件不支持嵌套结果映射

由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。

转载地址:https://blog.csdn.net/qq_45337431/article/details/101167433 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:面试题集锦(七)
下一篇:Mybatis-高级应用

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年09月18日 01时29分14秒