MyBatis授课笔记3

1 动态SQL

2 foreach

public List findEmp5(@Param(“idArr”) int [] idArr);
public List findEmp6(@Param(“idList”)List idList);

<select id=”findEmp5” resultType=”employee”>
select _from t_emp where emp_id in
<foreach collection=”idArr” item=”id” open=”(“ close=”)” separator=”,”>
#{id}
</foreach>
</*select
>
<select id=”findEmp6” resultType=”employee”>
select
_from t_emp where emp_id in
<foreach collection=”idList” item=”id” open=”(“ close=”)” separator=”,”>
#{id}
</foreach>
</*select
>
@Testpublic void testFindEmp2(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//int [] idArr = {1,7,10,20};
//List list = mapper.findEmp5(idArr);
List idList = new ArrayList<>();
Collections.addAll(idList,1,7,10);
List list = mapper.findEmp6(idList);
list.forEach(emp-> System.out.println(emp));
}
注意事项
1. 如果参数是数组,没有使用@Param,可以使用array来接收
1. 如果参数是List,没有使用@Param,可以使用collection、list来接收

3 set

针对update操作,会自动的去掉多余的,

public int updateEmp(Employee emp);
<update id=”updateEmp”>
update t_emp
<set>
<if test=”empName!=null and empName!=’’”>
emp_name = #{empName},
</if>
<if test=”empSalary>0”>
emp_salary = #{empSalary}
</if>
</set>
where emp_id = #{empId}
</update>
@Test
public void testUpdateEmp(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//Employee emp = new Employee(20,”zhangsan2”,450.0);
//Employee emp = new Employee(20,null,450.0);
// Employee emp = new Employee(20,””,450.0);
Employee emp = new Employee(20,“zhangsan3”,0.0);
mapper.updateEmp(emp);
}

4 sql

提取SQL语句的公共部门,并使用include标签来引用。便于修改。

<sql id=”empColumns”>
emp_id,emp_name,emp_salary
</sql>
select <include refid=”empColumns”></include> from t_emp where 1=1

5 缓存

6 缓存作用

如果缓存中有需要的数据,就直接读取缓存中数据,而不去读取硬盘。减少对硬盘(数据库)的访问次数,提高了效率。

7 MyBatis缓存分类

一级缓存:SqlSession级别 默认开启
二级缓存:SqlSessionFactory级别 默认没有开启
MyBatis授课笔记3 - 图1

8 MyBatis缓存访问步骤

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. 从数据库查询的数据,会直接放入到一级缓存
  5. SqlSession关闭之前,一级缓存中的数据会写入二级缓存

    9 一级缓存

    | @Testpublic void testCache1(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.findById(1);
    System.out.println(emp);
    //清空一级缓存
    sqlSession.clearCache();
    //提交事务,也会清空一级缓存
    //sqlSession.commit();
    Employee emp2 = mapper.findById(1);
    System.out.println(emp2);
    Employee emp3 = mapper.findById(2);
    System.out.println(emp3);
    } | | —- |

MyBatis授课笔记3 - 图2
MyBatis授课笔记3 - 图3
什么时候一级缓存失效

  • 不是同一个SqlSession
  • 同一个SqlSession但是查询条件发生了变化
  • 同一个SqlSession两次查询期间手动清空了缓存
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间提交了事务

    10 二级缓存

    1.全局开关:默认开启

    | <settings>

    <setting name=”cacheEnabled” value=”true”/>
    </settings> | | —- |

2.局部开关:二级缓存以namespace为单位。

<mapper namespace=”com.atguigu.mapper.EmployeeMapper”>

<cache/>
<select id=”findById” resultType=”employee”>
select _ _from t_emp where emp_id = #{empId}
</select>
</*mapper
>

3.实体类要序列化

java.io.NotSerializableException: com.atguigu.entity.Employee
什么时候需要序列化:在在内存中存储不需要序列化,如果内存的数据要存入硬盘,或者在网络上传输,就要序列化(其实是变成字节数组)
一级缓存肯定在内存中,二级缓存可能在内存中,但是二级缓存数据多,也可以存储到外存中,所以存在二级缓存中的数据必须序列化。

@Data
@AllArgsConstructor
@NoArgsConstructorpublic class Employee implements Serializable { //t_emp
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary}

4.测试类

| @Testpublic void testCache2(){
//由一个工厂创建两个SqlSession
SqlSession sqlSession1 = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);

  1. //使用两个SqlSession发送同一个请求<br /> Employee emp1 = mapper1.findById(1);<br /> System.**_out_**.println(emp1);<br /> //!!! 关闭SqlSession,将该SqlSession一级缓存中数据存入二级缓存<br /> // sqlSession1.close();
  2. Employee emp2 = mapper2.findById(1);<br /> System.**_out_**.println(emp2);<br />} |

| —- |

MyBatis授课笔记3 - 图4

MyBatis授课笔记3 - 图5
MyBatis授课笔记3 - 图6

5.二级缓存分开关的设置

<cache type=”” size=”” blocking=”” eviction=”” flushInterval=”” readOnly=””/>

  • Type:指定具体的二级缓存类型。如果不指定,就使用MyBatis自带的二级缓存。
  • Size:代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • flushInterval:刷新(清空缓存)间隔。默认情况是不设置,也就是没有刷新间隔
  • readOnly:只读。True:性能高 false,性能低,安全性高
  • Eviction:删除。比如二级缓存中最多放100个缓存内容,第101来了,怎么办?
    • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
  • Blocking 阻塞:访问某个缓存数据,在访问过程中对key(sql语句)加锁,其他的请求同一个SQL语句,要等待。保证了只有一个请求来访问缓存中的一份数据。

    11 整合Ehcache

    Ehcache是一种开源的、基于标准的缓存,它可以提高性能、减轻数据库负担并简化可伸缩性。它是使用最广泛的基于Java的缓存,因为它健壮、可靠、功能齐全,并与其他流行的库和框架集成。Ehcache可以从进程内缓存扩展到进程内/进程外混合部署和TB大小的缓存。

    12 添加依赖

    | <dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
    </dependency> | | —- |

13 配置文件

在resources目录下创建ehcache.xml

| <?xml version=”1.0” encoding=”utf-8” ?><ehcache xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation=”../config/ehcache.xsd”
>

<diskStore path=”D:\atguigu\ehcache”/>

<**defaultCache<br />            maxElementsInMemory="1000"<br />            maxElementsOnDisk="10000000"<br />            eternal="false"<br />            overflowToDisk="true"<br />            timeToIdleSeconds="120"<br />            timeToLiveSeconds="120"<br />            diskExpiryThreadIntervalSeconds="120"<br />            memoryStoreEvictionPolicy="LRU"**><br />    </**defaultCache**><br /></**ehcache**> |

| —- |

14 加入log4j适配器依赖

存在SLF4J时,是无法直接访问log4j的,需要加入一个适配器类:slf4j-log4j12。



org.slf4j
slf4j-log4j12
1.7.30
test

15 指明二级缓存使用Ehcache

<cache type=”org.mybatis.caches.ehcache.EhcacheCache”/>

16 测试查看结果

MyBatis授课笔记3 - 图7

17 理解Ehcache的配置项

18 日志框架

19 认识日志框架

MyBatis授课笔记3 - 图8

MyBatis授课笔记3 - 图9

20 体验logback

  1. 添加依赖 | <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency> | | —- |
  1. 添加配置文件 | <?xml version=”1.0” encoding=”UTF-8”?><configuration debug=”true”>

    <appender name=”STDOUT”
    class=”ch.qos.logback.core.ConsoleAppender”
    >
    <encoder>


    <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
    </encoder>
    </appender>


    <root level=”DEBUG”>

    <appender-ref ref=”STDOUT” />
    </root>

    <logger name=”com.atguigu.crowd.mapper” level=”INFO”/>
    </configuration> | | —- |
  1. 体验

MyBatis授课笔记3 - 图10

MyBatis授课笔记3 - 图11

MyBatis授课笔记3 - 图12

21 缓存源码

22 Cache相关的API

缓存(一级和二级缓存)的顶级接口:org.apache.ibatis.cache.Cache
缓存的实现类:org.apache.ibatis.cache.impl.PerpetualCache
缓存的(第三方的)实现类:org.mybatis.caches.ehcache.EhcacheCache
缓存的其他实现类:(装饰类)
MyBatis授课笔记3 - 图13

装饰模式
蛋糕接口:(Cache)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(PerpetualCache EhcacheCache)
蛋糕的装饰:卡片、干果、蜡烛(FifoCache、LruCache是添加了装饰的蛋糕)
如果采用继承,就会数不清的实现类。采用装饰模式,可以减少子类的数量,是继承的一种替代方案。现场组装。
蛋糕接口:(InputStream)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(FileInputStream ByteArrayInputStream)
蛋糕的装饰:卡片、干果、蜡烛(BufferedInputStream DataInputStream)

InputStream is = new FileInputStream(“readme.txt”);
BufferedInputStream bis = new BufferedInputStream(is );
DataInputStream dis = new DataInputStream(bis);

DataInputStream dis =
new DataInputStream(new BufferedInputStream( new FileInputStream(“readme.txt”)));

Cache内部有一个Map,存储缓存的信息
MyBatis授课笔记3 - 图14
所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。

MyBatis授课笔记3 - 图15
org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过PerpetualCache来操作缓存数据的。但是这就奇怪了,同样是PerpetualCache这个类,怎么能区分出来两种不同级别的缓存呢?
其实很简单,调用者不同。

  • 一级缓存:由BaseExecutor调用PerpetualCache
  • 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰

    23 一级Cache的源码

    MyBatis授课笔记3 - 图16

MyBatis授课笔记3 - 图17

24 二级Cache的源码

使用默认的二级Cache:PerpetualCache
MyBatis授课笔记3 - 图18

使用第三方的EhCacheCache
MyBatis授课笔记3 - 图19

源码:
MyBatis授课笔记3 - 图20

MyBatis的缓存查询过程:
先查询二级缓存,
二级缓存中没有,再查询一级缓存。
一级缓存中没有,就查询数据库
从数据库中查询到数据,放入到一级缓存。

25 逆向工程

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
  • 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:实体类、Mapper接口、映射文件

    26 添加插件

    | <build>

    <plugins>

    <plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.0</version>

    <dependencies>

    <dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.2</version>
    </dependency>

    <dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.2</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.8</version>
    </dependency>
    </dependencies>
    </plugin>
    </plugins>
    </build> | | —- |

27 添加配置文件

<?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>

<context id=”DB2Tables” targetRuntime=”MyBatis3Simple”>

<jdbcConnection driverClass=”com.mysql.jdbc.Driver”
connectionURL=”jdbc:mysql://localhost:3306/mybatis-example”
userId=”root”
password=”root”
>
</jdbcConnection>

<javaModelGenerator targetPackage=”com.atguigu.entity” targetProject=”.\src\main\java”>
<property name=”enableSubPackages” value=”true” />
<property name=”trimStrings” value=”true” />
</javaModelGenerator>

<sqlMapGenerator targetPackage=”mappers” targetProject=”.\src\main\resources”>
<property name=”enableSubPackages” value=”true” />
</sqlMapGenerator>

<javaClientGenerator type=”XMLMAPPER” targetPackage=”com.atguigu.mapper” targetProject=”.\src\main\java”>
<property name=”enableSubPackages” value=”true” />
</javaClientGenerator>



<table tableName=”t_emp” domainObjectName=”Employee”/>
<table tableName=”t_customer” domainObjectName=”Customer”/>
<table tableName=”t_order” domainObjectName=”Order”/>
</context>
</generatorConfiguration>

28 开始逆向并欣赏结果

MyBatis授课笔记3 - 图21

29 进行测试

30 QBC(了解)

QBC:Query By Criteria
QBC查询最大的特点就是将SQL语句中的WHERE子句进行了组件化的封装,让我们可以通过调用Criteria对象的方法自由的拼装查询条件。

31 Mapper映射

<mappers>

<package name=”com.atguigu.mapper”/>
</mappers>

要求是:

  • Mapper接口和Mapper配置文件名称一致
  • Mapper配置文件放在Mapper接口所在的包内

    32 插件机制(了解)

    插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。著名的Mybatis插件包括 PageHelper(分页插件)、通用 Mapper(SQL生成插件)等。

如果想编写自己的Mybatis插件可以通过实现org.apache.ibatis.plugin.Interceptor接口来完成,表示对Mybatis常规操作进行拦截,加入自定义逻辑

但是由于插件涉及到Mybatis底层工作机制,在没有足够把握时不要轻易尝试。

33 类型处理器typeHandler(了解)

1、Mybatis内置类型处理器

MyBatis授课笔记3 - 图22

2、Mybatis自定义类型处理器

1.开发自定义类型转换器

| public class AddressTypeHandler extends BaseTypeHandler

{
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) throws SQLException {

}

@Override<br />    **public **Address getNullableResult(ResultSet rs, String columnName) **throws **SQLException {<br />        //获取当前列的数据<br />       String str =  rs.getString(columnName);//"河北省,张家口市,崇礼县"<br />        //处理该列的数据转换为Address<br />        String[] arr = str.split(**","**);<br />        Address address = **new **Address(arr[0],arr[1],arr[2]);<br />        //返回Address<br />        **return **address;<br />    }

@Override<br />    **public **Address getNullableResult(ResultSet rs, **int **columnIndex) **throws **SQLException {<br />        **return null**;<br />    }

@Override<br />    **public **Address getNullableResult(CallableStatement cs, **int **columnIndex) **throws **SQLException {<br />        **return null**;<br />    }<br />} |

| —- |

2.在配置文件中进行注册

<typeHandlers>
<typeHandler handler=”com.atguigu.typehandler.AddressTypeHandler”
javaType=”com.atguigu.entity.Address”
jdbcType=”VARCHAR”
></typeHandler>
</typeHandlers>

3.修改实体类

@Data
@AllArgsConstructor
@NoArgsConstructorpublic class Address {
private String province;
private String city;
private String county;
}
@Data
@AllArgsConstructor
@NoArgsConstructorpublic class Employee implements Serializable { //t_emp
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary
//private String empAddress;
private Address empAddress;
}

34 MyBatis底层对JDBC的封装

目前为止,但是还没有看见过Connection、Statement、ResultSet。

问题1:SqlSessionFactory就相当于DriverMangaer,SqlSession就相当于Connection,Mapper就相当于Statement??错了,都错了。

SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor(BaseExecutor和CacheingExecutor)。
MyBatis授课笔记3 - 图23

35 Mybatis底层四大对象:

四大对象:SqlSessionFactory SqlSession Mapper ?? 错了,都错了
四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler

MyBatis授课笔记3 - 图24

①Executor

执行器,由它来调度StatementHandler、并由StatementHandler来调度ParameterHandler、ResultSetHandler等来执行对应的SQL。

②StatementHandler

使用数据库的Statemet(PreparedStatement)执行操作

③ParameterHandler

处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。

④ResultSetHandler

处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。

36 Mybatis底层四大步骤:

SimpleExecutor中
doQuery() select ——————>JDBC executeQuery()
doUpdate() insert update delete————->jDBC executeUpdate()

核心步骤就是四步:
第一步:获取数据库连接。
第二步:调用StatementHandler的prepare()方法,创建Statement对象并设置其超时、获取的最大行数等。
第三步:调用StatementHandler的parameterize()方法。负责将具体的参数传递给SQL语句。
第四步:调用StatementHandler的query()/update方法。完成查询/DML操作,并对结果集进行封装处理,然后返回最终结果。

第一步:获取数据库连接
Connection connection = getConnection(statementLog);
第二步:创建Statement(PreparedStatement)
Statement stmt = handler.prepare(connection, transaction.getTimeout());
第三步:处理参数
handler.parameterize(stmt);
第四步:执行CRUD操作
return handler.update(stmt);// insert update delete 返回 int
return handler.query(stmt, resultHandler)
//select 返回List 底层会用ResultSetHandler

MyBatis授课笔记3 - 图25

37 MyBatis总结

  1. 概述
      1. 工作在持久层的框架,对JDBC进行了封装
      1. 理解ORM (对象关系映射)
      1. 和Hibernate对比(半自动化战胜了全自动化的)
    
  2. 单表操作
      1. 使用SqlSession的方法完成单表CRUD操作(selectList,selectOne,insert)
      1. 使用Mapper代理(接口绑定)完成单表CRUD操作
      1. 数据输入 (#{?????})
      1. 数据输出(resultType ???)
    
  3. 多表操作(重点难点)
      1. 多表连接查询:立即查询,效率高,不灵活
      1. 分步查询+延迟加载  效率低,可以立即查询,也可以延迟查询,灵活
      1. 重点掌握一对多操作,在此基础上掌握多对多,一对一
    
  4. 动态SQL语句
      1. 主要解决多条件查询情况下条件的处理,避免了使用StringBuilder进行append操作
      1. 在xml中进行配置,而不是编码
      1. 其实不仅针对select操作,某些动态SQL也针对DML操作
    
  5. 缓存
      1. 一级缓存默认开启,二级缓存默认不开启
      1. 先查询二级缓存,再查询一级缓存
    
  6. MyBatis源码
      1. 一级缓存、二级缓存的源码
      1. 四大对象
      1. 四大步骤
      1. 通过SqlSessionFactory读取配置文件(映射文件)信息到内存