(1)简介
什么是MyBatis?
- MyBatis 是一款优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程。
- MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的实体类POJO,普通的 Java对象】映射成数据库中的记录。
- MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。
- Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html
- GitHub : https://github.com/mybatis/mybatis-3
为什么需要MyBatis?
- Mybatis就是帮助我们将数据存入数据库中,和从数据库中取数据。
- 传统的jdbc操作,有很多重复代码块 。比如 : 数据取出时的封装 , 数据库的建立连接等等… , 通过框架可以减少重复代码,提高开发效率。
- MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射。
- 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!
MyBatis的优点:
- 简单易学。
- 本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。
- sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离。
- 使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供xml标签,支持编写动态sql。
什么是持久化?
- 持久化是将程序数据在持久状态和瞬时状态间转换的机制。
- 即把内存中的数据保存到可永久保存的存储设备中(如磁盘)。
- JDBC就是一种持久化机制。文件IO也是一种持久化机制。
- 为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
- 内存断电后数据会丢失。
- 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。
Hibernate 简介
- 数据库交互框架(ORM框架)。ORM—> Object Relation Mapping 对象关系映射。黑箱操作,不需写SQL。
- Hibernate 缺点:
- 不能自己写SQL。
- 全映射框架,做部分字段映射很难。
MyBatis将SQL写在配置文件中。
即:
SQL和java分开,功能边界清晰,一个专注于数据,一个专注于业务。
(2)环境
数据库驱动:mysql-connector-java
MyBatis包:org.mybatis
日志包(非必需):在关键环节就会有日志打印。:log4j
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
layout>
appender>
<logger name="java.sql">
<level value="debug" />
logger>
<logger name="org.apache.ibatis">
<level value="info" />
logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
root>
log4j:configuration>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
(1)环境搭建
创建java工程
创建测试数据库
(2)MyBatis操作数据库的一般步骤
配置文件:两个
下配置环境
。环境可以配置多个。
配置基本属性。
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?serverTimezone=UTC&rewriteBatchedStatements=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="lu/dao/EmpDao.xml"/>
mappers>
configuration>
标签定义一个查询操作
#{id}
取值。取的是方法传过来的参数。
<mapper namespace="lu.dao.EmpDao">
<select id="getEmpById" resultType="lu.pojo.Emp">
select * from emp where id = #{id}
select>
mapper>
标签 <mappers>
<mapper resource="lu/dao/EmpDao.xml"/>
mappers>
从 XML 中构建 SqlSessionFactory,SqlSessionFactory构建SqlSession(SQL会话,SqlSession是跟数据库的一次会话,就相当于是一个Connection)。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
测试:
import lu.dao.EmpDao;
import lu.pojo.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
* @author 满眼星河
* @create 2020-11-25-9:05
*/
public class MyTest {
@Test
public void test01() throws IOException
{
//1. 根据全局配置文件构建出一个SQLSessionFactory
/**
* SqlSessionFactory是SqlSession工厂,负责创建SqlSession对象。
*
* SqlSession:代表和数据库的一次会话。
*/
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=null;
Emp emp=null;
try{
//2. 获取和数据库的一次会话,相当于getConnection()。拿到连接
sqlSession = sqlSessionFactory.openSession();
//3. 使用SQLSession操作数据库,获取到dao接口的实现。(相当于获取EmpDao.xml)
EmpDao empDao = sqlSession.getMapper(EmpDao.class);
//4. 调用dao接口的方法去查询即可
emp = empDao.getEmpById(1);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
sqlSession.close();
}
System.out.println(emp);
}
}
配置文件的约束文件:dtd是约束文件,
http://mybatis.org/dtd/mybatis-3-config.dtd
MyBatis中的增删改不会自动提交。
sqlSession.commit();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
(1)两个文件:
//class com.sun.proxy.$Proxy6
EmpDao empDao = sqlSession.getMapper(EmpDao.class);
System.out.println(empDao.getClass());
EmpDao是一个代理对象,MyBatis自动为其创建一个代理对象。
HelloWorld细节:
(2)全局配置文件:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?serverTimezone=UTC&rewriteBatchedStatements=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="lu/dao/EmpDao.xml"/>
mappers>
configuration>
配置文件中可以写如下配置:
这些标签的编写都是有顺序的,而且必须遵守,否则报错。
${}
获取值。
<properties resource="dbconfig.properties">properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driverclass}"/>
<property name="url" value="${jdbcurl}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
setting设置:
MyBatis中查询操作,会将查询结果自动封装为一个指定的对象(前提是要求数据库字段和pojo类的属性名一致)
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
typeAliases属性:指定别名。指定之后就可以在Empdao的实现文件中使用。
<typeAliases>
<package name="lu.pojo" />
typeAliases>
<typeAliases>
<typeAlias type="lu.pojo.Emp" alias="Emp"/>
typeAliases>
已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理。
typeHandlers:类型处理器。
objectFactory:对象工厂。查询SQL的返回的对象。MyBatis底层利用objectFactory通过反射来给我们创建对象。
plugins:插件。是MyBatis中的一个强大的功能。
environments:配置环境。可以配置多个环境,
中指定即可。 <environment id="development">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driverclass}"/>
<property name="url" value="${jdbcurl}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
type="DB_VENDOR"
这是写死的。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="SQL Server" value="sqlserver"/>
<property name="Oracle" value="oracle"/>
databaseIdProvider>
精确匹配优先。
<select id="getEmpById" resultType="Empss" databaseId="mysql">
select * from emp where id = #{id}
select>
<mappers>
<mapper resource="lu/dao/EmpDao.xml">mapper>
<mapper class="lu.dao.EmpDaoAnnotation">mapper>
<mapper url="">mapper>
mappers>
批量注册:
name写Dao接口所在的包名。
注意:批量注册时,dao的实现文件(例如:EmpDao.xml)一定要放在和dao同包的路径下才能获取到。
注解:
使用@Select之类的注解
public interface EmpDaoAnnotation {
@Select("select * from emp where id = #{id}")
Emp getEmpById(Integer id);
}
注解的形式只能使用class
注册。
<mappers>
<mapper class="lu.dao.EmpDaoAnnotation">mapper>
mappers>
注解和配置配合使用:
(1)Dao映射文件中能写的标签:
select标签的属性
id:指定方法。不能写重载方法。
statementType:指定执行SQL语句的执行器类型。
实现获取自增主键:
<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
insert into Emp(name,deptId) values(#{name},#{deptId})
insert>
但是:有的数据库不支持自增主键。或者没有设置主键自增。
在使用全字段更新时,如果插入的id已有,就会报错,为了解决这个问题,就可以使用 selectKey
先查询出最大的id再加一,赋值JavaBean给指定的属性。
调用这个方法时,传入的Emp为:
Emp emp = new Emp(null, "BBB", 2);
int i = empdao.insertEmp(emp);
<insert id="insertEmp">
<selectKey order="BEFORE" resultType="integer" keyProperty="id">select Max(id)+1 from empselectKey>
insert into Emp(id,name,deptId) values(#{id},#{name},#{deptId})
insert>
selectKey标签的属性。
(2)查询
传参到底能传什么?
<select id="getEmpByIdAndName" resultType="nuc.pojo.Emp">
select * from emp where id=#{id} and name=#{name}
select>
多个参数时,#{} 取不出来。报错如下:
Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
这样才是正确的:或者使用 param1, param2
<select id="getEmpByIdAndName" resultType="nuc.pojo.Emp">
select * from emp where id=#{arg0} and name=#{arg1}
select>
如果只有一个参数时,在SQL语句中取参数时,使用#{} ,大括号中无论写什么都能正确获取到参数。
现象:
单个参数:
多个参数:(推荐在方法命名时使用 @Param
命名参数)
取值:#{参数名}是无效的。只能使用#{param1……}
我们可以告诉MyBatis,封装map的时候,使用我们指定的key。使用 @Param 注解
Emp getEmpByIdAndName(@Param("id") Integer id, @Param("name") String name);
所以,@Param 为参数指定key。命名参数。
传入pojo:
传入Map:可以直接传入一个Map,将要使用的参数封装起来。
Emp getEmpByMap(Map<String,Object> map);
所以,#{}的本意就是去Map中取值。
EmpDao接口:
package nuc.inter;
public interface EmpDao {
Emp getEmpByMap(Map<String,Object> map);
}
EmpDao.xml实现文件:
<select id="getEmpByMap" resultType="nuc.pojo.Emp">
select * from emp where id=#{testId} and name=#{testName}
select>
传入Map作为参数的测试:
@Test
public void test01()
{
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao empdao = sqlSession.getMapper(EmpDao.class);
Map<String, Object> map = new HashMap<>();
map.put("testId",2);
map.put("testName","李四");
Emp emp = empdao.getEmpByMap(map);
System.out.println(emp);
}
多个参数的情况下:MyBatis会自动封装Map。
扩展:
Dao接口中的方法:
Emp method(@Param("id") Integer id, @Param("name") String name, @Param("emp") Emp emp);
取值:
#{id}
#{name}
#{emp.name} 取这个对象的name属性。
传参推荐使用Map。
在MyBatis中有两种取值方式:
#{}:是参数预编译的方式,参数的位置都是?,参数后来都是预编译设置进去的。
${}:不是预编译方式,而是直接和SQL进行拼串。
select * from ${tableName} where id=${testId} and name=#{testName}
(1)查询多条记录,返回一个List
List getAllEmp();
<select id="getAllEmp" resultType="nuc.pojo.Emp">
select * from emp
select>
(2)查询单条记录,返回类型写Map
Map<String,Object> getEmpByIdReturnMap(Integer id);
(3)查询多条记录,封装为Map。
@MapKey("id")
Map<Integer,Emp> getAllEmpReturnMap();
<select id="getAllEmpReturnMap" resultType="nuc.pojo.Emp">
select * from emp
select>
(4)自定义封装规则
MyBatis默认自动封装结果集:
按照列名和属性名一一对应的规则。(不区分大小写)
如果不一一对应
自定义封装:
resultMap="myEmp"
查出数据封装结果的时候,使用myEmp自定义的规则封装 。
标签自定义封装规则:
标签指定主键的对应规则
标签定义普通列的对应规则。自定义结果集代码示例:
<select id="getAllEmpReturnMap" resultMap="myEmp">
select * from emp
select>
<resultMap id="myEmp" type="nuc.pojo.Emp">
<id column="id" property="id">id>
<result property="name" column="Cname">result>
resultMap>
(1)如果JavaBean中还有一个JavaBean
(2)代码实现:
JavaBean—>Key类:
package nuc.pojo;
/**
* 查询钥匙的时候,顺便将这把钥匙能开的锁也查出来,封装进lock对象中。
*/
public class Key {
private Integer id;
private String keyName;
/**
* 表示当前这把钥匙能开哪个锁
*/
private Lock lock;
//省略getter、setter、toString
}
JavaBean—>Lock类:
package nuc.pojo;
public class Lock {
private Integer id;
private String LockName;
//省略getter、setter、toString
}
KeyDao接口:
public interface KeyDao {
/**
* 将钥匙和锁的信息一起查询出来
* @param id
* @return
*/
Key getKeyById(Integer id);
}
KeyDao.xml:
使用“左外连接查询”
SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.lockname FROM t_key k LEFT JOIN t_lock l ON k.`lockid`=l.`id` WHERE k.id=1;
在多表查询时,结果中有两个字段是同名的,为了后面的查询操作方便封装,需要在SQL中起别名。
在级联查询时,使用自定义封装:
标签:
<mapper namespace="nuc.inter.KeyDao">
<select id="getKeyById" resultMap="myKey">
SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.lockname FROM t_key k
LEFT JOIN t_lock l ON k.`lockid`=l.`id`
WHERE k.id=#{id};
select>
<resultMap id="myKey" type="nuc.pojo.Key">
<id property="id" column="id">id>
<result property="keyName" column="keyname"/>
<result property="lock.id" column="lid"/>
<result property="lock.LockName" column="lockname"/>
resultMap>
mapper>
上面使用的时级联赋值,MyBatis中推荐使用的是association 标签
实例:
<resultMap id="myKey" type="nuc.pojo.Key">
<id property="id" column="id">id>
<result property="keyName" column="keyname"/>
<association property="lock" javaType="nuc.pojo.Lock">
<id property="id" column="lid"/>
<result property="LockName" column="lockname"/>
association>
resultMap>
(3)一个JavaBean中的属性是一个集合。
需求:查锁子,也要查询出该锁子的所有钥匙:
Lock类:
public class Lock {
private Integer id;
private String LockName;
/**
* 查询锁子的时候,将该锁子的所有钥匙都查出来,放在这个List中
*/
private List<Key> keys;
//省略set,get方法
}
Key类:
public class Key {
private Integer id;
private String keyName;
/**
* 表示当前这把钥匙能开哪个锁
*/
private Lock lock;
//省略set,get方法
}
LockDao接口:
public interface LockDao {
/**
* 查询所有锁子,并将该锁子的所有钥匙都查出来
*/
Lock getLockById(Integer id);
}
LockDao.xml:
中使用
标签定义这个集合的封装规则。
<mapper namespace="nuc.inter.LockDao">
<select id="getLockById" resultMap="myLock">
SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.lockname
FROM t_key k
LEFT JOIN t_lock l ON k.`lockid`=l.`id`
WHERE l.id=#{id}
select>
<resultMap id="myLock" type="nuc.pojo.Lock">
<id property="id" column="lid"/>
<result property="LockName" column="lockname"/>
<collection property="keys" ofType="nuc.pojo.Key">
<id property="id" column="kid"/>
<result property="keyName" column="keyname"/>
collection>
resultMap>
mapper>
SQL查询结果:
扩展思考:
1-1:一个key开一个lock
1-n:一个lock有多个key
n-n:一个老师教多个学生,一个学生又有多个老师
问题:1-n,n-1,n-n:外键放在那个表中呢?
老师表:
学生表:
学生-老师关系表:
查钥匙的时候,顺便查出锁子
写两个简单的select语句,先查询出钥匙,再根据查询结果中的lockid查询出对应的锁子。
LockDao.xml中简单的select语句,根据id查询锁子。
<mapper namespace="nuc.inter.LockDao">
<select id="getLockByIdSimple" resultType="nuc.pojo.Lock">
select * from t_lock where id=#{id}
select>
mapper>
KeyDao.xml:
association
标签说明这个属性是JavaBean,然后再使用select属性指定一个查询SQL,使用column给这个SQL传递参数。
<mapper namespace="nuc.inter.KeyDao">
<select id="getKeyByIdSimple" resultMap="myKey02">
select * from t_key where id=#{id}
select>
<resultMap id="myKey02" type="nuc.pojo.Key">
<id property="id" column="id"/>
<result property="keyName" column="keyname"/>
<association property="lock" select="nuc.inter.LockDao.getLockByIdSimple" column="lockid"/>
resultMap>
mapper>
需求:在如下的案例中
需求中通常只需要查询key的名,lock只是偶尔会用到,但是MyBatis还是会每次都查询锁子,造成了浪费。
解决:只需开启全局按需加载功能:
在MyBatis的配置文件中设置:
开启延迟加载功能:lazyLoadingEnabled 置为true。
开启属性按需加载:aggressiveLazyLoading置为false
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
@Test
public void test04()
{
SqlSession sqlSession = sqlSessionFactory.openSession();
KeyDao keyDao = sqlSession.getMapper(KeyDao.class);
Key key = keyDao.getKeyByIdSimple(1);
//如果
System.out.println(key.getKeyName());
//解决数据库资源的浪费:按需加载--->需要的时候才去查询
//只需开启全局按需加载功能
}
因为代码中只需要key的name,所有就只执行一条SQL,只查询key的信息。
当需要查询lock的信息时,只管拿即可,MyBatis会自动去查询。
例:给上述测试代码加上:System.out.println(key.getLock().getLockName());
,之后,MyBatis就会执行两条SQL去查询出锁子的信息。
设置了懒加载之后,也可以在resultMap的association标签中使用,fetchType属性开启立即加载。eager表示立即加载。写上之后,就会覆盖MyBatis的全局设置。
<resultMap id="myKey02" type="nuc.pojo.Key">
<id property="id" column="id"/>
<result property="keyName" column="keyname"/>
<association property="lock" select="nuc.inter.LockDao.getLockByIdSimple" column="lockid" fetchType="eager"/>
resultMap>
collection的分步查询和association类似。
推荐:使用连接查询,因为分步查询的效率不如连接查询
动态SQL时MyBatis最强大的功能,极大的简化我们拼装SQL的操作。
有如下四个基本语法:
(1)if标签
&
<select id="getTeacherByCondition" resultMap="myTeacher">
select * from t_teacher where
<if test="id!=null">
id > #{id} and
if>
<if test="teacherName!=null">
teacherName like #{teacherName}
if>
select>
(2) where标签
(3)trim标签,截取字符串
prefix="" :前缀,为我们下面的SQL整体添加一个前缀。
prefixOverrides="":取出多余的字符串,例:and
suffixOverrides="" :去掉多余的字符串,例:AMD
suffix="":为所有SQL添加后缀
推荐使用where标签,并将所有的and写在前面。
(4)foreach标签:遍历集合
<select id="getTeacherList" resultType="myTeacher">
select * from t_teacher where id IN
<where>
<foreach collection="ids" item="id_item" open=" (" close=")" separator=",">
#{id_item}
foreach>
where>
select>
(5)choose标签
<select id="getTeahcers" resultType="myTeacher">
select * from t_teacher
<where>
<choose>
<when test="id!=null">
id = #{id}
when>
<when test="name!=null">
teachername=#{name}
when>
<when test="birth!=null">
birth_date=#{birth}
when>
<otherwise>
1=1
otherwise>
choose>
where>
select>
(6)set标签,update语句使用
<update id="updateTeacher">
update t_teacher
<set>
<if test="teacherName!=null">
teachername = #{teacherName},
if>
<if test="className!=null">
class_name=#{className},
if>
set>
<where>
id=#{id}
where>
update>
(7)bind标签
绑定一个表达式给一个变量。
(8)sql标签:抽取可重用的SQL语句
<sql id="sql01">
select * from t_teacher
sql>
<select id="getTeacherById" resultMap="myTeacher">
<include refid="sql01">include>
select>
OGNL:( Object Graph Navigation Language )对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等。
有类似以下的类,属性如下:
class Person{
lastName;
email;
Address;
city;
province;
street;
}
用点分隔各层属性,就是导航图。如:person.Address.street
OGNL 不仅可以访问属性,也可以访问方法,静态方法,构造方法,运算符,逻辑运算符。
在MyBatis中,除了可以使用传入的参数来判断外,还可以使用两个额外的参数。
_parameter
代表传入的参数
_databaseId
代表当前环境:MySQL,Oracle等
查询的时候,先看缓存中有没有,没有的话再去数据库中查。
(1)一级缓存
默认存在的。只要之前查询过的数据,MyBatis就会将其保存在缓存中(Map中),下次直接从缓存中拿。
@Test
public void test01()
{
SqlSession sqlSession = sqlSessionFactory.openSession();
TeacherDao teacherDao = sqlSession.getMapper(TeacherDao.class);
Teacher teacher01 = teacherDao.getTeacherById(1);
Teacher teacher02 = teacherDao.getTeacherById(1);
System.out.println(teacher01==teacher02);
}
输出:只执行一次SQL语句
一级缓存失效的情况:
/**
* 一级缓存失效的几种情况:
*
*/
@Test
public void test02()
{
//第一次会话
SqlSession sqlSession01 = sqlSessionFactory.openSession();
TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);
Teacher teacher01 = teacherDao01.getTeacherById(1);
//第二次会话
SqlSession sqlSession02 =sqlSessionFactory.openSession();
TeacherDao teacherDao02 = sqlSession02.getMapper(TeacherDao.class);
Teacher teacher02 = teacherDao02.getTeacherById(1);
System.out.println(teacher01==teacher02);//false
}
输出:执行了两次SQL,查询数据库两次。
@Test
public void test02()
{
//第一次会话
SqlSession sqlSession01 = sqlSessionFactory.openSession();
TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);
Teacher teacher01 = teacherDao01.getTeacherById(1);
//执行一次增删改操作
Teacher teacher = new Teacher();
teacher.setId(2);
teacher.setTeacherName("Test");
teacherDao01.updateTeacher(teacher);
Teacher teacher02 = teacherDao01.getTeacherById(1);
System.out.println(teacher01==teacher02);//false
sqlSession01.commit();
}
输出:在一个sqlSession中查询同样的数据,也会执行两次SQL语句。
@Test
public void test02()
{
//第一次会话
SqlSession sqlSession01 = sqlSessionFactory.openSession();
TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);
Teacher teacher01 = teacherDao01.getTeacherById(1);
//手动清空缓存
sqlSession01.clearCache();
Teacher teacher02 = teacherDao01.getTeacherById(1);
System.out.println(teacher01==teacher02);//false
}
总结:每次查询,先去缓存中查看有没有该数据,如果没有才发起新的SQL。每个sqlSession拥有自己的缓存。
MyBatis的缓存就是一个Map,如下:
保存的数据的key:hashCode+查询的SqlId+编写的sql查询语句+参数,我们可以打开它的源码看一下。
(2)二级缓存
MyBatis中使用二级缓存的步骤:
<setting name="cacheEnabled" value="true"/>
<cache/>
测试代码:
@Test
public void test_03()
{
//创建两个会话
SqlSession sqlSession01 = sqlSessionFactory.openSession();
SqlSession sqlSession02 = sqlSessionFactory.openSession();
UserDao UserDao01 = sqlSession01.getMapper(UserDao.class);
UserDao UserDao02 = sqlSession02.getMapper(UserDao.class);
//第一个Dao查询一号用户
User user1 = UserDao01.getUserById(1);
sqlSession01.close();
System.out.println("user1: "+user1);
//第二个Dao也查询一号用户
User user2 = UserDao02.getUserById(1);
sqlSession02.close();
System.out.println("user2: "+user2);
System.out.println("user1是否与user2相同:"+(user1==user2));
}
}
输出:
显然只执行一次SQL
二级缓存总结:
二级缓存是namespace级别的缓存,哪个Dao要用缓存,就配置在哪个Dao的实现文件中。
在Dao中的
标签中可以配置的属性:
不会出现二级缓存和一级缓存中有同一个数据。
任何时候都是先看二级缓存,再看一级缓存,再数据库。
缓存原理:
sql标签(增删改查)的flushCache属性:
因为MyBatis的缓存太过简陋,就是一个Map。
MyBatis将Cache做成了一个接口,可以使用其他第三方缓存进行代替。
(1)导入jar包
ehcache-core:ehcache的核心包
mybatis-ehcache:ehcache和MyBatis的整合包
依赖的日志包:
slf4j-api:
slf4j-log4j12:
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcache-coreartifactId>
<version>2.6.11version>
dependency>
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>2.0.0-alpha1version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>2.0.0-alpha1version>
<scope>testscope>
dependency>
(2)ehcache的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="E:\ehcache" />
<defaultCache
maxElementsInMemory="100000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
(3)在Mapper.xml文件中配置使用自定义的缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache">cache>
【系列文章】
1. Git&GitHub(基础)
2. Git&GitHub(进阶)
3. Java多线程
4. JavaScript 总结
5. SpringMVC(一)
6. SpringMVC(二)
……
关注博主