Mybatis
Mybatis框架概述
-
mybatis是基于JDBC进行的封装简化,主要替代JDBC对于数据库的操作
-
mybatis前身是ibatis,在2010年时,mybatis从创始团队转交给了google code维护,更名为mybatis;2013年入驻github,目前mybatis的源码依旧托管在github上
-
github是外网,码云是中文版
-
mybatis框架特点
- 简单:使用mybatis框架只需要引入一个mybatis的核心依赖即可,不依赖任何的第三方jar
- 灵活:mybatis将sql语句书写在配置文件中,利于重构
JDBC Mybatis 需要手动给占位符赋值,操作冗余 查询时手动封装结果集 每次与数据库进行交互,都需要创建和销毁连接,没有提供连接池 不需要每次都创建和销毁 没有提供查询的缓存优化,没有数据缓存机制 常用数据存储在缓存中
使用Mybatis开发
-
搭建开发环境
- 引入jar:mybatis-3.4.6.jar
- 引入配置文件:
- mybatis-config.xml,Mapper.xml文件位置不强制要求
- log4j.properties必须放在src根目录下
- 初始化配置:编写mybatis-config.xml文件,配置mybatis框架的数据库运行环境以及其他的配置信息
<configuration> <!-- 配置数据库运行环境,default指定mybatis默认采用哪个数据库 --> <environments default="oracle"> <!-- id是当前数据库运行环境的唯一标识,也是使用这个环境的依据 --> <environment id="oracle"> <!-- 指定事务管理采用的机制 --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据库连接参数,type属性指连接池的名字 POOLED是mybatis默认的连接池 --> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" /> <property name="username" value="hr"/> <property name="password" value="hr"/> </dataSource> </environment> </environments> <!-- 注册mapper文件 --> <mappers> <!-- 指定mapper文件路径 --> <mapper resource="com/yuan/dao/AccountDaoMapper.xml"/> </mappers> </configuration>
Mybatis中核心API
-
Resources:用于读取mybatis-config.xml核心配置文件
- InputStream in = Resources.getResourceAsStream(“核心配置文件路径”);
-
SqlSessionFactory:用于创建SqlSession对象,使用了工厂设计模式
- 是一个重量级资源,也是一个线程安全的对象,一般一个应用只创建一个SqlSessionFactory
-
SqlSession:一个SqlSession对象对应一个Connection对象,SqlSession相当于JDBC中的Connection
- Mybatis不是使用类实现接口,是使用Mapper文件,所有要先把Mapper文件转换成类再运行,由SqlSession解析Mapper.xml转换
- 使用SqlSession可以获取到DAO实现类的对象
- 进行事务的管理和控制:sqlSession.commit() / sqlSession.rollback() (间接操作Connection对象来管理事务)
- mabatis默认事务控制方式为手动,不需要setAutoCommit()
- SqlSession在并发场景下,是线程不安全的对象,每一个用户必须独立使用
-
使用Mybatis操作数据库
public interface AccountDao { Account selectByCardId(Integer cardId); } <!-- namespace属性指定mapper文件管理接口全限定名 且一个mapper文件只能管理一个Dao接口 --> <mapper namespace="com.yuan.dao.AccountDao"> <!-- id属性指定接口对应方法名 resultType指定接口方法返回值类型的全限定名, 告知Mybatis查询返回的结果封装到哪个类的属性中 --> <select id="selectByCardId" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account where card_id=#{card_id} </select> </mapper> @Test public void testSelectByCardId() throws IOException{ //读取mybatis-config.xml InputStream inputStream = Resources.getResourceAsStream("com/yuan/conf/mybatis-config.xml"); //创建SqlSessionFactory SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //创建SqlSession SqlSession sqlSession = sessionFactory.openSession(); //获取DAO实现类的对象,需要指定接口的类型 AccountDao dao = sqlSession.getMapper(AccountDao.class); //测试方法 Account account = dao.selectByCardId(10062); System.out.println(account); sqlSession.close(); }
- 在核心配置文件中注册mapper文件的位置
<!-- 注册mapper文件位置 --> <mappers> <!-- 一对mapper标签注册一个mapper文件的位置 路径严格区分大小写,多级之间用 / 分割 --> <mapper resource="com/hang/dao/AccountDaoMapper.xml"></mapper> </mappers>
-
Mybatis的接口参数绑定
- 一个参数时:如果接口定义的方法声明了一个参数,在mapper文件中使用#{ }取值的方式为:#{任意字符 },不能为汉字
<select id="queryById" resultType="com.hang.entity.Account"> select card_id,username,password,balance,mobile from account where card_id=#{id} </select>
- 多个参数时
- 通过下标的方式:将方法的参数看成一个数组,每个参数看成数组的元素,下标从0开始,顺序是以方法参数的顺序为主
<select id="queryByUsernameAndPassword" resultType="com.hang.entity.Account"> select card_id,username,password,balance,mobile from account where username=#{0} and password=#{1} </select>
- 注解方式:通过在参数的类型前面指定@Param注解,同时给参数指定一个绑定的名字,@Param注解中的value可以省略,如:@Param(“username”)
- 多用于查询操作接口方法的定义
public interface AccountDao { Account selectByUsernameAndPassword( @Param(value="name")String username,//value可以不写 @Param(value="pwd")String password);//@Param("pwd") } <select id="selectByUsernameAndPassword" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account where username=#{name} and password=#{pwd} </select>
- 使用Map作为参数:调用时Map的key必须和Mapper.xml中#{ }一致;优点是参数个数无限
public interface AccountDao { Account selectByMap(Map<String,String> map); } <select id="selectByMap" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account where username=#{username} and password=#{password} </select> public void testSelectByMap() throws IOException{ //读取mybatis-config.xml InputStream in = Resources.getResourceAsStream("com/yuan/conf/mybatis-config.xml"); //创建SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //获取SqlSession SqlSession sqlSession = factory.openSession(); AccountDao dao = sqlSession.getMapper(AccountDao.class); //注意map是key和value Map<String,String> map = new HashMap<>(); map.put("username", "ceshi"); map.put("password", "1234"); Account account = dao.selectByMap(map); System.out.println(account); sqlSession.close(); }
- 使用对象的方式:将参数声明为一个自定义对象类型,在mapper中取值的时候,使用#{ 对象类型的属性名 }
- 主要用于CRUD中的添加或者更新
public interface AccountDao { Account selectByObj(Account account); } <select id="selectByObj" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account where username=#{username} and password=#{password} //对应实体类属性名 </select>
使用Mybatis开发CRUD
-
增删改必须手动提交事务,不然mybatis会自动做回滚
-
查询操作一定不要控制事务
-
查询所有
List<Account> selectAll(); <!-- 如果接口方法返回的是list集合,则书写集合泛型类型的全限定名 --> <select id="selectAll" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account </select>
-
模糊查询
List<Account> selectByLike(String username); <!-- 模糊查询 --> <select id="selectByLike" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from account where username like '%'||#{username}||'%'<!-- '%xx%' --> </select>
-
分页查询
List<Account> selectByPage( @Param("begin")Integer begin, @Param("end")Integer end); <!-- 分页 --> <select id="selectByPage" resultType="com.yuan.entity.Account"> select card_id,username,password,balance,mobile from (select card_id,username,password,balance, mobile,rownum rn from account) where rn>=${begin} and rn <=#{end} </select>
-
修改
void update(Account acc); <!-- 更新 --> <update id="update"> update account set username=#{username},password=#{password}, balance=#{balance},mobile=#{mobile} where card_id=#{card_id} </update>
-
根据id删除
void delete(Integer cardId); <!-- 根据id删除 --> <delete id="delete"> delete from account where card_id=#{card_id} </delete>
-
添加
void insert(Account acc); <!-- 添加 --> <!-- 第一种实现添加 --> <insert id="insert"> insert into account values(account_seq.nextval,#{username},#{password},#{balance},#{mobile}) </insert> <!-- 第二种添加,添加成功返回主键值 --> <insert id="insert"> <!-- 在执行添加到SQL之前,先用序列生成新的主键值 --> <!-- keyProperty代表当前SQL查询返回的值,要赋值给参数 对象类型 的哪个属性 resultType指定返回值类型 order指定当前SQL执行顺序,值必须大写,BEFORE代表先执行 --> <selectKey keyProperty="card_id" resultType="java.lang.Integer" order="BEFORE"> select account_seq.nextval from dual </selectKey> <!-- 再执行添加操作 --> insert into account values(#{card_id},#{username},#{password},#{balance},#{mobile}) </insert> <!-- Account acc = new Account(null,"ceshi","1234",546.9,"465434564"); System.out.println("添加前"+acc); 此时card_id为空 dao.insert(acc); System.out.println("添加后:"+acc); 此时card_id为序列不为空 -->
工具类
- Mybatis核心API特点与配置工具类思想:
- 读取资源配置文件操作,执行一次即可
- SqlSessionFactory属于重量级资源,是一个线程安全的对象,一个应用通常创建一个即可
- SqlSession属于线程不安全的对象,一个用户必须单独使用一个,可以绑定到当前线程ThreadLocal中
mybatis文件中第二种取值语法
- ${ … }:以字符串拼接的方式生成sql语句取出来的,值是原样放置,可作为表中的一个字段名
- 不管参数几个,都必须使用注解绑定
- 取出来的值会以字符串原样拼接到SQL语句中
- #{ … }:以占位符的方式生成sql语句,取出来的带’ '号
public interface AccountDao {
//参数必须使用注解绑定
List<Account> queryByLikeUsername(@Param("username")String username);
}
<select id="queryByLikeUsername" resultType="com.hang.entity.Account">
select card_id,username,password,balance,mobile
from account
where username like '%${username}%'
</select>
-
将参数作为表中字段
public interface AccountDao { //按照手机号模糊查询,第一个为模糊查询条件,第二个参数为按什么排序 List<Account> queryByLikeMobile( @Param("mobile")String mobile,@Param("sort")String sort); } <select id="queryByLikeMobile" resultType="com.hang.entity.Account"> select card_id,username,password,balance,mobile from account where mobile like '%${mobile}%' order by ${sort} desc //字段desc也可以拼接 </select>
-
区别
#{ } ${ } 取值是以占位符的方式生成最后的sql语句,可以有效避免sql注入 取值是以字符串拼接到方式生成最后的sql语句,存在sql注入的风险,可以在前端规避 常用方式 不得已时使用 -
分页:任何的配置文件中不允许写大于和小于号
List<Account> queryByPage(@Param("begin")Integer begin,@Param("end")Integer end); <select id="queryByPage" resultType="com.hang.entity.Account"> select card_id,username,password,balance,mobile,rn from (select a.*,rownum rn from account a) where rn >= #{begin} and rn <= #{end} // 注意大于和小于号 </select> // 也可用between and 但是效率相对较低 //也可以查所有之后,用List中的 subList(int begin,int end)方法来实现分页,但是当数据量过多时,占用内存,通常不用
Mybatis-config.xml的其他配置
-
给实体类起别名
<!-- 第一种给实体类起别名 --> <typeAliases> <!-- type属性值为实体类全限定名 alias属性值为别名 --> <typeAlias type="com.hang.entity.Account" alias="Account"/> </typeAliases> <!-- 第二种给实体类起别名 --> <typeAliases> <!-- 给指定包下的所有实体类起别名 name属性值为指定的包结构,此时com.hang.entity包下所有实体类都会起别名, 默认的规则是以类名为别名 如com.hang.entity.Account别名为Account --> <package name="com.hang.entity"></package> </typeAliases> <!-- resultType此时写的别名 --> <select id="queryAll" resultType="Account"> select card_id,username,password,balance,mobile from account </select>
-
mybatis-config.xml中配置的参数化
- 为了提高配置文件的可维护性,将数据库运行环境进行参数化
- 将数据库的参数提取到一个properties类型的配置文件中定义,然后在mybatis-config.xml中引入外部的配置文件
- 为了防止框架加载参数出现冲突,建议在数据库参数的key前指定一个前缀
jdbc.properties db.driver=oracle.jdbc.OracleDriver db.url=jdbc:oracle:thin:@localhost:1521:xe db.username=hr db.password=hr <!-- 在mybatis-config.xml中使用标签引入外部的配置文件,注意放置顺序 --> <properties resource="com/hang/conf/db.properties"></properties> <dataSource type="POOLED"> <!-- 连接数据库的参数 --> <property name="driver" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </dataSource>
resultMap
-
手动定义结果集封装的映射关系
-
Mybatis默认结果集封装规则:默认情况下要求实体类的属性名与表的字段名完全一致,这时候Mybatis会根据查询数据库返回结果集中的字段名,去实体类的属性上进行对应的封装。一旦实体类属性名和表字段名不一致默认时则无法进行对应封装
-
当查询结果集中的字段名与实体类的属性名不一致的时候,我们可以使用resultMap自定义结果集封装的映射关系,即表字段名和实体属性之间的映射关系
- 可以通过字段起别名方式来解决字段名和实体属性名不一致的问题
- 如果实体类属性名与查询返回结果集字段名一致,可以不定义封装规则
<!-- 自定义结果集映射关系 type指定实体类全限定名或别名 id是当前resultMap的唯一标识,也是未来使用resultMap的依据 --> <resultMap type="Account" id="accResultMap"> <!-- 用于映射表的主键字段与实体指定属性的关系 column为查询返回结果集中的字段名,若SQL语句有别名,则必须写别名 property为对应到实体类上的属性名--> <id column="card_id" property="card_id"/> <!-- 主键 --> <result column="username" property="username"/> <!-- 其余字段 --> <result column="password" property="password"/> <!-- 相同的可以不写 --> </resultMap> <!-- 使用时要更换规则,不能使用默认的resultType,且值为自定义id 指定当前查询按照哪个resultMap定义的封装规则进行结果集的封装 --> <select id="queryAll" resultMap="accResultMap"> select card_id,username,password,balance,mobile from account </select> <!-- 第二种,使用as来更换规则 --> <select id="queryAll" resultType="Account"> select card_id as cardid,username,password,balance,mobile from account </select>
-
关联关系查询处理
-
分类
- 一对一:此时外键放在哪一张表都可以体现出来,此时外键必须唯一;实体类中的关系属性放在哪一方要看根据谁去找谁,通常放在"根据谁的一方"
- 一对多(多对一):外键放在多的一方
- 多对多:可以理解为两个双向的一对多;主要建立数据的关联关系
-
一对一:
public class MyPerson implements Serializable{ private Integer id; private String name; private Integer age; private String mobile; //关系属性 private PersonId personid; getter / setter } public class PersonId implements Serializable{ private String cardId; private String addressInfo; } <!-- 定义resultMap --> <resultMap type="MyPerson" id="personResultMap"> <id column="pid" property="id"/> <result column="pname" property="name" /> <result column="page" property="age" /> <result column="pmobile" property="mobile" /> <!-- 关系属性的映射 property指定关系属性变量名 javaType指定实体类全限定名或别名 --> <association property="personid" javaType="PersonId"> <id column="cardId" property="cardId" /> <result column="addressInfo" property="addressInfo" /> </association> </resultMap> <select id="queryAll" resultMap="personResultMap"> select p.id as pid,p.name as pname,p.age as page,p.mobile as pmobile, p.card_id as cardId,i.address_info as addressInfo from myperson p left join personid i on p.card_id=i.card_id </select> @Test public void testQueryAll(){ MyPersonDao dao = MybatisUtil.getMapper(MyPersonDao.class); List<MyPerson> list = dao.queryAll(); for(MyPerson person:list) System.out.println(person.getName()+"\t"+person.getPersonid().getCardId()); }
-
一对多的处理
<!-- 自定义resultMap封装结果集的规则 --> <resultMap type="Student" id="studentMap"> <id column="sid" property="id"></id> <result column="sname" property="name"></result> <result column="sex" property="sex"></result> <result column="sage" property="age"></result> <!-- 定义关系属性 关系属性是一个简单的java对象 property:指定关系属性变量名 javaType:指定关系属性类型的全限定名或别名--> <association property="clazz" javaType="Clazz"> <id column="cid" property="id"/> <result column="cname" property="name"/> </association> </resultMap> <select id="selectById" resultMap="studentMap"> select s.id sid,s.name sname,s.sex sex,s.age sage,c.id cid,c.name cname from t_stu s left join t_clz c on s.cid=c.id where s.id=#{id} </select>
-
多对多:
- 表:
create table t_student( id number(3) primary key, name varchar2(26), sex varchar2(3) ) create table t_course( id number(3) primary key, name varchar2(26) ) create table stu_cou( sid number(3) references t_student(id), cid number(3) references t_course(id), primary key (sid,cid) )
- 实体类
public class Course implements Serializable{ private Integer id; private String name; //关系属性 private List<Stu> stus; //getter / setter / 有参无参构造 } public class Stu implements Serializable{ private Integer id; private String name; private String sex; //关系属性 private List<Course> courses; //getter / setter / 有参无参构造 }
- DaoMapper
<mapper namespace="com.yuan.dao.SchoolDao"> <!-- 自定义映射关系 --> <resultMap type="Stu" id="BaseMap"> <id column="sid" property="id" /> <result column="sname" property="name"/> <result column="sex" property="sex"/> <!-- 关系属性是List集合 --> <collection property="courses" ofType="Course"> <id column="cid" property="id" /> <result column="cname" property="name"/> </collection> </resultMap> <select id="selectAll" resultMap="BaseMap"> select s.id sid,s.name sname,s.sex sex,c.id cid,c.name cname from t_student s left join stu_cou sc on s.id=sc.sid left join t_course c on c.id=sc.cid </select> </mapper>
- 测试
public void testSelectAll(){ SchoolDao dao = MybatisUtil.getMapper(SchoolDao.class); List<Stu> list = dao.selectAll(); for(Stu stu:list){ System.out.println(stu.getName()+"课程有:"); for(Course cou:stu.getCourses()){ System.out.println(cou.getName()); } } MybatisUtil.close(); }
动态SQL
-
SQL片段:将多个sql语句的共同部分,提取到一个位置定义,未来在需要使用的位置再进行引入,提高项目的可维护性
<!-- 定义SQL片段:id属性值是一个唯一的标识,也是未来引用这个SQL片段的依据 --> <sql id="ACCOUNT_SELECT_SQL"> select card_id,username,password,balance,mobile from account </sql> <select id="selectById" resultType="Account"> <!-- 引入一个SQL片段 --> <include refid="ACCOUNT_SELECT_SQL"/> where card_id=#{cardId} </select>
-
where标签的动态SQL:查询时使用,减少接口数,提高效率
- where子句会根据if标签等判断,决定是否加where;同时会自动剔除第一个无效的 and 和 or
<!-- where动态SQL子句 --> <select id="selectByLikeAll" resultType="Account"> select card_id,username,password,balance,mobile from account <!-- where --> <where> <if test="username!=null"> and username like '%'||#{username}||'%'</if> <if test="mobile!=null"> and mobile like '%'||#{mobile}||'%'</if> <if test="balance!=null"> and balance < #{balance}</if> </where> </select>
-
set标签:更新时使用,修改哪个字段就只在sql中拼接哪个字段
- 自动剔除最后一个非法的逗号且修改哪个,自动拼接哪个字段
<update id="update"> <!-- update account set username=#{username},password=#{password}, balance=#{balance},mobile=#{mobile} --> update account <!-- set --> <set> <if test="username!=null">username=#{username},</if> <if test="password!=null">password=#{password},</if> <if test="balance!=null">balance=#{balance},</if> <if test="mobile!=null">mobile=#{mobile}</if> </set> where card_id=#{card_id} </update>
-
foreach标签:可以遍历参数的集合或数组
<!-- foreach标签的语法结构 --> delect from account where card_id in <foreach open="(" item="v" separator="," close=")"> #{v} </foreach> <!-- collection:指定接口参数集合绑定的名字,相当于JSP的items,item相当于JSP的var,循环变量临时变量 open为从哪开始,separator为用什么隔开,close为以什么结尾 --> <delete id="batchDelect"> delete from account where card_id in <foreach collection="ids" open="(" item="id" separator="," close=")"> #{id} </foreach> </delete>
-
trim:
- 使用trim实现where动态SQL语句:prefix指定子句前面的关键字为where;prefixOverrides指定剔除的首个非法关键字是什么,可以同时剔除多个,and | or
- 使用trim实现set语句:prefix指定子句前面的关键字为set;suffixOverrides指定剔除最后一个非法的字符
<!-- 使用trim替换where prefix:用于指定查询子句的前面的关键字 prefixOverrides:指定剔除的首个非法关键字是什么,可以同时剔除多个 --> <trim prefix="where" prefixOverrides="and | or"> <if test="username!=null"> and username like '%'||#{username}||'%'</if> <if test="mobile!=null"> and mobile like '%'||#{mobile}||'%'</if> <if test="balance!=null"> and balance < #{balance}</if> </trim> <!-- 使用trim替代set suffixOverrides:指定剔除最后一个非法的字符 --> <trim prefix="set" suffixOverrides=","> <if test="username!=null">username=#{username},</if> <if test="password!=null">password=#{password},</if> <if test="balance!=null">balance=#{balance},</if> <if test="mobile!=null">mobile=#{mobile}</if> </trim>
-
choose:相当于
<!-- field代表要根据哪个字段进行匹配 value代表条件的值--> List<Account> selectForList( @Param("field")String field, @Param("value")String value ); <select id="selectForList" resultType="Account"> select card_id,username,password,balance,mobile from account where <choose> <!-- 必须加单引号,代表是字符串,且不能为一个字符 --> <when test="field= 'name' ">username like '%'||#{value}||'%'</when> <when test="field= 'mobile' ">mobile like '%'||#{value}||'%'</when> <!-- 防止非法字符,不满足以上所有条件时,会执行otherwise --> <otherwise>1=1</otherwise> </choose> </select>
Mybatis缓存
-
缓存机制:Web应用中的缓存机制是一种优化项目的手段,通常指内存;主要是针对查询的一种效率上的优化手段
-
加入缓存后请求数据的过程:当客户端请求查询数据的时候,先到缓存中找,如果没有再到数据库查询,从数据库查询返回的数据再向缓存备份一份。当下一次请求相同的数据查询时,可以直接从缓存中获取
-
优点:
- 减轻了数据库的压力,减少了查询数据库的次数
- 由于缓存的数据是在内存存储的,提高了检索数据的效率
-
Mybatis底层缓存默认用的Map,便于寻找
-
Mybatis中的缓存机制
-
SqlSession会话级别缓存,即同一个SqlSession下共享 —> 一级缓存:默认开启
-
基于同一个SqlSessionFactory的mapper级别的,即同一个SqlSessionFactory下共享 —> 二级缓存:默认关闭
- 基本使用
- 开启二级缓存:在mybatis-config.xml中添加标签settings
<!-- 开启二级缓存 --> <settings> <setting name="cacheEnabled" value="true" /> </settings>
- 在指定的mapper文件中使用二级缓存
<!-- 使用二级缓存 使用的是mybatis默认提供的缓存实现 --> <cache></cache>
- 必须关闭sqlSession才会向缓存中存入数据
- 对象不实现序列化接口,不能使用mybatis缓存
-
-
缓存代码演示
//一级缓存演示 @Test public void testCacheOne() throws IOException{ InputStream inputStream = Resources.getResourceAsStream("com/yuan/conf/mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = factory.openSession(); AccountDao dao1 = sqlSession.getMapper(AccountDao.class); Account acc1 = dao1.selectById(10008); System.out.println("acc1:"+acc1); System.out.println("==============="); AccountDao dao2 = sqlSession.getMapper(AccountDao.class); Account acc2 = dao2.selectById(10008); System.out.println("acc2:"+acc2); sqlSession.close(); } //二级缓存演示 @Test public void testCacheTwo() throws IOException{ InputStream inputStream = Resources.getResourceAsStream("com/yuan/conf/mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); System.out.println("========第一次查询======="); SqlSession sqlSession1 = factory.openSession(); AccountDao dao1 = sqlSession1.getMapper(AccountDao.class); Account acc1 = dao1.selectById(10008); System.out.println("acc1:"+acc1); //必须关闭SqlSession才会向缓存存数据 sqlSession1.close(); // 多次查询相同的Cache Hit Ratio 命中率会越来越高 System.out.println("========第二次查询======="); SqlSession sqlSession2 = factory.openSession(); AccountDao dao2 = sqlSession2.getMapper(AccountDao.class); Account acc2 = dao2.selectById(10008); System.out.println("acc2:"+acc2); sqlSession2.close(); }
脏读
- 缓存中的数据与数据库表中的数据不一致
- 解决方案:每次写操作时清空指定缓存