【Mybatis】基础总结:缓存机制、如何实现分页
【Mybatis】基础总结缓存机制、如何实现分页1、MyBatis与Hibernate有何不同2、Mybatis的优点有哪些3、Mybatis是如何实现字段映射的4、#和\$的区别是什么什么情况必须用\$5、Mybatis的缓存机制5.1 一级缓存5.2 二级缓存6、使用MyBatis如何实现分页6.1 在SQL中添加limit语句6.2 基于PageHelper分页插件实现分页6.3 基于RowBounds实现分页6.4 基于MyBatis-Plus实现分页7、MyBatis-Plus有什么用1、MyBatis与Hibernate有何不同Hibernate和Mybatis都是ORM框架都支持JDBC和JTA事务处理它们创建的目的都是为了简化Java原生程序操作数据库的步骤。增加开发者的开发效率。不同点有以下几点Hibernate是全自动的Mybatis是半自动的。在Hibernate当中开发者只需要定义好数据库的表字段和Java DO的映射关系和规则即可Hibernate会开放出来接口自动去处理数据库表的CURD并按照规定好的规则映射到DO对象中这个过程中操作者是完全不需要感知Sql逻辑的。但是在Mybatis中则完全不是这样Mybatis不会帮助开发者编写sql逻辑Mybatis只会按照定义好的规则将数据库字段映射到Java的DO中但是具体的sql逻辑还是需要开发者自己编写的。正是因为Mybatis需要自己编写Sql逻辑这是一个包袱因为开发者需要根据不同的DB选择不同的SQL语句DB移植性不高而且正是因为要自己写SQL项目初期的开发工作量要比Hibernate大一点。但是这也是优点通过Mybatis开发者就可以定制sql譬如多表joinsql优化等操作是Hibernate不具备的Hibernate的缓存系统要优于Mybatis如果二级缓存出现脏数据Hibernate会及时报错但是Mybatis就需要开发者自己去感知总的来说MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。对于性能要求不太苛刻的系统比如管理系统、ERP 等推荐使用 Hibernate而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。2、Mybatis的优点有哪些先看一段用JDBC和Mybatis写的代码publicvoidtestJdbc(){StringurldbLink;try(ConnectionconnDriverManager.getConnection(url,root,password)){// 加载MySQL驱动Class.forName(com.mysql.cj.jdbc.Driver);intauthor1;Stringdate2018.06.10;StringsqlSELECT id, title, content, create_time FROM article WHERE author_id author AND create_time date;Statementstmtconn.createStatement();ResultSetrsstmt.executeQuery(sql);ListArticlearticlesnewArrayList(rs.getRow());while(rs.next()){ArticlearticlenewArticle();article.setId(rs.getInt(id));article.setTitle(rs.getString(title));article.setContent(rs.getString(content));article.setCreateTime(rs.getDate(create_time));articles.add(article);}System.out.println(Query SQL sql);System.out.println(Query Result: );articles.forEach(System.out::println);}catch(ClassNotFoundException|SQLExceptione){e.printStackTrace();}}publicvoidtestMyBatis()throwsIOException{Stringresourcemybatis-config.xml;InputStreaminputStreamResources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder().build(inputStream);inputStream.close();try(SqlSessionsessionsqlSessionFactory.openSession()){ArticleDaoarticleDaosession.getMapper(ArticleDao.class);ListArticlearticlesarticleDao.findByAuthorAndCreateTime(1,2018-06-10);System.out.println(articles);}}intefaceArticleDao{Query({SELECT id, title, content, create_time FROM article WHERE author_id #{id} AND create_time #{time}})ListArticlefindByAuthorAndCreateTime(intid,Stringtime);}使用Mybatis的代码结构更清晰代码量也比较少将数据库的操作逻辑和业务操作解耦合使得开发人员可以专心业务逻辑的处理。开发人员只写Sql就可以访问数据库不需要关心各种数据库连接等额外的操作。各种Connection和Statement都交给了Mybatis来管理可以将数据库表的字段按照业务规则直接映射到DO层不用再像JDBC一样需要业务代码来转换3、Mybatis是如何实现字段映射的Mybatis实现字段映射的代码主要在ResultSetHandler类中。该类是Mybatis查询结果集处理的核心类负责将JDBC ResultSet对象转换为Java对象并进行字段映射。Mybatis实现字段映射的原理可以简单描述为以下几个步骤Mybatis通过JDBC API向数据库发送SQL查询语句并获得查询结果集。查询结果集中的所有数据封装到一个ResultSet对象中Mybatis遍历ResultSet对象中的数据。对于每一行数据Mybatis根据Java对象属性名和查询结果集中的列名进行匹配。如果匹配成功则将查询结果集中的该列数据映射到Java对象的相应属性中。如果Java对象属性名和查询结果集中的列名不完全一致Mybatis可以通过在SQL语句中使用“AS”关键字或使用别名来修改列名或者使用ResultMap来定义Java对象属性和列的映射关系。对于一些复杂的映射关系例如日期格式的转换、枚举类型的转换等可以通过自定义TypeHandler来实现。Mybatis将自定义TypeHandler注册到映射配置中根据Java对象属性类型和查询结果集中的列类型进行转换。最终Mybatis将所有映射成功的Java对象封装成一个List集合返回给用户使用。Mybatis通过查询结果集中的列名和Java对象属性名之间的映射关系将查询结果集中的数据映射到Java对象中。Mybatis提供了多种灵活的映射方式可以满足不同场景下的需求。4、#和$的区别是什么什么情况必须用$在Mybatis的mapper文件中可以使用#{param}和${param}来作为动态参数的替换。#{}和${}在预编译处理中是不一样的。#{}类似jdbc中的PreparedStatement对于传入的参数在预处理阶段会使用?代替可以有效的避免SQL注入。SELECT*FROMusersWHEREid#{userId}这里的#{userId} 会被替换为 ?并且 userId 的值在执行时会被安全地设置为参数值。使用 $ 传递参数时MyBatis 会将其视为字面量并在构建 SQL 语句时直接替换成参数的实际值。这意味着参数值会直接拼接到 SQL 语句中。SELECT*FROM${tableName}由于 $ 导致的是直接替换如果参数内容是用户输入这可能导致 SQL 注入的风险因为恶意的输入可以被拼接成一部分 SQL 语句执行。所以我们在Mybatis中能使用#{}的地方应尽量使用#{}但是有一些情况是必须要用${}的比如我们要把他用在order by、group by 等语句后面的时候。order by ${sortParam}${sortType}order by id desc;5、Mybatis的缓存机制Mybatis的缓存机制有两种一级缓存和二级缓存。5.1 一级缓存一级缓存也叫 本地缓存默认情况下开启的缓存SqlSession 级别的缓存。在同一个会话中Mybatis会将执行过的SQL语句的结果缓存到内存中下次再执行相同的SQL语句时会先查看缓存中是否存在该结果如果存在则直接返回缓存中的结果不再执行SQL语句。一级缓存是默认开启的可以通过在Mybatis的配置文件中设置禁用或刷新缓存来控制缓存的使用。工作流程如下对于一级缓存有两点需要注意的是MyBatis一级缓存内部设计简单只是一个没有容量限定的HashMap在缓存的功能性上有所欠缺。MyBatis的一级缓存最大范围是SqlSession内部有多个SqlSession或者分布式的环境下数据库写操作会引起脏数据换句话说当一个SqlSession查询并缓存结果后另一个SqlSession更新了该数据其他缓存结果的SqlSession是看不到更新后的数据的。一级缓存下多个会话存在时已缓存数据不生效举例TestpublicvoidtestQueryUserById(){SqlSessionsqlSession1MybatisUtil.getSqlSession();UserDaomapper1sqlSession1.getMapper(UserDao.class);SqlSessionsqlSession2MybatisUtil.getSqlSession();UserDaomapper2sqlSession2.getMapper(UserDao.class);Useruser1mapper1.queryUserById(1);System.out.println(user1);Useruser2mapper2.queryUserById(1);System.out.println(user2);System.out.println(user1user2);sqlSession1.close();sqlSession2.close();}5.2 二级缓存二级缓存是基于命名空间的缓存它可以跨会话在多个会话之间共享缓存可以减少数据库的访问次数。要使用二级缓存需要在Mybatis的配置文件中配置相应的缓存实现类并在需要使用缓存的Mapper接口上添加CacheNamespace注解。二级缓存的使用需要注意缓存的更新和失效机制以及并发操作的问题。工作流程如下因为二级缓存是基于namespace的所以一般情况下Mybatis的二级缓存是不适合多表查询的情况的。举个例子我们有两个表student和class我们为这两个表创建了两个namespace去对这两个表做相关的操作。同时为了进行多表查询我们在namespacestudent的空间中对student和class两张表进行了关联查询操作sqlA。此时就会在namespacestudent的空间中把sqlA的结果缓存下来如果我们在namespaceclass下更新了class表namespacestudent是不会更新的这就会导致脏数据的产生。平时在工作中直接用Mybatis的二级缓存的场景会比较少。如果要用缓存还不如直接用第三方的缓存至少我们明确地知道这里有个缓存而不是像Mybatis一样可能开发者忽略了这里的缓存导致出了问题不好排查。二级缓存下多个会话存在时已缓存数据可以生效举例TestpublicvoidtestGetUserByPassword(){SqlSessionsqlSessionMybatisUtils.getSqlSession();SqlSessionsqlSession2MybatisUtils.getSqlSession();UserDaomappersqlSession.getMapper(UserDao.class);UserDaomapper2sqlSession.getMapper(UserDao.class);Userusermapper.getUserByPassword(1234567);System.out.println(user);Useruser2mapper2.getUserByPassword(1234567);System.out.println(user2);System.out.println(useruser2);sqlSession.close();sqlSession2.close();}6、使用MyBatis如何实现分页MyBatis中可以通过两种方式来实现分页基于物理分页和基于逻辑分页。所谓物理分页指的是最终执行的SQL中进行分页即SQL语句中带limit这样SQL语句执行之后返回的内容就是分页后的结果。所谓逻辑分页就是在SQL语句中不进行分页照常全部查询在查询到的结果集中再进行分页。物理分页和逻辑分页工作中推荐使用那种分页呢数据小的话无所谓逻辑分页更简单点数据量大的话一定是物理分页避免查询慢也避免内存被撑爆。6.1 在SQL中添加limit语句selectidgetUsersresultTypeUserselect*fromuserwhereiftestname ! nullandnamelikeCONCAT(%,#{name},%)/if/wherelimit#{offset}, #{limit}/select6.2 基于PageHelper分页插件实现分页PageHelper是MyBatis中提供的分页插件主要是用来做物理分页的。当我们在代码中使用PageHelper.startPage(int pageNum, int pageSize)设置分页参数之后其实PageHelper会把他们存储到ThreadLocal中。PageHelper会在执行器的query方法执行之前会从ThreadLocal中再获取分页参数信息页码和页大小然后执行分页算法计算需要返回的数据块的起始位置和大小。最后PageHelper会通过修改SQL语句的方式在SQL后面动态拼接上limit语句限定查询的数据范围从而实现物理分页的效果。并且在查询结束后再清除ThreadLocal中的分页参数。// Java代码中使用 PageHelperPageHelper.startPage(1,10);ListUseruserListuserMapper.getUsers();PageInfoUserpageInfonew PageInfo(userList);6.3 基于RowBounds实现分页MyBatis的RowBounds是一个用于分页查询的简单POJO类它包含两个属性offset和limit分别表示分页查询的偏移量和每页查询的数据条数。RowBounds是基于逻辑分页的。在查询的时候会先将所有符合条件的记录返回然后再在内存中进行分页分页的方式是根据RowBounds中指定的offset和limit进行数据保留即抛弃掉不需要的数据再返回。intoffset10;// 偏移量intlimit5;// 每页数据条数RowBounds rowBoundsnew RowBounds(offset,limit);ListUseruserListsqlSession.selectList(getUserList,null,rowBounds);6.4 基于MyBatis-Plus实现分页MyBatis-Plus支持分页插件——PaginationInnerInterceptorPaginationInnerInterceptor采用的是物理分页方式物理分页是在数据库中进行分页即直接在SQL语句中加入LIMIT语句只查询所需的部分数据。PaginationInnerInterceptor这个分页插件就会自动拦截所有的SQL查询请求计算分页查询的起始位置和记录数并在SQL语句中加入LIMIT语句。AutowiredprivateUserMapperuserMapper;publicIPageUserselectUserPage(intcurrentPage,intpageSize){PageUserpagenewPage(currentPage,pageSize);IPageUseruserPageuserMapper.selectPage(page,null);returnuserPage;}7、MyBatis-Plus有什么用MyBatis-Plus是一个增强的MyBatis框架提供了许多实用的功能和工具。1、通用Mapper提供了一组通用的Mapper接口和实现可以快速进行增删改查操作无需手写SQL语句。例如BaseMapper、ConditionMapper等publicinterfaceUserMapperextendsBaseMapperUser{}2、分页插件提供了一种简单易用的分页功能可以根据传入的分页参数自动计算出分页信息无需手动编写分页SQL语句。publicinterfaceUserMapperextendsBaseMapperUser{ListUserselectUserPage(PageUserpage,Param(name)Stringname);}3、自动生成代码可以根据数据库表自动生成实体类、Mapper接口、Mapper XML映射文件等代码大大减少了开发人员的工作量。AutoGeneratorgeneratornewAutoGenerator();generator.setDataSource(dataSourceConfig);generator.setPackageInfo(newPackageConfig().setParent(com.example.mybatisplus));generator.setGlobalConfig(newGlobalConfig().setOutputDir(System.getProperty(user.dir)/src/main/java));generator.setTemplateEngine(newFreemarkerTemplateEngine());generator.execute();4、SQL注入器提供了自定义的SQL注入器功能可以自由扩展MyBatis的SQL语句实现更加灵活的SQL操作。publicclassCustomSqlInjectorextendsAbstractSqlInjector{OverridepublicListAbstractMethodgetMethodList(Class?mapperClass){ListAbstractMethodmethodListnewArrayList();methodList.add(newCustomInsert());returnmethodList;}}publicclassCustomInsertextendsInsertMethod{OverridepublicStringgetMethod(SqlSourcesqlSource){returncustomInsert;}}5、性能分析插件提供了性能分析插件可以帮助开发人员分析SQL执行效率优化数据库操作。参考链接1、https://www.yuque.com/hollis666/wk6won/bi28ye2、https://www.yuque.com/hollis666/wk6won/evq52f3、https://www.yuque.com/hollis666/wk6won/ygkn2rwkgv7hrvnv