概述
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
框架:
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
JDBC 的缺点
硬编码
- 注册驱动,获取链接
- SQL 语句
操作繁琐
- 手动设置参数
- 手动封装结果集
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>
<!-- 链接数据库只需要一个属性来配置文件,就会自动解析 -->
<properties resource="druid.properties"/>
<!-- 设置别名,然后就可以使用别名来代替前面的路径 -->
<typeAliases>
<typeAlias type="com.domain.User" alias="User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件,sql映射 -->
<mappers>
<mapper resource="mapper/User.xml"/>
</mappers>
</configuration>
SQL 映射文件
- parameterType 包装成什么类型
- namespace 接口的位置
<?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 是用于调用的时候的映射,对应绑定的接口类
-->
<mapper namespace="com.dao.klo">
<insert id="add" parameterType="User">
<!-- 这里的#username 就等于是用 ? 的方式,等方法调用的时候,会传递一个参数,就会自动映射到username的属性上 -->
insert into user (id,username,password) values (#{id},#{username},#{password})
</insert>
</mapper>
快速入门
加载配置(Resources . getResourceAsStream)
// 加载 mybatis 核心配置文件
InputStream asStream = Resources.getResourceAsStream("mybatis-config.xml");
获取工厂
会话工厂,根据配置文件创建工厂
作用:创建SqlSession
// 加载 mybatis 核心配置文件
InputStream asStream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(asStream);
获取执行语句对象(openSession)
会话,是一个接口,面向用户(程序员)的接口
作用:操作数据库(发出sql增、删、改、查)
sqlSession 需要释放资源
// 加载 mybatis 核心配置文件
InputStream asStream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(asStream);
// 获取执行语句对象
SqlSession sqlSession = factory.openSession();
mapper 代理开发
定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。
注意:resources资源目录和 java 目录编译后在同一级
在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
执行 sql (getMapper)
传入接口类,然后调用里面的方法
// 加载 mybatis 核心配置文件
InputStream asStream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(asStream);
// 获取执行语句对象
SqlSession sqlSession = factory.openSession();
// 执行 sql
List<com.domain.User> users = sqlSession.getMapper(User.class).selectAll();
提交
sqlSession.commit();
mapper 代理包扫描
不需要指定路径了就
<mappers>
<!-- <mapper resource="com/mapper/User.xml"/>-->
<!-- 可以使用包扫描的方式-->
<package name="com.mapper"/>
</mappers>
细节:数据库字段名和 javaBean属性不匹配
- 给数据库字段起别名(缺点:每一个都要写别名)
- 使用 sql 片段(缺点:限制了字段个数)
- 使用 resultMap (常用)
数据库里user_name 和 JavaBean类 属性username 对应不上,获取的是 null
<mapper namespace="com.example.mappers.brandMappers">
<select id="findAll" resultType="com.example.beans.User">
select * from tb_brand;
</select>
</mapper>
使用 resultMap 解决
- id 主键的方式
- result 普通字段
- column 数据库字段
- property JavaBean属性
- resultMap 替换 resultType
<mapper namespace="com.example.mappers.brandMappers">
<!-- id 表示给这个 resultMap 起的名字 type 表示对应的那个 JavaBean 类 -->
<resultMap id="brandMap" type="com.example.beans.Brand">
<!-- id 表示主键 -->
<id column="id" property="id"/>
<!-- result 表示普通字段 -->
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<!-- 记得把 resultType 换成 resultMap 并且设置名称 -->
<select id="findAll" resultMap="brandMap">
select *
from tb_brand;
</select>
</mapper>
参数占位符
#号
会替换掉 ?号,为了防止sql注入
参数传递时用 #
- 参数:parameterType 可省略不写
特殊符号
CD = 这样就可以写特殊符号了
<![CDATA[
]]>
$号
拼接 sql
动态查询一张表,表名不固定,可以使用
但是 sql 注入一定会存在
参数接收
- 手动分配 @Param
@Param(“status”) int status 指定后面这个 status 在 sql 语句中叫 status - 封装成对象(名字要一一对应)
模糊查询 like
<select id="selectByCondition" resultMap="brandMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
第一种:参数形式
接口
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName,
@Param("brandName") String brandName);
实现
// 获取执行
SqlSession sqlSession = build.openSession();
// 处理参数,因为 模糊查询需要匹配%
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 执行 sql
List<Brand> brands = sqlSession.getMapper(brandMappers.class).selectByCondition(status, companyName, brandName);
System.out.println(brands);
sqlSession.close();
第二种:封装成对象传入
接口
List<Brand> selectByCondition(Brand brand);
实现
// 获取执行
SqlSession sqlSession = build.openSession();
// 处理参数,因为 模糊查询需要匹配%
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 封装对象
Brand brand = new Brand(status, companyName, brandName);
// 执行 sql
List<Brand> brands = sqlSession.getMapper(brandMappers.class).selectByCondition(brand);
System.out.println(brands);
sqlSession.close();
第三种:map 集合也可以,保证键名和 sql 字段一样就可以
多个参数
如果没有进行 @Param 注解,那么会进行 map 封装
- 两种形式:依次进行存放。键 = 值形式
- 形式1:map . put( arg0, 参数1 )
- 形式1:map . put( param1, 参数值1 )
建议使用 @Param 注解
动态 sql
if
test 后面 跟表达式
- 利用where标签代替where
- 去除了第一个 and
- 如果都为空,去除本身 where
<select id="selectByCondition" resultMap="brandMap">
select *
from tb_brand
<where>
<if test="status != null">
status = #{status}
</if>
<if test="companyName != null">
and company_name like #{companyName}
</if>
<if test="brandName != null">
and brand_name like #{brandName}
</if>
</where>
</select>
choose
<select id="selectBySingle" resultMap="brandMap">
select *
from tb_brand
where
<choose># 相当于 switch
<when test="status != null"> # 相当于 case
status = #{status}
</when>
<when test="companyName != null and companyName != ''"># 相当于 case
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"># 相当于 case
brand_name like #{brandName}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</select>
if forEach
Where
forEach
- collection 要遍历哪一个数组 这里面的值 数组 = array 集合 list。可以使用别名
批量添加
<insert id="addArray">
insert into user(username, password) values
<foreach collection="list" item="user" separator=",">
(#{user.username},#{user.password})
</foreach>
</insert>
批量删除
<delete id="deleteArray">
delete from user where id in
<foreach collection="list" item="item" separator="," open="(" close=")">
${item}
</foreach>
</delete>
map 遍历查询
- collection 一定要指定
- index 是key
- key必须使用$符号,占用
<select id="selectAll" resultType="com.example.domin.IomBaseDataNe">
select * from iom_basedata_ne
<where>
<foreach collection="map" index="key" item="value" separator="and">
${key} = #{value}
</foreach>
</where>
</select>
一对一
- property 给 user注入
- column 主表链接条件
- javaType 返回的类型在java中是什么类型
新增语句
主键返回,就是没有新增 id 字段,由数据库自动新增,但是实例 bean类,id 字段为空,那么就需要主键返回
- keyProperty 指定返回的字段
- useGeneratedKeys 默认false,获取返回值
<insert id="insertOne" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand(brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>
map 注入
<resultMap id="andUser" type="AddressAndUser" autoMapping="true">
<id property="userId" column="user_id"/>
<!-- property 给 user注入 -->
<!-- column 主表链接条件 -->
<!-- javaType 返回的类型在java中是什么类型 -->
<association property="user" column="user_id" javaType="User"
select="com.example.mappers.UserMappers.findOneByIdUsers"/>
</resultMap>
更新语句
<update id="replaceByid">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id};
</update>
第一种(不常用)
sql 语句
<select id="getAddressAndUser" resultType="AddressAndUser">
select *
from t_user
where id = #{id};
</select>
第二种(一般)
<resultMap id="andUser" type="AddressAndUser" autoMapping="true">
<id property="id" column="id"/>
<association property="user" javaType="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="nickname" column="nickname"/>
</association>
</resultMap>
<select id="getAddressAndUser" resultMap="andUser">
select *
from t_address ta
left join t_user tu on tu.id = ta.user_id
where ta.id = #{id};
</select>
第三种(常用)
就是把第二种拆分开了
<resultMap id="andUser" type="AddressAndUser" autoMapping="true">
<id property="id" column="id"/>
<association property="user" javaType="User" autoMapping="true" resultMap="UserAtt"/>
</resultMap>
<resultMap id="UserAtt" type="User" autoMapping="true">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="nickname" column="nickname"/>
</resultMap>
一对多
<mapper namespace="com.example.mappers.UserMappers">
<resultMap id="byAdress" type="UserAndManyAdress" autoMapping="true">
<id property="id" column="id"/>
<collection property="addressList" column="id" autoMapping="true" ofType="address">
<id column="taid" property="userId"/>
<result column="taid" property="userId"/>
</collection>
</resultMap>
<select id="manyAddress" resultMap="byAdress">
select *, t.id tid, ta.id taid
from t_user t
left join t_address ta on t.id = ta.user_id
where t.id = #{id}
</select>
</mapper>
分步查询
优势:将多表链接查询,改为分步单表。数据量大会提高性能
延迟加载
就是使用到的时候才会被加载
开启迟加载(默认没有开启延迟加载)
- lazyLoadingEnabled 默认 false,懒加载没有打开
- aggressiveLazyLoading 默认 true 积极加载的意思
案例
mybatis 设置 延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
环境配置
<mapper namespace="com.example.mappers.ordersMapper">
<resultMap id="findMap" type="orders" autoMapping="true">
<!-- 对订单信息进行配置 -->
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<!-- 实现用户延迟加载 -->
<!-- select 指定延迟加载需要执行的 sql 语句 -->
<!-- colum 关联用户信息的列 -->
<association property="user" column="user_id" javaType="user" select="com.example.mappers.UserMapper.findUserByid">
</association>
</resultMap>
<select id="findOrdersUserLazyLoading" resultMap="findMap">
select *
from orders
</select>
</mapper>
延迟加载对象
<mapper namespace="com.example.mappers.UserMapper">
<!-- 延迟加载 -->
<select id="findUserByid" resultType="com.example.beans.User">
select *
from user
where id = #{id}
</select>
</mapper>
运行代码
public static void main(String[] args) throws IOException {
// 配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取工厂
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
// 获取执行
SqlSession sqlSession = build.openSession(true);
List<Orders> loading = sqlSession.getMapper(ordersMapper.class).findOrdersUserLazyLoading();
for (Orders orders : loading) {
// 执行到这里才实现按需要加载
User user = orders.getUser();
}
sqlSession.close();
}
刚开始执行查询到了三个结果,执行了一个 sql
Preparing: select * from orders
mappers.ordersMapper.findOrdersUserLazyLoading]-==> Parameters:
mappers.ordersMapper.findOrdersUserLazyLoading]-<== Total: 3
继续执行,执行到了才查询第二个 sql
mappers.UserMapper.findUserByid]-==> Preparing: select * from user where id = ?
mappers.UserMapper.findUserByid]-==> Parameters: 1(Integer)
mappers.UserMapper.findUserByid]-<== Total: 1
处理器
创建处理器,并继承BaseTypeHandler<Double> 泛型表示数据库的字段类型
根据情况会拦截数据库表每一个字段
下面的 resultMap 为每个字段加处理器,这样才能被拦截
<resultMap id="lossTestMap" autoMapping="true" type="demo.grid.vo.IomPerFormAnceDataVoTest">
<result column="subtract" property="subtract" typeHandler="demo.grid.handler.DoubleTypeHandler"/>
<result column="lastsubtractSrc" property="lastSubtractSrc" typeHandler="demo.grid.handler.DoubleTypeHandler"/>
<result column="predictPower" property="predictPower" typeHandler="demo.grid.handler.DoubleTypeHandler"/>
<result column="lastsubtract" property="lastsubtract" typeHandler="demo.grid.handler.DoubleTypeHandler"/>
</resultMap>
<select id="lossTest" resultMap="lossTestMap">
select
subtract,
lastsubtractSrc,
lastsubtract,
predictPower
from xxx
</select>
public class DoubleTypeHandler extends BaseTypeHandler<Double> {
/**
* 执行插入或者更新时参数非空,则调用
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Double parameter, JdbcType jdbcType) throws SQLException {
}
/**
* 从数据库查询的某个字段非空则调用---这里处理的是,四舍五入并保留两位小数
*/
@Override
public Double getNullableResult(ResultSet rs, String columnName) throws SQLException {
double v = rs.getDouble(columnName);
if (v == 0.0) {
return null;
} else {
return Math.round(v * 100.0) / 100.0;
}
}
/**
* 执行插入或者更新时参数为空,则调用
*/
@Override
public Double getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
/**
* 从数据库中查询出来的某个字段为空,则调用
*/
@Override
public Double getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
缓存
缓存:只针对于查询
用于减轻数据压力
如果缓冲区域有数据,不需要再去数据库中查询,大大提高性能
一级缓存(SqlSession级别 - - - 默认开启)
- 操作数据库时需要 构造 sqlSession 对象
- 对象中有一个数据结构 hashMap 存储缓存数据
- 不同的 sqlSession 之间缓存数据区域 HashMap 是互相不影响的
就算 mapper 对象不一样,也是只查询一次,其实跟mapper对象关系不大
- 执行原理
- 第一次发起查询用户1
- 先去缓存中找是否有id为1 的用户
- 如果没有就从数据库查到,并存到一级缓存中
- 如果有直接从缓存获取
如果 sqlSession 去执行commit 操作(执行插入、更新、删除)会清空 sqlsession 中的一级缓存,避免脏读
// 获取执行
SqlSession sqlSession = build.openSession(true);
// 执行 sql
List<User> select = sqlSession.getMapper(cacheMapper.class).getSelect(12);
System.out.println(select);
// 再打印一次
List<User> select2 = sqlSession.getMapper(cacheMapper.class).getSelect(12);
System.out.println(select2);
sqlSession.close();
打印结果
可以看出 Total: 1,只去数据库查询了一次,也就是说一级缓存默认开启的
[com.example.mappers.cacheMapper.getSelect]-<== Total: 1
[User{id = 12, username = 3109, password = 1234, nickname = null}]
[User{id = 12, username = 3109, password = 1234, nickname = null}]
它的范围就是 sqlSession,如果这个对象获取了两个,那么缓存也就是两个独立的。就会去数据库查询两次。sqlsession关闭即销毁
一级缓存失效四种情况
只要 SqlSession 没有 flush 或 close,它就存在。
当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
二级缓存(mapper)
开启二级缓存
在 mybatis-config.xml设置
<setting name="cacheEnabled" value="true"/>
开启本 UserMapper.xml 的二级缓存 也要配置
<mapper namespace="com.example.mappers.UserMapper">
<!-- type属性:执行缓存的类,默认就是 cache -->
<cache/>
案例
配置信息和一级缓存,还有上面的
必须关闭资源,才能写入到二级缓存
// 创建代理对象
SqlSession sqlSession1 = build.openSession();
SqlSession sqlSession2 = build.openSession();
SqlSession sqlSession3 = build.openSession();
User userByid1 = sqlSession1.getMapper(UserMapper.class).findUserByid(1);
System.out.println(userByid1);
sqlSession1.close(); // 不执行 关闭操作无法写入到二级缓存区域
User userByid2 = sqlSession2.getMapper(UserMapper.class).findUserByid(1);
System.out.println(userByid2);
sqlSession2.close();
User userByid3 = sqlSession3.getMapper(UserMapper.class).findUserByid(1);
System.out.println(userByid3);
sqlSession3.close();
调用 pojo类实现序列化接口(需要对应的JavaBean 类实现 Serializable接口)
为了将缓存取出执行反序列化操作,因为二级缓存存储介质多种多样
多个 sqlSession 可以共用二级缓存
- 首先开启二级缓存
- 每一个UserMapper有一个二级缓存区域
- 缓存区域是按照 namespace分
- 也就是买一个 namespace mapper有一个二级缓存区域
- 如果namespace相同,他们将会使用相同的二级缓存区域
- 如果有一个执行 commit sql 那么就会清空二级缓存
命中率 0
[com.example.mappers.UserMapper]-Cache Hit Ratio [com.example.mappers.UserMapper]: 0.0
这时,sqlSession4 更新了数据,那么清空了二级缓存。这时其他在查询都是就没有了
User user = new User();
user.setUsername("张ming明");
user.setId(1);
sqlSession4.getMapper(UserMapper.class).updateById(user);
sqlSession4.commit();
sqlSession4.close();
禁用二级缓存 配置(useCache)
在对应的sql 映射文件中设置xml 文件中设置 useCache=“false”
<select id="findUserByid" resultType="com.example.beans.User" useCache="false">
select *
from user
where id = #{id}
</select>
刷新缓存(清空缓存)
- flushCache=“false” 不刷新缓存
- 默认是true 刷新
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="false">
echcache 分布式缓存框架,整合mybatis
不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统 开发。所以要使用分布式缓存对缓存数据进行集中管理。
就是让多个服务器之间缓存进行统一管理
- mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
设置缓存逻辑
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。
mybatis 和 ehcache 整合包中提供了一个 cache 接口的实现类。
需要在cache <cache type = " 指定实现cache接口的实现类 "/>
需要导入 jar包 & ehcache 配置文件
- mybatis-ehcache
- ehcache-core
使用整合好的
<cache type=“org.mybatis.caches.ehcache.EhcacheCache”/>
配置文件
<ehcache>
<diskStore path="D:\ffff"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<!--
maxElementsInMemory="10000" // 最大缓存个数
eternal="true" // 缓存对象是否永久有效,一旦设置了永久缓存timeout将不起作用,
timeToIdleSeconds="120" // 表示缓存对象在失效前的允许闲置时间
timeToLiveSeconds="120" // eternal=false 对象不是永久有效时,该属性才生效;表示缓存对象在失效前允许存活的时间
overflowToDisk="true" // overflowToDisk 表示当内存中的对象数量达到 maxElementslnMemory时, Ehcache 是否将对象写到磁盘中
maxElementsOnDisk="10000000"
diskPersistent="false" // 是否缓存虚拟机重启期数据
diskExpiryThreadIntervalSeconds="120" // 表示磁盘失效线程运行时时间
memoryStoreEvictionPolicy="LRU"
-->
<cache name="User_cache"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
二级缓存应用场景
- 访问多的查询
- 用户对查询结果实时性要求不高
- 耗时较高的统计分析sql、电话账单查询sql等
- 实现方法如下:通过设置刷新间隔时间
- 设置缓存刷新间隔flushInterval,比如设置为30分钟
局限性
对数据量大,并且细粒度高的数实现不好
- 比如;商品信息缓存,存入一万个,有一个商品更新了,所有都要清空
1285





