一、Mybatis入门
mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
步骤:
- 根据xml配置文件(全局配置信息)创建一个SqlSessionFact
有数据源的一些运行环境信息 - sql映射文件;配置了每一个sql,以及sql的分装规则
- 将sql映射文件注册在全局配置文件中
- 写代码:
1)根据全局配置文件得到SqlSessionFactory
2)使用SqlSession工厂,获取到的sqlSession对象
一个sqlSession就是代表和数据库的一次会话,用完关
3)使用sql的唯一标志来告诉MyBatis执行哪个sql
- 接口式编程
原生: Dao ===》 DaoImpl
mybatis:Mapper ===》 xxMapper.xml- SqlSession代表和数据库的一次会话:用完必须关闭
- SqlSession和Connection一样都是线程不安全的,每次使用都应该去获取一个新的对象
- mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象
(将接口和XXX.xml绑定)
AdminMapper adminMapper = session.getMapper(AdminMapper.class);- 两个重要的配置文件:
mybatis的全局配置文件
:包含了数据库连接池信息,事务管理器信息等…系统运行环境信息
sql映射文件
:保存了每一个sql语句的映射信息, 将sql抽取出来
二、MyBatis-全局配置文件
- MyBatis的配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息。文本档的顶层结构如下:
配置文件详细信息:https://mybatis.org/mybatis-3/zh/configuration.html
三、MyBatis映射文件
1. 基本概况
- MyBatis允许增删改直接定义以下类型返回值
Integer,Long,Boolean,void - `SqlSession session = sqlSessionFactory.openSession(true)) //openSession中的参数为true时,自动commit
- 自增主键的获取
①useGeneratedKeys=“true”
:使用自增主键的获取
②keyProperty
:指定对应的主键属性,也就是mybatis获取到主键以后,将这个值封装给javaBean的哪个属性
<insert id="addAdmin" parameterType="com.zc.bean.Admin"
useGeneratedKeys="true" keyProperty="id">
insert into admin(username,password)
values(#{username},#{password})
</insert>
2.参数处理
- 单个参数:Mybatis不会做任何特殊处理
#{参数}: 取出参数值 - 多个参数:mybatis会做特殊处理
多个参数会被封装成一个map
,- key:param1 … paramN
- value:传入的参数值
- #{}就是从map中获取指定的key
如果用获取单个参数的方法去获取参数值,则会报如下异常
- 异常:
rror querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found.Available parameters are [arg1, arg0, param1, param2]
- 操作:
方法:Admin getAdminByIdAndName(Long id,String username);
取值:#{id},#{username}
- 解决方法:
1.明确指定封装参数时map的key
:@Param(“id”) Long id, @Param(“username”)
#{对应的key}就可以取出对应的参数值
2.POJO
:如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入POJO
#{属性名}:取出传入的pojo的参数值
3.Map
:如果多个参数不是业务逻辑的数据模型,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出key对应的value
4.TO
如果多个参数不是业务模型中的数据,但是要经常使用,推荐编写一个TO(Transfer Object)数据传输对象
特别注意
:如果是Collection(List,Set)类型或者是数组,也会特殊处理,- 也是把传入的list或数组分组在map中
- key:
Collection——collection
List——list
数组——array - 方法:Admin getAdminByIds(List<Long> ids)
取值:取出第一个id的值:#{list[0]}
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key,#{key}就可以取出map中的值
- 参数值的获取
- #{}:可以获取map中的值或者pojo对象属性的值
- ${}:可以获取map中的值或者pojo对象属性的值
- 例子:
① select * from admin where id = ${id} and username = #{username}
② Preparing: select * from admin where id = 23 and username = ?
③ Parameters: Tom(String) - 区别
① #{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
② ${}:取出的值直接拼装在sql语句中;会有安全问题
③ 大多数情况下,我们取参数的值都是使用 #{}
④原生 jdbc 不支持占位符的地方我们就可以使用\${}进行取值
比如分表、排序。。。:按照年份分表拆分
select * from ${year}_salary where xxx
select * from tbl_admin order by ${f_name} ${order}
#{}
:更丰富的用法,规定参数的一些规则:javaType,JdbcType,mode(存储过程),numericScale,resultMap,typeHandle,jdbcTypeName,expression-
jdbcType通常需要在某种特定的条件下被设置:在我们数据为null的时候,有些数据库可能无法识别mybatis对null的默认处理。比如Oracle(会报错:JdbcType OTHER:无效的类型),因为mybatis对所有的null都映射的是原生jdbc的OTHER类型,Oracle不能正常处理
-
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;有两种方法解决
① #{email,jdbcType = NULL}
② jdbcTypeForNull = NULL
-
3. select元素
- id:唯一标识符,用来引用这条语句,需要和接口的方法名一致
- parameterType:参数类型,可以不传,MyBatis会根据TypeHandle自动推断
- resultType:返回值类型,别名或者全类名,如果返回的是集合,定义集合中元素的类型,不能和resultMap同时使用
- resultType,返回值为集合:
//返回值为List
List<Admin> getAllAdmin();
//返回一条记录的Map:key就是列名,值就是对应的值
Map<String,Object> selectAdminByIdReturnMap(@Param("id") Long id);
//返回多条记录分装成一个map,map<Long,Admin> :键是这条记录的主键,值是记录封装后的javaBean
//告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("username")
Map<Long,Admin> selectAdminByUsernameLikeReturnMap(@Param("username") String username);
<!-- 返回集合-->
<!-- 返回List-->
<select id="getAllAdmin" resultType="Admin">
select * from admin
</select>
<!-- 返回单挑记录的Map-->
<select id="selectAdminByIdReturnMap" resultType="map">
select * from admin
where id = #{id}
</select>
<!-- 返回多条记录的Map-->
<select id="selectAdminByUsernameLikeReturnMap" resultType="Admin">
select * from admin
where username like #{username}
</select>
4. resultMap,自定义resultMap,实现高级结果集映射
<!-- 自定义某个javaBean的映射规则 -->
<resultMap id="myAdmin" type="com.zc.bean.Admin">
<!-- id 指定主键列的分装
column:指定哪一列
property:指定对应的javaBean属性
--> <id column="id" property="id"/>
<!-- 定义普通的封装规则-->
<result column="username" property="username"/>
<result column="password" property="password"/>
<!-- 其他不指定的列会自动封装,但是建议,只要写resultMap,就把全部的列的映射都写上-->
</resultMap>
<!-- resultMap:自定义结果集映射规则-->
<!-- Admin getAdminById(Long id);-->
<select id="getAdminById" resultMap="myAdmin">
select * from admin where id = #{id}
</select>
例子一:对象中包含对象
Beauty getBeautyById(Long id);
List<Beauty> getAllBeauty();
List<Beauty> getAllBeauty();
}
/******************************************/
/**
* @author ZC
* @Description
* @date 2020-07-20 16:55
*/
@Data
public class Beauty {
private Long id;
private String name;
private String sex;
private Date bornDate;
private String phone;
private Boys boyfriend;
}
/*******************************************/
/**
* @author ZC
* @Description
* @date 2020-07-20 17:02
*/
@Data
public class Boys {
private Long id;
private String boyName;
private int userCP;
}
<?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">
<mapper namespace="com.zc.dao.BeautyMapper">
<!-- 联合查询:级联属性封装结果集-->
<resultMap id="myBeauty1" type="Beauty">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="born_date" property="bornDate"/>
<result column="phone" property="phone"/>
<result column="bid" property="boyfriend.id"/>
<result column="boy_name" property="boyfriend.boyName"/>
<result column="userCP" property="boyfriend.userCP"/>
</resultMap>
<select id="getBeautyById" resultMap="myBeauty1">
select
g.id id,
g.name name,
g.sex sex,
g.born_date born_date,
g.phone phone,
b.id bid,
b.boy_name boy_name,
b.userCP userCP
from beauty as g
inner join boys as b on g.boyfriend_id = b.id
where g.id = #{id}
</select>
<!-- 使用association定义关联的单个对象的封装规则 -->
<resultMap id="myBeauty2" type="Beauty">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="born_date" property="bornDate"/>
<result column="phone" property="phone"/>
<!-- association 可以指定联合的javaBean
property:指定哪个属性是联合的对象
javaType:指定这个属性对象的类型(不能省略)
--> <association property="boyfriend" javaType="Boys">
<id column="bid" property="id"/>
<result column="boy_name" property="boyName"/>
<result column="userCP" property="userCP"/>
</association>
</resultMap>
<select id="getAllBeauty" resultMap="myBeauty2">
select
g.id id,
g.name name,
g.sex sex,
g.born_date born_date,
g.phone phone,
b.id bid,
b.boy_name boy_name,
b.userCP userCP
from beauty as g
inner join boys as b on g.boyfriend_id = b.id
</select>
<!-- 使用association中的select属性查询 -->
<resultMap id="myBeauty3" type="Beauty">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="born_date" property="bornDate"/>
<result column="phone" property="phone"/>
<!-- association 可以指定联合的javaBean
select:表明当前属性是调用select查出的结果
column:指定将哪一列的值传个这个方法
-->
<association property="boyfriend" javaType="Boys"
select="com.zc.dao.BoysMapper.getBoyById" column="boyfriend_id"/>
</resultMap>
<!-- 可以使用延迟加载(懒加载、按需加载)
只需要在使用association的select基础上加上两个配置
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
-->
<select id="getBeauty" resultMap="myBeauty3">
select * from beauty where id = #{id}
</select>
</mapper>
<?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">
<mapper namespace="com.zc.dao.BoysMapper">
<select id="getBoyById" resultType="Boys">
select * from boys
where id = #{id}
</select>
</mapper>
例子二:对象中包含集合
<!-- collection 嵌套结果集的方式,定义关联的集合类型元素的封装规则-->
<resultMap id="myBoys1" type="Boys">
<id column="id" property="id"/>
<result column="boy_name" property="boyName"/>
<result column="userCP" property="userCP"/>
<collection property="beauties" ofType="Beauty">
<!-- collection 定义关联集合类型的属性的封装规则
ofType:指定集合里面的元素的类型 -->
<id column="gid" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="phone" property="phone"/>
<result column="born_date" property="bornDate"/>
</collection>
</resultMap>
<select id="getBoyByIdPlus" resultMap="myBoys1">
select b.id id,b.boy_name boy_name,b.userCP userCP,
g.id gid,g.name name,g.sex sex,g.born_date born_date,g.phone phone
from boys b
left join beauty g
on b.id = g.boyfriend_id
where b.id = #{id}
</select>
<!-- collection分步查询-->
<resultMap id="myBoys2" type="Boys">
<id column="id" property="id"/>
<result column="boy_name" property="boyName"/>
<result column="userCP" property="userCP"/>
<collection property="beauties" ofType="Beauty"
select="com.zc.dao.BeautyMapper.getBeautyByBoyId"
column="id"
fetchType="lazy"
/>
</resultMap>
<select id="getBoyByStep" resultMap="myBoys2">
select * from boys where id = #{id}
</select>
<!-- BeautyMapper.xml中-->
<select id="getBeautyByBoyId" resultType="com.zc.bean.Beauty">
select * from beauty where boyfriend_id = #{boyId}
</select>
扩展, 如果要传递多列的值:将多列的值封装成map传递
column ="{key1 = column1,key2 = column2}"
如:column ="{boyId = id}”
.
fetchType = “lazy”:表示使用延迟加载
- lazy:延迟加载
- eager:立即加载
四、动态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">
<mapper namespace="com.zc.dao.AdminMapperDynamicSQL">
<!-- 测试Mybatis的动态SQL
if
choose(when,otherwise):分支选择,switch-case
trim(where(封装查询条件),set(封装修改条件))
foreach
-->
<!-- 抽取可重用的sql片段。方便后面引用
1. sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
2. include来引用已经抽取的sql
3. include还可以自定义一些property,sql标签内部就能使用自定义的属性
取值的方式:${prop} 不能使用#{}
-->
<sql id="insertColumn">
username,password
</sql>
<select id="getAdminByConditionIf" resultType="com.zc.bean.Admin">
select * from admin
<!-- List<Admin> getAdminByConditionIf(Admin admin);
测试if:
test:判断表达式(OGNL)
1.OGNL用法参照官方文档
2.从参数中取值进行判断
3.遇见特殊符号应该去写转义字符
4.OGNL会进行字符串与数字的转换判断 “0”==0
5.查询的时候如果某些条件没带,可能sql拼装有问题
解决方法:1. 给where后面加上 1=1 ,之后的条件都 and xxx
2. mybatis使用where标签来将所有的查询条件都包括在内,
mybatis就会自动将sql中多余的and(第一个多余的and或or)和or去掉
-->
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="username != null and username != ''">
and username like #{username}
</if>
<if test="password != null and password != ''">
and password like #{password}
</if>
</where>
</select>
<select id="getAdminByConditionTrim" resultType="com.zc.bean.Admin">
select * from admin
<!-- List<Admin> getAdminByConditionTrim(Admin admin);
测试trim:
trim标签体中是整个字符串拼串后的内容
prefix="" :给拼串后的整个字符串加一个前缀
prefixOverrides="" :前缀覆盖,去掉整个字符串前面多余的字符
suffix="" :给拼串后的整个字符串加一个后缀
suffixOverrides="" :后缀覆盖,去掉整个字符串后面多余的字符
-->
<trim prefix="where" prefixOverrides="and">
<if test="id != null">
and id = #{id}
</if>
<if test="username != null and username != ''">
and username like #{username}
</if>
<if test="password != null and password != ''">
and password like #{password}
</if>
</trim>
</select>
<select id="getAdminByConditionChoose" resultType="com.zc.bean.Admin">
select * from admin
<!--List<Admin> getAdminByConditionChoose(Admin admin);
测试choose
分支查询,相当于java中带了break的switch-case
-->
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="username != null and username != ''">
<!-- bind:可以将OGNL表达式的值绑定带一个变量中,方便后来引用这个变量的值 -->
<bind name="_username" value="'%'+username+'%'"/>
username like #{_username}
</when>
<when test="password != null and password != ''">
<bind name="_password" value="'%'+password+'%'"/>
password like #{_password}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</where>
</select>
<select id="getAdminByConditionForeach" resultType="com.zc.bean.Admin">
select * from admin where id in
<!--List<Admin> getAdminByConditionForeach(Admin admin);
测试foreach
collection:指定要遍历的集合:
list类型的参数会特殊处理封装在map,map的key就叫list
item:将当前遍历出的元素赋值给指定的变量
#{变量名}就能取出变量的,也就是当前遍历出的元素
separator:每个元素之间的分隔符
open:遍历出所有结果拼接一个开始的字符
close:遍历出所有结果拼接一个结束的字符
index:遍历list的时候index是索引,item是当前值
遍历map的时候index表示的就是map的key,item就是map的value
-->
<foreach collection="ids" item="item_id" separator="," open="(" close=")">
#{item_id}
</foreach>
</select>
<update id="updateAdmin">
update admin
<!--void updateAdmin(Admin admin);
测试set和if的结合,可根据传递过来的参数进行更新
set可以去除掉拼接后的字符串中多余的 “,”
-->
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
</set>
<where>
id = #{id}
</where>
</update>
<insert id="addAdmins">
<!--void addAdmins(@Param("admins") List<Admin> admins)
测试使用foreach批量保存
MySQL下批量保存:可以foreach遍历,mysql支持values(),(),()语法
-->
insert into admin(
<!--引用外部定义的sql-->
<include refid="insertColumn"/>
)
values
<foreach collection="admins" item="admin" separator=",">
(#{admin.username},#{admin.password})
</foreach>
</insert>
<!-- <insert id="addAdmins">-->
<!-- <!–void addAdmins(@Param("admins") List<Admin> admins)-->
<!-- 这种方式需要数据库里连接属性allowMultiQueries=true-->
<!-- 这种分号分隔多个sqk语句,可以用于其他批量操作(批量删除或批量修改)-->
<!-- –>-->
<!-- <foreach collection="admins" item="admin" separator=";">-->
<!-- insert into admin(username,password)-->
<!-- values(#{admin.username},#{admin.password})-->
<!-- </foreach>-->
<!-- </insert>-->
<!--两个内置参数
不只是方法传递过来的参数可以被用来判断,取值
mybatis还有两个内置参数:
1. _parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map:_parameter就是代表这个map
2. _databaseId:如果配置了databaseIdProvider标签
_databaseId就是代表当前数据库的别名
-->
</mapper>
五、MyBatis-缓存机制
1.两级缓存
-
一级缓存(本地缓存):sqlSession级别的缓存。一级缓存是一直开启的,本质上是sqlSession的一个Map
与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
一级缓存失效情况:(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)
①:sqlSession不同
②:sqlSession相同,查询条件不同(当前一级缓存中没有此数据)
③:sqlSession相同,两次查询期间执行了增删改(这次增删改可能对当前数据有影响)
④:SqlSession相同,手动清除了一级缓存 -
二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存
1.工作机制:
①:一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
②:如果会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话信息,就可以参照二级缓存中的数据
③:sqlSession ===》AdminMapper ===》Admin
sqlSession ===》 BeautyMapper ===》 Beauty
不同namespace查出的数据会放在自己对应的缓存中(map)
④:效果:数据会从二级缓存中获取,查出的数据都会默认先放在一级缓存中,只有会话提交或关闭以后,一级缓存中的数据才会转移到二级缓存中- 使用:
① 开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
② 去XxxMapper.xml中配置二级缓存<cache/>
,详细信息查看myBatis的官方文档
mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
- 使用:
2. 和缓存相关的设置/属性
① 全局配置:cacheEnabled=true/false
(关闭二级缓存,一级缓存一直可用)
② 每个select标签都有useCache=“true”
false:不使用缓存(关闭的是二级缓存,一级缓存一直可用)
③ 每个增删改标签都有 flushCache="true"
:即增删改执行完后会清除缓存(两级缓存全部清掉)
查询(select)标签默认 flushCache="flase"
:
④ sqlSession.clearCache()
:只清除当前session的一级缓存
⑤ 全局配置:localCacheScope=SESSION/STATEMENT
:本地缓存作用域:
SESSION:一级缓存,当前会话的所有数据保存在会话缓存中
STATEMENT:可以禁用一级缓存