MyBatis 前身为ibatis,提供包括SQL Maps 和 Data Access Objects (DAO)
JDBC 代码和手动设置参数以及获取结果集POJO(Plain Old java Objects, 普通的java对象)映射成数据库中的记录ORM(Object Relation Mapping)框架JDBC
SQL夹杂在java代码中,造成耦合度太高,导致硬编码内伤
维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
代码冗长,开发效率低
Hibernate和JPA(没用过)
MyBatis
jar从mybatis.pdf 官方文档中直接可以找到
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="com.cczj.mybatis.pojo"/>
typeAliases>
<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>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<package name="com.cczj.mybatis.mapper"/>
mappers>
configuration>
注意:
The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.
配置
tag要按照要求的顺序
该mapper接口类似于DAO (Data Access Object) 数据访问对象
相关概念:ORM (Object Relationship Mapping)对象映射关系
| java概念 | 数据库概念 |
|---|---|
| 类 | 表 |
| 属性 | 字段/列 |
| 对象 | 记录/行 |
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cczj.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into ssm.t_user
values (1, 'admin', '123456', 18, '男', '123456@qq.com');
insert>
mapper>
日志的级别
FATAL(致命) -> ERROR(错误) -> WARN(警告) -> INFO(信息) -> DEBUG(调试)
DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<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>
${} 本质是字符串拼接、#{} 本质是占位符赋值${} 拼接时,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号#{} 赋值时,若为字符串类型或日期类型的字段进行赋值时,可以自动加单引号@param("指定的变量名") 作为指定名${} 中可以使用任意变量名,但是需要加上'' 作为字符串拼接来使用。#{}中可以使用任意的变量名,因为在底层实现过程中,此标记仅仅作为 ? 这个占位符来使用,而传进来的具体参数值作为一个字面量是不会改变的。arg0、arg1、arg2... 和 param1、param2、...KeyMybatis 映射中传入参数时,为以下结果自定义了别名
右侧为Map类型,左侧为类型别名:
由于普通的#{username} 在引号'' 中会失去占位符的效果
select * from table where username like '%${usename}%'select * from table where username like "%"#{username}"%"select * from table where username like concat('%', #{username}, '%')增删改的操作下返回值都是一个int的该变量,所以我们要手动设置一个主键KEY
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert>
useGeneratedKeys : 表示当前添加功能使用了自增的主键
keyProperty : 将添加的数据的自增主键作为实体类对象参数的主键名
pojo包中属性名和数据库表中字段名不同的解决办法由JDBC可知,查询结果会通过getter、setter的方法呈现。如果名称不一致,那么无法进行存储值。因此解决办法就是为SQL语句结果设置别名。将查询语句结果别名设置为成员属性名。
在Mybatis的核心配置文件中设置一个全局变量,可以自动将下划线映射为驼峰
<setting>
mapUnderscoreToCamelCase 将 蛇形命名法 映射为 小驼峰命名法
<setting name="mapUnderscoreToCamelCase" value="true"/>
setting>
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_gender" property="empGender"/>
<result column="emp_age" property="empAge"/>
resultMap>
因为成员属性的类型可能是一个类,那么要将多张表查询到一张表中,就需要用到以下处理方式
级联处理方式:
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_gender" property="empGender"/>
<result column="emp_age" property="empAge"/>
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
select
t_dept.*, t_emp.*
from t_emp
left join t_dept
on t_emp.dept_id = t_dept.dept_id
where t_emp.emp_id = #{empId};
select>
association:处理多对一的映射关系 (处理实体类类型的属性)
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_gender" property="empGender"/>
<result column="emp_age" property="empAge"/>
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
association>
resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
select
t_dept.*, t_emp.*
from t_emp
left join t_dept
on t_emp.dept_id = t_dept.dept_id
where t_emp.emp_id = #{empId};
select>
分步查询方式:
通过两个不同的接口,调用不同的方法。即用子表的结果查询父表。
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_gender" property="empGender"/>
<result column="emp_age" property="empAge"/>
<association property="dept"
select="com.cczj.mybatis.mapper.DeptMapper.方法名"
column="dept_id">
association>
resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
select * from t_emp where t_emp.emp_id = #{empId}
select>
<resultMap id="empResultMap" type="Emp">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
select * from t_dept where t_dept.dept_id = #{deptId}
select>
分布查询有什么优势呢?
可以实现延迟加载,但是必须在核心配置文件中进行全局配置
<settings>
<settins name="lazyLoadingEnabled" value="true"/>
<settins name="aggressiveLazyLoading" value="false"/>
settings>
collection: 处理一对多和多对多的方式
<resultMap id="empResultMap" type="Emp">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="emps" ofType="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_gender" property="empGender"/>
<result column="emp_age" property="empAge"/>
collection>
resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
select
t_emp.* , t_dept.*
from t_dept
left join t_emp
on t_emp.dept_id = t_dept.dept_id
where t_emp.emp_id = #{empId};
select>
分布查询方法和多对一基本一致
<select id="方法名" resultType="结果类型">
select * from t_emp where 1=1
<if test="empName != null and empName != ''">
and empName = #{empName}
if>
<if test="empAge != null and empAge != ''">
and empAge = #{empAge}
if>
select>
<select id="方法名" resultType="结果类型">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
empName = #{empName} and
if>
<if test="empAge != null and empAge != ''">
empAge = #{empAge}
if>
where>
select>
trim:
prefix、suffix: 在标签中内容前面或者后面添加指定内容
prefixOverrides、suffixOverrides: 在标签内容前面或者后面去掉指定内容
<select id="方法名" resultType="结果类型">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName != null and empName != ''">
empName = #{empName}
if>
<if test="empAge != null and empAge != ''">
empAge = #{empAge}
if>
trim>
select>
if、if else 和 else
<select id="方法名" resultType="结果类型">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
empName = #{empName}
when>
<when test="empAge != null and empAge != ''">
empAge = #{empAge}
when>
<otherwise>
类似于else ... 也可以看作default
otherwise>
choose>
where>
select>
collection: 表示当前传入集合的参数名 (建议通过@param进行手动设置)
item: 表示当前循环的参数名 (类似于foreach中的每一次循环的结果)
separate: 循环分隔符
open: 当前循环所有的内容以什么开始 close:循环的内容以什么结束
index: 当前索引的下标是多少
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.empAge})
foreach>
insert>
<delete id="deleteMoreEmp">
delete from t_emp where emp_id in
(
<foreach collection="empIds" item="empId" separator=",">
#{empId}
foreach>
)
delete from t_emp where emp_id in
<foreach collection="empIds" item="empId" separator="or">
emp_id = #{empId}
foreach>
delete>
将部分sql语句通过sql片段存起来,用到的时候通过include标签进行调用
<sql id="empColumns">
emp_id,emp_name,emp_age,emp_gender
sql>
<select>
select <include refid="empColumns">include> from t_emp
select>
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库中重新访问。
一级缓存失效的四种情况:
sqlSession.clearCache(); 该方法用于清除一级缓存二级缓存是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后如果再次执行相同的查询语句,结果就会从缓存中获取
二次缓存的开启条件:
cacheEnabled="true" 默认为true,不需要设置cache (仅仅添加一个即可)SqlSession关闭或提交之后有效使二级缓存失效的情况:
两次查询过程中执行了任意增删改的操作,会使一级和二级缓存同时失效
eviction: 缓存回收策略,有这几种回收策略
LRU - 最近最少回收,移除最长时间不被使用的对象FIFO - 先进先出,按照缓存进入的顺序来移除它们SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象默认是 LRU 最近最少回收策略
flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改size : 缓存存放多少个元素type: 指定自定义缓存的全类名(实现Cache 接口即可)blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKQwPEJ0-1660203247214)(后端框架.assets/33b6b5887c9a4a198964ca58ae86f04e.png)]](https://1000bd.com/contentImg/2022/08/15/035221207.png)
先查询二级缓存,因为二级缓存中可能会有其他程序已经查询出来的结果,可以直接拿来使用
如果二级缓存也没有命中,那么再查询一级缓存 (当一级缓存没有结束时的情况下会发生该情况)
如果一级缓存也没有命中,那么再查询数据库
SqlSession 关闭后,一级缓存中的数据会写入二级缓存