1、MayBatis的缓存:
缓存通过减少IO(读写文件)的方式,来提高程序的执行效率。mybatis的缓存将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO,另一方面不再执行繁琐的查找算法,效率大大提升。
mybatis缓存包括:
- 一级缓存:将查询到的数据存储到SqlSession中。(当前的会话)
- 二级缓存:将查询到的数据存储到SqlSessionFactory中。(对于整个数据库)
- 或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。
缓存只针对于DQL语句,也就是说缓存机制只对应select语句
(1)一级缓存:
默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,如果执行2次相同的SQL查询,那么第二次SQL查询会直接取缓存的数据,而不是到数据库中查询,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会到数据库中查找数据。
一级缓存在下面情况会被清除
- 在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存(DML的操作和表没有关系,即使操作的不是同一个表也会触发这操作!)
- SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
- 对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效,在全局配置文件中设置 <setting name="localCacheScope" value="STATEMENT"/>,这样会使一级缓存失效,二级缓存不受影响
- 执行sqlSession的clearCache(),方法后一级缓存会被清空(手动清空缓存)
MappedStatement是指:一个CRUD标签在mybatis中会被封装成一个MappedStatement。
测试
mapper接口:
//通过id查询汽车
Car selectById(Long id);
mapper映射文件:
<select id="selectById" resultType="Car">
select * from t_car where id=#{id}
</select>
测试类: 使用的是同一个mapper
//测试一级缓存
@Test
public void test1(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = mapper.selectById(194L);
System.out.println(car1);
Car car2 = mapper.selectById(194L);
System.out.println(car2);
//关闭
sqlSession.close();
}
测试结果:
执行相同的sql的时候,第二次没有执行sql语句,而是直接使用缓存!
换换测试方式:既然是在同一个sqlsession那么和创建的mapper应该没有什么关系吧
//使用的是不同mapper(狗头保命sqlsession)
@Test
public void test2(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = mapper.selectById(194L);
System.out.println(car1);
Car car2 = mapper.selectById(194L);
System.out.println(car2);
//关闭
sqlSession.close();
}
测试结果 还是一个
获取不同的Sqlsession对象测试:
@Test
public void test3() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession sqlSession = sessionFactory.openSession();
SqlSession sqlSession1 = sessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById(194L);
System.out.println(car);
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(194L);
System.out.println(car1);
sqlSession.close();
sqlSession1.close();
}
测试结果:
测试的结果是使用的是两个sqlsession对象,所以导致这样的情况发生。由于只会存在同一个SQL Session的缓存,所以此次的查询使用啦两个sql语句。
(2)二级缓存:
默认关闭,可通过全局配置文件中的<settings name="cacheEnabled" value="true"/>开启二级缓存总开关,然后在某个具体的mapper.xml中增加<cache />,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交或者关闭,查询的数据才会被刷新到二级缓存当中。
使用二级缓存需要具备以下几个条件:
1、<setting name="cacheEnabled" value="true"> 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。
2、在需要使用二级缓存的SqlMapper.xml文件中添加配置:<cache />
3、使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
4、SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
测试类:
@Test
public void test4() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession sqlSession = sessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById2(194L);
System.out.println(car);
SqlSession sqlSession1 = sessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById2(194L);
System.out.println(car1);
}
这里避免了一级缓存的情况,使用的不同sqlSession对象,但是没有关闭和提交所以应该是两条的sql语句。
测试结果:
执行的是两个sql语句!
那么将上面的测试的代码稍稍稍修改一下,然后体现二级缓存的功能!
关闭后才会将一级缓存存入二级缓存中,这样下一次执行相同的数据就会在缓存中获取查询结果。
测试结果:
触发二级缓存!只是执行啦一次的sql语句!
缓存机制:
执行sql语句后------存入一级缓存------存入二级缓存----再次执行sql------会先到一级缓存(没有)------然后到二级缓存(没有)------到数据库中
只要两个sql查询语句之间有DML操作执行,那么一级和二级缓存都会被清理!!!!
(3)二级缓存的相关配置:
(1)eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
1、LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
2、FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
3、SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
4、WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
(2)flushInterval:二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
(3)readOnly:
1、true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
2、false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
(4)size:设置二级缓存中最多可存储的java对象数量。默认值1024。
(4)使用第三方缓存:
集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。EhCache是Java写的。Memcache是C语言写的。所以mybatis集成EhCache较为常见,按照以下步骤操作,就可以完成集成:
导入依赖:
<!--mybatis集成ehcache的组件-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
<diskStore path="e:/ehcache"/>
<!--defaultCache:默认的管理策略-->
<!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
<!--maxElementsInMemory:在内存中缓存的element的最大数目-->
<!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
<!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
<!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
<!--FIFO:first in first out (先进先出)-->
<!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
<!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
</ehcache>
引入第三方缓存:在XXXMapper.xml文件中
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
2、MyBatis的逆向工程:
mybatis官方提供了mapper自动生成工具mybatis-generator-core来针对单表,生成PO类,以及Mapper接口和mapper.xml映射文件。针对单表,可以不需要再手动编写xml配置文件和mapper接口文件了,非常方便。美中不足的是它不支持生成关联查询,一般做关联查询,就自己单独写SQL就好了。
就是根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
配置信息包含:
- pojo类名、包名以及生成位置。
- SqlMapper.xml文件名以及生成位置。
- Mapper接口名以及生成位置。
- 连接数据库的信息。
- 指定哪些表参与逆向工程。
第一步导入依赖:
<!--定制构建过程-->
<build>
<!--可配置多个插件-->
<plugins>
<!--其中的一个插件:mybatis逆向工程插件-->
<plugin>
<!--插件的GAV坐标-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<!--允许覆盖-->
<configuration>
<overwrite>true</overwrite>
</configuration>
<!--插件的依赖-->
<dependencies>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
配置核心文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime有两个值:
MyBatis3Simple:生成的是基础版,只有基本的增删改查。
MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!--防止生成重复代码-->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
<commentGenerator>
<!--是否去掉生成日期-->
<property name="suppressDate" value="true"/>
<!--是否去除注释-->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--连接数据库信息-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/powernode"
userId="root"
password="root">
</jdbcConnection>
<!-- 生成pojo包名和位置 -->
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
<!--是否开启子包-->
<property name="enableSubPackages" value="true"/>
<!--是否去除字段名的前后空白-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成SQL映射文件的包名和位置 -->
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
<!--是否开启子包-->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 生成Mapper接口的包名和位置 -->
<javaClientGenerator
type="xmlMapper"
targetPackage="com.powernode.mybatis.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 表名和对应的实体类名-->
<table tableName="t_car" domainObjectName="Car"/>
</context>
</generatorConfiguration>
使用插件生成文件:
然后在修修改改其他的配置信息就可以进行测试啦!
创建简单的sql语句我觉得就可以,对于负载的sql觉得使用起来并不方便
3、MyBatis使用PageHelper分页插件:
分页的参数介绍:
pageNum 当前页码
pageSize 显示信息数
totalcount 总记录数
totalPage 总页码
totalPage=totalCount % pageSize ==0? (totalCount % pageSize) : (totalCount % pageSize) +1 ;
分页展示的时候要使用 limit 分页查询 使用这个关键字要传递两个参数,第一个 start 就是开始的索引,然后是查询的条数,也就是pageSize,那么start怎么计算呐!
start=(pagaNum-1) * pageSize
第一步导入依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.3</version>
</dependency>
第二步在核心配置文件设置插件:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
mapper接口:
//分页插件查询所有
List<Car> selectAll();
mapper映射文件:
<select id="selectAll" resultType="Car">
select * from t_car
</select>
这样看好像也没什么区别和之前的操作,是不是很迷惑!
测试类来啦:
//使用分页插件
@Test
public void test2(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
//在执行DQL语句之前开启分页功能
int pageNum=2;
int pageSize=5;
PageHelper.startPage(pageNum,pageSize);
List<Car> carList = mapper.selectAll();
carList.forEach(System.out::println);
sqlSession.close();
}
另外说一下,pagehelper中可以将查询出来的数据封装到pageInfo中,然后这个对对象里面有很多的信息,真的就很牛 。节省很多功夫!这里有大概的数据展示可以了解一下!
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3, reasonable=false, pageSizeZero=false}
[Car{id=86, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'},
Car{id=87, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]
}
4、基于注解开发:
mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。但是使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
注解方式插入数据:
//注解方式插入
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
注解方式删除:
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
注解方式修改:
@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);
注解方式查询:
@Select("select * from t_car where id = #{id}")
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "car_num", property = "carNum"),
@Result(column = "brand", property = "brand"),
@Result(column = "guide_price", property = "guidePrice"),
@Result(column = "produce_time", property = "produceTime"),
@Result(column = "car_type", property = "carType")
})
Car selectById(Long id);
多个参数的注解方式:
@Select("SELECT * FROM student WHERE name like '%${name}%' AND major like '%${major}%'")
List<Student> find(@Param("name") String name, @Param("major") String major);