[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHgy2SXp-1624636948861)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210425120606038.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMo2pABo-1624636948864)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210425121418908.png)]
mybatis运行原理:
1.通过加载mybatis全局配置文件以及mapper映射文件初始化configuration对象和Executor对象(通过全局配置文件中的defaultExecutorType初始化);
2.创建一个defaultSqlSession对象,将configuration对象和Executor对象注入给defaulSqlSession对象中;
3.defaulSqlSession通过getMapper()获取mapper接口的代理对象mapperProxy(mapperProxy中包含defaultSQLSession对象)
4.执行增删改查:
1)通过defaulSqlSession中的属性Executor创建statementHandler对象;
2)创建statementHandler对象的同时也创建parameterHandler和
resultSetHandler;
3) 通过parameterHandler设置预编译参数及参数值;
4)调用statementHandler执行增删改查;
5)通过resultsetHandler封装查询结果
1.MyBatis简介
2.MyBatis-HelloWorld
1.普通编程
1.创建MyBatis全局配置文件
–MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息、如数据库连接池信息等。指导着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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="2580147"/>
</dataSource>
</environment>
</environments>
<!--写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis.xml)中-->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
2.创建SQL映射文件
<?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:名称空间
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<mapper namespace="EmployeeMapper">
<select id="selectEmp" resultType="Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
3.根据全局配置文件,利用SqlSessionFactoryBuilder创建SqlSessionFactory
lic void test() throws IOException {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
4.使用SqlSessionFactory获取sqlSession对象。一个SqlSession对象代表和数据库的一次会话
SqlSession sqlSession = sqlSessionFactory.openSession();
5.使用SqlSession根据方法id进行操作
try {
List<Object> objects = sqlSession.selectList("EmployeeMapper.selectEmp", 1);
System.out.println(objects);
} finally {
sqlSession.close();
}
2.接口式编程
1.创建一个Dao接口
public interface EmployeeDao {
Employee getEmployeeById(Integer id);
}
2.修改Mapper文件
<mapper namespace="dao.EmployeeDao">
<select id="getEmployeeById" resultType="bean.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
3.使用SqlSession获取映射器进行操作
//3.获取接口的实现类对象,会为接口自动创建代理对象,代理对象去实现增删改查
try {
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee employeeById = mapper.getEmployeeById(1);
System.out.println(employeeById);
} finally {
sqlSession.close();
}
3.SqlSession
1.接口式编程
原生: Dao ========> DaoImpl
MyBatis: Mapper =========> xxMapper.xml
2.SqlSession
(1)SqlSession代表和数据库的一次对话, 使用完成后需要关闭
(2)SqlSession和connection一样是非线程安全, 每次使用都应该获取新的对象
(3)mapper接口没有实现类,但是mybati会为这个接口生成一个代理对象
3.MyBatis全局配置文件
1.properties属性
jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMTjdbc.username=rootjdbc.password=2580147
<!-- 1.mybatis可以使用properties来引入properti配置文件的内容 resource:引入类路径下的资源 url:引入网络路径或者磁盘路径下的资源 --> <properties resource="config.properties">
如果属性在不只一个地方进行了配置,那么 MyBatis 将按
照下面的顺序来加载:
在 properties 元素体内指定的属性首先被读取。
然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
最后读取作为方法参数传递的属性,并覆盖已读取的同名属性
2.settings设置
这是MyBatis中极为重要的调整设置, 他们会改变MyBatis的运行行为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fvlkpRFE-1624636948867)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210425205425911.png)]
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/></settings>
3.typeAliases别名处理器
1.类型别名是为Java类型设置一个短的名字,可以方便我们引用某个类
<typeAliases> <!-- typeAlias:为某个java类型起别名 type:指定要起别名的类型全类名,默认别名就是类名小写 alias:制定新的别名 --> <typeAlias type="bean.Employee" alias=""/> </typeAliases>
2.类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写
<typeAliases><package name="bean"/></typeAliases>
3.使用注解@Alias注解起别名
@Alias("emp")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyHtTXMI-1624636948868)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210425212103259.png)]
4.typeHandlers类型处理器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFDKNqXQ-1624636948869)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210426153517879.png)]
日期类型的处理
日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了。
• 日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。
• MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zTSS30Rp-1624636948870)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210426153813622.png)]
5.plugins插件
• 插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。后面会有专门的章节我们来介绍mybatis运行原理以及插件
• Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
• ParameterHandler (getParameterObject, setParameters)
• ResultSetHandler (handleResultSets, handleOutputParameters)
• StatementHandler (prepare, parameterize, batch, update, query)
6.environment环境
• MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
• 每种环境使用一个environment标签进行配置并指定唯一标识符
• 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
environment-指定具体环境
id: 指定当前环境的唯一标识
transactionManager, 和dataSource都必须有
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
transactionManager: 事务管理器
type: 事务管理器类型; JDBC|MANAGED|自定义
– JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。
JdbcTransactionFactory
– MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。
ManagedTransactionFactory
– 自定义:实现TransactionFactory接口,type=全类名/别名
dataSource: 数据源
• type: UNPOOLED | POOLED | JNDI | 自定义
– UNPOOLED:不使用连接池,
UnpooledDataSourceFactory
– POOLED:使用连接池, PooledDataSourceFactory
– JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
– 自定义:实现DataSourceFactory接口,定义数据源的获取方式
实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
7.databaseIdProvider环境
MyBatis 可以根据不同的数据库厂商执行不同的语句
<databaseIdProvider type="DB_VENDOR"> <!--为不同数据库厂商起别名--> <property name="MySQL" value="mysql"/> <property name="Orace" value="orace"/> <property name="SQL Server" value="sql server"/> </databaseIdProvider>
• Type: DB_VENDOR
– 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
• Property-name:数据库厂商标识
• Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用
在mapper文件设置引用起别名的数据库, 使用databaseId属性
<mapper namespace="dao.EmployeeDao"> <select id="getEmployeeById" resultType="employee" databaseId="mysql"> select id,last_name lastName,email,gender from tbl_employee where id = #{id} </select>
8.mapper映射
1.mapper逐个注册SQL映射文件
<mappers> <mapper resource="EmployeeMapper.xml"/> <mapper url=""/> <mapper class=""/> </mappers>
resource: 引用类路径下的sql映射文件
url:应用网络路径或者磁盘路径下的sql映射文件
推荐: 比较重要的Dao接口写sql映射文件
不重要的,简单的Dao接口为了开发快速使用注解
2.接口注册
class: 引用接口
1.有sql映射文件, 要求SQL映射文件名必须和接口名相同并且在同一目录下
2.没有sql映射文件, 所有的sql都是利用注解写在接口上
<mappers><mapper class="com.atguigu.dao"/></mappers>
3.批量注册
<mappers><package name="com.atguigu.dao"/></mappers>
4.MyBatis映射文件
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。
cache-ref
– 引用其它命名空间的缓存配置。
resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
sql
– 可被其它语句引用的可重用语句块。
insert
– 映射插入语句。
update
– 映射更新语句。
delete
– 映射删除语句。
select
– 映射查询语句。
下一部分将从语句本身开始来描述每个元素的细节。
1.select
<select id="getEmployeeById" resultType="bean.Employee"> select id,last_name lastName,email,gender from tbl_employee where id = #{id} </select>
2.insert, update 和 delete
<insert id="insertAuthor"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio})</insert><update id="updateAuthor"> update Author set username = #{username}, password = #{password}, email = #{email}, bio = #{bio} where id = #{id}</update><delete id="deleteAuthor"> delete from Author where id = #{id}</delete>
3.主键生成方式
<!--mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.get useGeneratedKeys="true", 使用自增主键获取主键值 keyProperty="id",指定对应的主键属性, 当mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性 --> <!--添加方法--> <insert id="addEmp" parameterType="bean.Employee" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
2.对于不支持自增型数据库, 则可以使用selectKey子元素:
selectKey 元素将会首先运行,id会被设置,然后插入语句会被调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8ayqtzR-1624636948871)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210428164530651.png)]
4.参数(Parameters)传递
单个参数
#{参数名}: 取出参数值
mybatis不会做特殊处理
多个参数
mybatis会做特殊处理
多个参数会被封装成一个map, #{}就是从map中获取指定的key的值
Map的key是param1, param2, 0, 1…, 值是参数的值
select*from tbl_employee where id=#{param1} and last_name=#{param2}
命名参数
明确指定封装参数时map的key
为参数@Param起一个名字,myBatis会将参数装进map
pulic void getIdandEmail(@Param("email")String email,@Param("id")Integer id);
POJO
如果多个参数是业务逻辑的数据模型,直接传入pojo
#{属性名}: 取出传入的pojo属性值
Map
封装成为多个参数为map,直接传递
pulic Employee getEmpByMap(Map<String,Object> map);
TO
如果多个参数不是业务模型中的数据,但是要经常使用,推荐编写TO
Page{ int index; int size;}
5.参数处理
#{}: 可以获取map中的值或者pojo对象属性的值
${}: 可以获取map中的值或者pojo对象属性的值
区别:
#{}: 是以预编译的形式, 将参数设置到sql语句中; PreparedStatement; 防止sql注入
${}: 取出的值直接拼装在sql语句中; 会有安全问题
大多数情况下, 我们的参数值都使用#{}
#{}: 更丰富的用法
规定参数的一些规则
#{property, javaType=int, jdbcType=NUMRIC}
参数支持的属性
javaType、jdbcType、mode(存储过程)、numericScale、
resultMap、typeHandler、jdbcTypeName、expression
jdbcType通常需要在某种特定的条件下被设置
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理
6.select元素
Select元素定义查询操作
Id: 唯一标识符
用来引用这条语句, 需要和接口的方法名一致
parameterType: 参数类型
可以不传, MyBatis会根据TypeHandler自动判断
resultType: 返回值类型
别名或者全类名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-juO9LizC-1624636948872)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210505203952721.png)]
7.自动映射
1.全局setting设置
2.自定义resultMap, 实现高级结果映射
<mapper namespace="dao.EmployeeMapper"> <!--自定义JavaBean的封装规则 id定义主键底层会有优化 type: 自定义规则的Java类型 id: 唯一id方便引用--> <resultMap id="MyEmp" type="bean.Employee"> <!--指定主键列的封装规则 column: 指定哪一列 property: 指定对应的JavaBean属性--> <id column="id" property="id"/> <!--定义普通列封装规则--> <result column="lat_name" property="lastName"/> <!--其他不指定的列会自动封装, 建议全写--> <result column="email" property="email"/> <result column="gender" property="gender"/> </resultMap> <select id="getEmployeeId" resultMap="MyEmp">select * from tbl_employee where id=#{id}</select></mapper>
3.association
联合查询, 并以级联属性封装对象
<!-- 场景一 查询Employee的同时查询员工对应的部门 --> <resultMap id="MyEmp1" type="bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="did" property="dept.id"/> <result column="dept_name" property="dept.name"/> </resultMap> <select id="getEmpandDept" resultMap="MyEmp2">SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept dWHERE e.d_id=d.id AND e.id=#{id}</select>
使用association标签定义对象封装规则
<!--association指定联合的JavaBean对象 property: 指定哪个属性是联合的对象 javaType: 指定这个属性对象的类型--> <resultMap id="MyEmp2" type="bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <association property="dept" javaType="bean.Department"> <id column="did" property="id"/> <result column="dept_name" property="name"/> </association> </resultMap>
association-分步查询
<!-- 使用association分步查询 public Employee getEmpIdStep(Integer id); 1.先按照员工id查询员工信息 2.根据查询的员工id去查出部门信息 3.部门设置到员工中 --> <resultMap id="MyEmp3" type="bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> <!-- 定义封装规则 select: 表明当前属性是调用select指定的方法 column: 指定将那一列的值传给这个方法--> <association property="dept" select="dao.DepartmentMapper.getDepartmentId" column="d_id"> </association> </resultMap> <select id="getEmpIdStep" resultMap="MyEmp3"> SELECT * FROM tbl_employee WHERE id=#{id}; </select>
association-分段查询&延迟加载
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
4.Collection-集合类型&嵌套结果集
<resultMap id="MyDept" type="bean.Department"> <id column="did" property="id"/> <result column="dept_name" property="name"/> <!--定义集合类型属性封装规则--> <collection property="emps" ofType="bean.Employee"> <!--定义集合中元素封装规则--> <id column="eid" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> </collection> </resultMap> <select id="getDepartmentAll" resultMap="MyDept"> SELECT d.id did,d.dept_name dept_name, e.id eid,e.last_name last_name,e.email email,e.gender genderFROM tbl_dept dLEFT JOIN tbl_employee eON d.id=e.d_idWHERE d.id=#{id} </select>
扩展-多列值封装成map传递
column="{key1=column1, key2=column2}"
fetchType=eager/lazy
覆盖全局的延迟加载
5.动态SQL
if
<mapper namespace="dao.EmployeeMapperSql"> <!-- if choose trim foreach--> <!--查询员工 public List<Employee> getEmployId(Employee employee); 从参数中取值判断 遇见特殊符号使用转义字符--> <select id="getEmployId" resultType="bean.Employee"> select * from tbl_employee where <if test="id!=null"> id=#{id} </if> <if test="lastName!=null && lastName!="""> and last_name like #{lastName} </if> <if test="email!=null and email.trim()!="""> and email=#{email} </if> <if test="gender==0 or gender==1"> and gender=#{gender} </if> </select></mapper>
choose: 分支选择 swtich-case
<!--public List<Employee> getEmployIdChoose(Employee employee);--> <select id="getEmployIdChoose" resultType="bean.Employee"> select * from tbl_employee <where> <choose> <when test="id!=null"> id=#{id} </when> <when test="lastName!=null"> last_name=#{lastName} </when> <when test="email!=null"> email=#{email} </when> <otherwise> gender=0 </otherwise> </choose> </where> </select>
trim 字符串截取
<!-- prefix="" 加前缀 prefixOverrides="" 前缀覆盖: 去掉前面多余的字符 suffix="" 加后缀 suffixOverrides="" 后缀覆盖: 去掉后面多余的字符 --> <select id="getEmployIdTrim" resultType="bean.Employee"> select * from tbl_employee <trim prefix="where" suffixOverrides="and"> <if test="id!=null"> id=#{id} and </if> <if test="lastName!=null && lastName!="""> last_name like #{lastName} and </if> <if test="email!=null and email.trim()!="""> email=#{email} and </if> <if test="gender==0 or gender==1"> gender=#{gender} and </if> </trim> </select>
//查询的时候如果某些条件没带sql拼装可能会有问题 //1.给where后面加上1=1, 以后的条件都 and //2.使用where标签, mybatis会将where标签中拼装的sql, 多出来的and或者or去掉, 只会去掉第一个<select id="getEmployId" resultType="bean.Employee"> select * from tbl_employee <where> <if test="id!=null"> id=#{id} </if> <if test="lastName!=null && lastName!="""> and last_name like #{lastName} </if> <if test="email!=null and email.trim()!="""> and email=#{email} </if> <if test="gender==0 or gender==1"> and gender=#{gender} </if> </where> </select>
set-封装修改条件
<!--public void updateEmp(Employee employee);--> <update id="updateEmp"> update tbl_employee <set> <if test="lastName!=null"> last_name=#{lastName}, </if> <if test="email!=null"> email=#{email}, </if> <if test="gender!=null"> gender=#{gender} </if> </set> where id=#{id} </update>
foreach
<!-- public List<Employee> getEmployIdForeach(List<Integer> ids);--> <select id="getEmployIdForeach" resultType="bean.Employee"> <!-- collection: 指定要遍历的集合 list类型的参数会特殊处理封装在map中, map的key就叫list item: 将遍历的元素赋值给指定的变量 #{变量名} 就能取出变量的值 separator="" 每个元素的分隔符 open="" 遍历出所有结果拼接出一个开始字符 close=")" 遍历出所有结果拼接出一个结束字符 index 索引, 遍历map的时候是map的key--> select * from tbl_employee <foreach collection="list" item="item_id" separator="," open="where id in(" close=")"> #{item_id} </foreach> </select>
2.批量插入
<!--批量保存 public void addEmploy(@Param("employees") List<Employee> employees); --> <insert id="addEmploy"> INSERT INTO tbl_employee(last_name,email,gender,d_id) VALUES <foreach collection="employees" item="emp" separator=","> (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id}), </foreach> </insert>
MyBatis两个内置
_parameter: 代表整个参数
单个参数: 就是自身
多个参数: 参数会被封装成为一个map; 代表map
_databaseId: 配置文件中配置了databaseIdProvider标签
代表当前数据库的别名
bind标签
可以将OGNL表达式的值绑定到一个变量中, 方便后来引用
<bind name="_lastName" value="'%'+lastName+'%'"/> select * from tbl_employee <foreach collection="ids" item="item_id" separator="," open="where id in(" close=")"> #{item_id} </foreach>
sql标签
抽取可重用的sql片段, 方便后面引用
1.sql抽取
2.include标签引用 sql标签
3.自定义property, sql标签内部就能使用自定义的属性
<insert id="addEmploy"><include refid="insert"></include> INSERT INTO tbl_employee(last_name,email,gender,d_id) VALUES <foreach collection="employees" item="emp" separator=","> (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id}), </foreach> </insert> <sql id="insert"> NSERT INTO tbl_employee(last_name,email,gender,d_id) VALUES <foreach collection="employees" item="emp" separator=","> (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id}), </foreach> </sql>
6.MyBatis缓存机制
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
–2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
–3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
1.一级缓存
一级缓存: 本地缓存, SqlSession级别的缓存, 是一直开启的与数据库同一次会话期间查询到的数据会放在本地缓存中
以后如果需要获取相同的数据, 直接从缓存中拿, 没必要查询数据库
一级缓存失效情况(还需要向数据库发出查询)
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
@Test public void test02() throws IOException { String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //1.获取SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2.获取sqlSession实例,能执行已经映射的sql语句 SqlSession sqlSession = sqlSessionFactory.openSession(); //sql的唯一标识 //执行sql要用的参数 //3.获取接口的实现类对象,会为接口自动创建代理对象,代理对象去实现增删改查 try { EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class); Employee employeeId = mapper.getEmployeeById(1); System.out.println(employeeId); Employee employeeId1 = mapper.getEmployeeById(1); System.out.println(employeeId1); System.out.println(employeeId==employeeId1); } finally { sqlSession.close(); } }
2.二级缓存
二级缓存:全局作用域缓存, 基于namespace级别
工作机制
- 一个会话, 查询一条数据, 这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭, 一级缓存的内容会被保存在二级缓存中
- 不同的namespace查处的数据会放在自己对应的缓存中(map)
使用步骤
-
开启全局默认缓存
-
去mapper.xml中配置使用
<cache eviction="" flushInterval="" readOnly="" size="" type=""></cache>
eviction="": 缓存的回收策略
•LRU –最近最少使用的:移除最长时间不被使用的对象。
•FIFO –先进先出:按对象进入缓存的顺序来移除它们。
•SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
•WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
•默认的是LRU。
flushInterval="" 刷新间隔, 单位毫秒
•默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
readOnly="" 只读,true/false
•true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
•false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
size="" 引用数目,正整数
•代表缓存最多可以存储多少个对象,太大容易导致内存溢出
type="" :指定自定义接口的全类名
- POJO需要实现Serializable接口
二级缓存在SqlSession关闭或提交之后才会生效
- 全局setting的cacheEnable:
配置二级缓存的开关, 一级缓存一直是打开的
- select标签的useCache属性:
配置这个select是否使用二级缓存, 一级缓存一直是使用的
- sql标签的flushCache属性
增删改flushCache=true, sql执行后, 会同时清空一级和二级缓存
- sqlSession.clearCache()
只清除一级缓存
- 当在某一个作用域(一级缓存Session/二级缓存Namespaces) 进行了C/U/D 操作后,默认该作用域下所有select 中的缓存将被clear
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UruqRq0y-1624636948872)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210511190600414.png)]
3.第三方缓存整合
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
MyBatis定义了Cache接口方便我们进行自定义扩展。
步骤
- 导入依赖
<!--ehcache--><dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.8</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> <scope>test</scope></dependency><dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.3</version></dependency>
- 配置ehcache.xml文件
<?xml version="1.0" encoding="UTF-8"?><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="F:/ehcache"/> <defaultCache maxElementsInMemory="1" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache></ehcache> <!-- 属性说明: l diskStore:当内存中不够存储时,存储到指定数据在磁盘中的存储位置。 l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略 以下属性是必须的: l maxElementsInMemory - 在内存中缓存的element的最大数目 l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大 l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断 l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的: l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大 l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区. l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。 l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作 l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) -->
- 配置cache标签
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="dao.EmployeeMapper"/>
7.MyBatis逆向工程
MyBatis Generator:
简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写
官方文档地址
http://www.mybatis.org/generator/
官方工程地址
https://github.com/mybatis/generator/releases
使用步骤
- 导包
<!--mybatis-generator--><dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version></dependency>
- 编写MBG的配置文件
1)jdbcConnection配置数据库连接信息
2)javaModelGenerator配置javaBean的生成策略
3)sqlMapGenerator配置sql映射文件生成策略
4)javaClientGenerator配置Mapper接口的生成策略
5)table配置要逆向解析的数据表
tableName:表名
domainObjectName:对应的javaBean名
<?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"><!-- mybatis-generator的核心配置文件 --><generatorConfiguration> <!-- 数据库驱动位置 --> <!--<classPathEntry location="D:\maven_repository\mysql\mysql-connector-java\5.1.41\mysql-connector-java-5.1.41.jar"/>--> <!--jdbcConnection: 如何连接目标数据库--> <context id="DB2Tables" targetRuntime="MyBatis3"> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT" userId="root" password="2580147"> </jdbcConnection> <!--指定生成的类型为java类型,避免数据库中number等类型字段 --> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!--自动生成的实体的存放包路径 targetPackage="com.zjh.bean" 目标包名 targetProject="./src/main/java" 目标工程--> <javaModelGenerator targetPackage="com.zjh.bean" targetProject="./src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!--自动生成的*Mapper.xml文件存放路径 --> <sqlMapGenerator targetPackage="com.zjh.dao" targetProject="./src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!--自动生成的*Mapper接口存放路径 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.zjh.dao" targetProject="./src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 指定要逆向分析那些表, 根据表创建javabean--> <table tableName="tbl_dept" domainObjectName="Department"></table> <table tableName="tbl_employee" domainObjectName="Employee"></table> </context></generatorConfiguration>
- 运行代码生成器生成代码
@Testpublic void test04() throws Exception{ List<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("E:\\mbg.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null);}
maven约定大于配置
<build> <resources> <resource> <!-- 设定主资源目录 --> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> </resources> </build><mappers> <mapper resource="com/zjh/dao/EmployeeMapper.xml"/> </mappers>
复杂查询
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); //1.查询所有员工 List<Employee> list = mapper.selectByExample(null); //2.查询员工名字中有e字母的 EmployeeExample employeeExample = new EmployeeExample(); //EmployeeExample.Criteria,拼装查询 EmployeeExample.Criteria criteria = employeeExample.createCriteria(); criteria.andLastNameLike("%j%"); criteria.andGenderEqualTo("2"); employeeExample.or(); List<Employee> employees = mapper.selectByExample(employeeExample); for (Employee employee : employees) { System.out.println(employee.getdId()); }} finally { sqlSession.close();}
8.MyBatis插件开发
MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
•MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
•默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler(getParameterObject, setParameters)
ResultSetHandler(handleResultSets, handleOutputParameters)
StatementHandler(prepare, parameterize, batch, update, query)
插件开发步骤
- 编写Interceptor的实现类
public class MyFirstPlugin implements Interceptor { /** * 拦截 * 拦截目标对象的目标方法的执行 * * @param invocation * @return * @throws Throwable */ public Object intercept(Invocation invocation) throws Throwable { //执行目标方法 Object proceed = invocation.proceed(); //执行后的返回值 return proceed; } /** * 包装目标对象, 为目标对象创建一个代理对象 * * @param target * @return */ public Object plugin(Object target) { //我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象 Object wrap = Plugin.wrap(target, this); //返回当前为target创建的动态代理 return wrap; } /** * 将插件注册时的property属性设置进来 * * @param properties */ public void setProperties(Properties properties) { System.out.println("插件配置的信息" + properties); }}
- 使用@Intercepts注解完成插件签名
/** * 完成插件签名 * 告诉mybatis当前插件拦截那个对象的哪个方法 */@Intercepts({ @Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)})
- 将写好的插件注册到全局配置文件中
<!--注册插件--><plugins> <plugin interceptor="com.zjh.dao.MyFirstPlugin"> <property name="username" value="root"/> <property name="password" value="root"/> </plugin></plugins>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5TOJFYq-1624636948873)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210512121348314.png)]
插件原理
-
按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
-
多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
-
目标方法执行时依次从外到内执行插件的intercept方法
-
多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CJ0dRbzW-1624636948874)(C:\Users\ZJH\AppData\Roaming\Typora\typora-user-images\image-20210512121645710.png)]
常用代码: 从代理链中分离真实被代理对象
//1、分离代理对象。由于会形成多次代理,所以需要通过一个while循环分离出最终被代理对象,从而方便提取信息MetaObject metaObject = SystemMetaObject.forObject(target);while(metaObject.hasGetter("h")) {Object h = metaObject.getValue("h");metaObject = SystemMetaObject.forObject(h);}//2、获取到代理对象中包含的被代理的真实对象Object obj = metaObject.getValue("target");//3、获取被代理对象的MetaObject方便进行信息提取MetaObject forObject = SystemMetaObject.forObject(obj);常用代码:
9.MyBatis扩展-实用场景
1.PageHelper插件进行分页
- 导入相关jar包
<!-- 分页处理引入包 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency>
- 在MyBatis全局配置文件中配置分页插件
<plugins> <plugin interceptor="com.github.pagehelper.PaginationInterceptor"> </plugin> </plugins>
- 使用PageHelper提供的方法进行分页
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Page<Object> page = PageHelper.startPage(1, 1); List<Employee> employees = mapper.getEmployees(); for (Employee employee : employees ) { System.out.println("当前页码"+page.getPageNum()); System.out.println("总记录数"+page.getTotal()); System.out.println("每页的记录数"+page.getPageSize()); System.out.println(); System.out.println(); System.out.println(employee); }} finally { sqlSession.close();}
- 可以使用更强大的PageInfo封装返回结果
2.批量操作
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); for (int i = 0; i < 1000; i++) { mapper.addemp(new Employee("a", "32130@qq.com", "2")); } sqlSession.commit();} finally { sqlSession.close();}