版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37121463/article/details/80820587
项目地址:https://gitee.com/chuyunfei/learn.git
一、mybatis基础入门
xml配置方式:
1、需要一个实体类:
public class User {
private Long id;
private String count;
private String password;
/* setter and getter */
}
2、需要一个dao接口:
public interface UserDao {
User selectUserById(Long id);
}
3、需要一个mybatis的主配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--用于配置别名-->
<typeAliases>
<!--为一个类配置一个简单的别名-->
<typeAlias type="basic.pojo.User" alias="User"/>
</typeAliases>
<!--用于配置数据库环境,mybatis可以配置多个数据库环境,并且会指定一个默认的数据库环境-->
<environments default="default">
<!--一个具体的数据库环境,这个数据库环境有一个唯一标识的标识符-->
<environment id="default">
<!--采用JDBC的事物管理器方式来管理事物-->
<transactionManager type="JDBC"/>
<!--配置一个数据源,同时采用数据库连接池的方式来管理数据库连接-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///learn?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--用于配置mapper配置文件,mybatis的默认在接口的相同包下面寻找与接口名称相同的配置文件-->
<mappers>
<mapper resource="basic/dao/UserDao.xml"/>
</mappers>
</configuration>
4、需要一个实现接口的mapper文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace当使用接口方式时必须是所实现接口的全限定名称-->
<mapper namespace="basic.dao.UserDao">
<!--一个预先的SQL执行过程-->
<select id="selectUserById" parameterType="long" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
5、需要一个主函数来测试:
public static void main(String[] args){
//主配置文件的位置,用于构建sqlsessionfactory,注意不要加:classpath
String resource = "basic/mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = null;
try{
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch(Exception e){
e.printStackTrace();
System.out.println(e.getMessage());
}
if(sqlSessionFactory != null){
//开启一个会话
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE,true);
//获取一个代理的mapper接口,会自动的将我们写的配置文件使用代理的方式来实现接口的方法
UserDao userDao = sqlSession.getMapper(UserDao.class);
//像执行普通方法一样执行
User user = userDao.selectUserById(1L);
System.out.println(user);
//关闭资源
sqlSession.close();
}
}
6、主配置文件使用代码来进行配置:
/**
* 使用纯代码配置 mybatis-config.xml
*/
@Test
public void main1(){
//声明一个数据源
PooledDataSource dataSource = new PooledDataSource(
"com.mysql.jdbc.Driver"
,"jdbc:mysql:///learn?useSSL=false"
,"root"
,"root"
);
//不自动提交事物
dataSource.setDefaultAutoCommit(false);
//声明一个事物管理器
TransactionFactory transactionFactory = new JdbcTransactionFactory();
//创建一个数据库环境
Environment environment = new Environment("default",transactionFactory,dataSource);
//创建最高的配置节点
Configuration configuration = new Configuration(environment);
//添加别名
configuration.getTypeAliasRegistry().registerAlias("User",User.class);
//添加一个mapper:这里无法添加资源的位置,所以需要遵循mybatis的默认文件位置,还有maven的构建配置
configuration.addMapper(UserDao.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
System.out.println(userDao.selectUserById(1L));
}
二、mybatis进阶之缓存
<!--配置mybatis的主要配置:是最复杂的配置,深刻的影响到底层的运行,常见的配置如下所示:-->
<settings>
<!--配置mybatis的日志实现框架:配置日志实现后,只要放置配套的日志配置文件就可以实现日志-->
<setting name="logImpl" value="LOG4J2"/>
<!--
映射器的缓存全局开关(二级缓存),默认为true,即默认开启映射器缓存
mybatis的缓存:
缓存的命中率越高则缓存效果越好,在mybatis中,一级缓存(本地缓存:同一个SqlSession对象中的缓存)是默认开启并且不能够控制的,所以mybatis缓存主要是指二级缓存。
一级缓存:
场景:
//这个sqlsession不会自动的提交事物,需要手动提交
SqlSession sqlSession = sqlSessionFactory.openSession(false);
//获取一个mapper
UserDao userDao = sqlSession.getMapper(UserDao.class);
1:User user1 = userDao.selectUserById(1L);
//plus.dao.UserDao.selectUserById - ==> Preparing: SELECT * FROM user WHERE id = ?
//plus.dao.UserDao.selectUserById - ==> Parameters: 1(Long)
//plus.dao.UserDao.selectUserById - <== Total: 1
2:user1.setId(2L);
3:User user2 = userDao.selectUserById(1L);
4:System.out.println(user2.getId());
//2
5:System.out.println(user1 == user2);
//true
User user3 = new User("971210","971210");
6:userDao.insertUser(user3);
//plus.dao.UserDao.insertUser - ==> Preparing: INSERT INTO user (count, password) VALUES (?,?);
//plus.dao.UserDao.insertUser - ==> Parameters: 971210(String), 971210(String)
//plus.dao.UserDao.insertUser - <== Updates: 1
7:user2 = userDao.selectUserById(1L);
//plus.dao.UserDao.selectUserById - ==> Preparing: SELECT * FROM user WHERE id = ?
//plus.dao.UserDao.selectUserById - ==> Parameters: 1(Long)
//plus.dao.UserDao.selectUserById - <== Total: 1
8:System.out.println(user2.getId());
//1
//提交事物:但是这里不提交,不想产生脏数据
9://sqlSession.commit();
//关闭资源
sqlSession.close();
分析:
问题提出:在1处,使用DAO查询了一个对象,同时mybatis的日志进行了打印,说明SQL语句执行,真正的去查询了数据库。
现在在2哪里,将查询出来的对象的一个属性设置为一个新的值,然后执行3的时候没有任何的日志打印,说明一级
缓存命中,返回了缓存里面的值,然后在4执行输出打印,发现user2的id值是在2处设置的值,也就是说在3哪里我
并没有去查数据库,而是将上一次查询的结果的引用直接返回给了3作为查询结果,同时5的打印为true也证明了确确实实是
同一个对象的引用,至此发现第一个无语点。
在6处,我使用DAO插入一条新的记录,同时日志打印插入成功,然后在7处,我重新的查询原来的那个对象,发现日志打印
,并且对查询结果的打印表示我们真正的查到了数据库里面的数据,而不是缓存,同时在9处我并没有提交事物。
问题原因:1.mybatis的一级缓存默认开启,并无法控制,且缓存的生命周期在同一个SqlSession对象里面,所以在3处没有日志
打印,因为命中了缓存。
2.mybatis的一级缓存命中机制是:把执行的方法和参数通过算法生成一个键值,将这个键值和查询结果放入一个Map对象里面
所以,如果执行的方法和参数相同,那么将返回同一个对象的引用,这就是为什么5的打印为true。
3.mybatis的一级缓存刷新机制:我们发现8的打印输出是1,也就是数据库里面的真实数据,而且7有日志打印,也就是说
7没有命中缓存,但是刚才不还有缓存吗?这是因为之前执行了 insert操作,在mybatis的一级缓存中,INSERT,UPDATE,DELETE
操作将刷新缓存,也就是说刚才的缓存不见了,所以才没有缓存命中,而去查的数据库。在写mapper文件的时候也可以为select语句
增加一个:flushCache="true" 来强制在查询前刷新缓存,每次都是去数据库里面查询,但是这样子会影响效率。
问题思考:
1.一级缓存只在Sqlsession对象的生命周期里面,不同的对象无法共享缓存
2.要避免对对象的修改导致下一次需要查询数据库真实数据的结果与数据库不一致,注意缓存的刷新问题
3.使用同一个Sqlsession的多个线程的对缓存的修改是有影响的,源码得知,它并不是线程安全的,缓存底层是一个HashMap
二级缓存:
二级缓存的配置:
xml配置:
1.二级缓存适时和SqlSessionFactory对象关联的,范围比一级缓存要大,同时一个应用一般是只有一个SqlSessionFactory对象
2.开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
3.二级缓存适是和命名空间(xml里面的namespace或者接口的全限定名称)绑定的,所以二级缓存要配置在mapper.xml文件里面
<mapper namespace="plus.dao.UserDao">
<cache
eviction="FIFO":缓存回收策略:
1.LRU:最近最少使用
2.FIFO:先进先出
3.SOFT:软引用,垃圾回收方法
4.WEAK:弱引用,垃圾回收方法
flushInterval="60000":刷新间隔,单位为毫秒,默认是不会刷新缓存的(执行相关语句时会刷新)
刷新相当于一次主动的GC,被动的GC是执行除Select语句以外的语句
size="2048":要记住的缓存对象个数
readOnly="true":当true时,每一次返回相同的实例,但是这些实例是不能被修改的,
当false时通过序列化返回缓存对象的拷贝,虽然慢,但是安全,默认为false
/>
默认值:<cache/>的默认属性值:
1.所有的select语句结果将被缓存
2.insert,update,delete语句将刷新缓存
3.使用LRU算法回收缓存
4.缓存不主动刷新
5.缓存1024个对象引用,记住1024个对象
6.缓存为可读写的
</mapper>
注解配置:
1.纯注解配置:当一个DAO的方法全部都是使用注解方式实现时,用以下注解加在DAO接口上:
@CacheNamespace(
eviction = LruCache.class
,flushInterval = 60000
,size = 512
,readWrite = false
)
2.当使用上面的注解配置时,如果里面有方法是使用XML配置时,需要使用特殊的配置方法,因为注解和XML都必须开启
二级缓存,不然会启动报错的,这个时候有两种方式实现:
①:xml实现,注解引用:
<cache eviction="FIFO" flushInterval="60000" size="2048" readOnly="true"/>
@CacheNamespaceRef(UserDao.class)//里面的就是本身接口的字节码对象
②:注解实现,XML引用:
//LruCache.class就是LRU回收算法,就是:算法+Cache.class
@CacheNamespace( eviction = LruCache.class ,flushInterval = 60000 ,size = 512 ,readWrite = false)
<cache-ref namespace="plus.dao.UserDao"/>
场景:
//二级缓存配置:@CacheNamespace(eviction = LruCache.class ,flushInterval = 6000 ,size = 512 ,readWrite = true)
//获取第一个session
SqlSession session1 = sqlSessionFactory.openSession(false);
UserDao userDao1 = session1.getMapper(UserDao.class);
1:User user1 = userDao1.selectUserById(1L);
//plus.dao.UserDao - Cache Hit Ratio [plus.dao.UserDao]: 0.0
//plus.dao.UserDao.selectUserById - ==> Preparing: SELECT * FROM user WHERE id = ?
//plus.dao.UserDao.selectUserById - ==> Parameters: 1(Long)
//plus.dao.UserDao.selectUserById - <== Total: 1
2:user1.setId(2L);
//只有关闭session后才会将二级缓存数据真正的保存包二级缓存里面去
session1.close();
//因为配置的刷新时间为六秒,所以休眠7秒后将没有缓存了
//Thread.sleep(7000);
//获取第二个session
SqlSession session2 = sqlSessionFactory.openSession(false);
UserDao userDao2 = session2.getMapper(UserDao.class);
3:User user2 = userDao2.selectUserById(1L);
//plus.dao.UserDao - Cache Hit Ratio [plus.dao.UserDao]: 0.5
4:System.out.println(user2.getId());
//2
5:System.out.println(user1 == user2);
//false
6:user2.setId(3L);
session2.close();
//Thread.sleep(7000);
//获取第三个session
SqlSession session3 = sqlSessionFactory.openSession(false);
UserDao userDao3 = session3.getMapper(UserDao.class);
7:User user3 = userDao3.selectUserById(1L);
//plus.dao.UserDao - Cache Hit Ratio [plus.dao.UserDao]: 0.6666666666666666
8:System.out.println(user3.getId());
//2
9:System.out.println(user3 == user2);
//false
session3.close();
分析:
问题提出:
基于本地缓存提出问题,在4处的打印是2,为2处所设置的值,但是5处打印为false,表示不是同一个对象,为第一个问题点。
在6处设置值为3,但是在7处显示缓存命中,但是8处大打印的并不是在6处设置的值,并且:9处打印仍然为false,此为第二个问题点
问题分析:
首先配置:readWrite=true 说明如果命中缓存后返回的是缓存的一个副本,对这个副本的修改不应该影响缓存本身,这就是为什么
第6处的设置没有对滴8处的打印造成影响的原因,至于为什么2处的值设置为什么对4处的打印产生了影响那是因为1处获取的对象就
直接是缓存本身的引用,并不是缓存命中后的缓存副本,这也就解释了为什么2处的设置对4、8处的打印都有影响,就是因为user2和
user3都是user1的克隆。缓存的流程是:第一次查询没有命中缓存时,查询数据库,将查询到的对象放入缓存,并将这个对象的引用
返回,也就是说返回的是缓存本身,但是在第二次查询时,先在缓存命中,所以在这种配置下面返回的是缓存的副本。
但是还有另外一种情况:readWrite=false时,这时返回的就是同一个对象的引用,对这个对象号称是只读,但是我仍然可以修改这个
对象,对这个对象的修改可以直接反馈到缓存里面的那个真正的对象上,所以这个对象可不是号称的只读!!!
还有就是我注释掉的休眠代码,当休眠时间超过配置的刷新时间后,缓存就没有了哦
问题思考:
发现在第一个获取这个缓存的对象引用可以直接修改这个缓存本身从而可能造成缓存与数据库不一致的情况。
在可读写的配置下:第一个获取该数据的引用不能做出任何影响缓存与数据库数据不一致的操作,最好第一个引用直接抛弃
对象要实现可序列化接口
在只读的配置下:虽然不能限制我们的修改,但是既然配置这个,肯定就是为了只读,还是只做只读的事情吧
存放在一个Map里面,不用实现序列化接口
只读配置被可读写配置快:因为不用对象克隆啊
mybatis集成第三方缓存框架
1.EhCache缓存框架:
-->
<!--开启二级缓存全局开关,如果这里没有开启,那么后面配置的缓存都将没有效果:默认就是开启的-->
<setting name="cacheEnabled" value="true"/>
<!--延迟加载的全局开关,当开启时,所有的关联对象都会延迟加载。但是在特定的关联关系里可以使用 fetchType 属性来覆盖该属性-->
<setting name="lazyLoadingEnabled" value="false"/>
<!--
积极懒加载开关:
——》true:对延迟关联属性的加载将对整个对象进行加载
——》false:对延迟属性的加载实现按需加载
-->
<setting name="aggressiveLazyLoading" value="false"/>