MyBatis授课笔记3
1 动态SQL
2 foreach
public List public List |
---|
<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 Collections.addAll(idList,1,7,10); List 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级别 默认没有开启
8 MyBatis缓存访问步骤
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- 从数据库查询的数据,会直接放入到一级缓存
- 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);
} | | —- |
什么时候一级缓存失效
- 不是同一个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);
//使用两个SqlSession发送同一个请求<br /> Employee emp1 = mapper1.findById(1);<br /> System.**_out_**.println(emp1);<br /> //!!! 关闭SqlSession,将该SqlSession一级缓存中数据存入二级缓存<br /> // sqlSession1.close();
Employee emp2 = mapper2.findById(1);<br /> System.**_out_**.println(emp2);<br />} |
| —- |
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。
15 指明二级缓存使用Ehcache
<cache type=”org.mybatis.caches.ehcache.EhcacheCache”/> |
---|
16 测试查看结果
17 理解Ehcache的配置项
18 日志框架
19 认识日志框架
20 体验logback
- 添加依赖
| <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency> | | —- |
- 添加配置文件
| <?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> | | —- |
- 体验
21 缓存源码
22 Cache相关的API
缓存(一级和二级缓存)的顶级接口:org.apache.ibatis.cache.Cache
缓存的实现类:org.apache.ibatis.cache.impl.PerpetualCache
缓存的(第三方的)实现类:org.mybatis.caches.ehcache.EhcacheCache
缓存的其他实现类:(装饰类)
装饰模式:
蛋糕接口:(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,存储缓存的信息
所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。
org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过PerpetualCache来操作缓存数据的。但是这就奇怪了,同样是PerpetualCache这个类,怎么能区分出来两种不同级别的缓存呢?
其实很简单,调用者不同。
- 一级缓存:由BaseExecutor调用PerpetualCache
- 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰
23 一级Cache的源码
24 二级Cache的源码
使用默认的二级Cache:PerpetualCache
使用第三方的EhCacheCache
源码:
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 开始逆向并欣赏结果
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内置类型处理器
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)。
35 Mybatis底层四大对象:
四大对象:SqlSessionFactory SqlSession Mapper ?? 错了,都错了
四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler
①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 |
---|
37 MyBatis总结
- 概述
1. 工作在持久层的框架,对JDBC进行了封装 1. 理解ORM (对象关系映射) 1. 和Hibernate对比(半自动化战胜了全自动化的)
- 单表操作
1. 使用SqlSession的方法完成单表CRUD操作(selectList,selectOne,insert) 1. 使用Mapper代理(接口绑定)完成单表CRUD操作 1. 数据输入 (#{?????}) 1. 数据输出(resultType ???)
- 多表操作(重点难点)
1. 多表连接查询:立即查询,效率高,不灵活 1. 分步查询+延迟加载 效率低,可以立即查询,也可以延迟查询,灵活 1. 重点掌握一对多操作,在此基础上掌握多对多,一对一
- 动态SQL语句
1. 主要解决多条件查询情况下条件的处理,避免了使用StringBuilder进行append操作 1. 在xml中进行配置,而不是编码 1. 其实不仅针对select操作,某些动态SQL也针对DML操作
- 缓存
1. 一级缓存默认开启,二级缓存默认不开启 1. 先查询二级缓存,再查询一级缓存
- MyBatis源码
1. 一级缓存、二级缓存的源码 1. 四大对象 1. 四大步骤 1. 通过SqlSessionFactory读取配置文件(映射文件)信息到内存