添加依赖
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.30version>
dependency>
编写配置文件
# 日志模块的配置
log4j.rootLogger=ERROR, stdout
# 打印级别
log4j.logger.site.leric.mybatis=DEBUG
# trace打印更多
#log4j.logger.site.leric.mybatis=TRACE
# 打印某个mapper 甚至单个方法的日志级别
log4j.logger.site.leric.mybatis.dao.VideoMapper.selectById=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
public class Main {
public static void main(String[] args) throws IOException {
// 读取配置文件
String resources = "config/mybatis-config.xml";
// 构建sessionFactory
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取session
// 使用try方法获取会自动关闭
try(SqlSession session = factory.openSession()){
VideoMapper mapper = session.getMapper(VideoMapper.class);
Video video = mapper.selectById(36);
System.out.println(video.toString());
// 使用注解
// List
List<Video> videos = mapper.selectListByXML();
for (Video video1 : videos) {
System.out.println(video1.toString());
}
}
}
一个映射器由接口与mapper配置文件组成
public interface VideoMapper {
/**
* 根据视频id查找对象
* @param videoId
* @return
*/
// 使用注解给参数取别名 取了别名就要用别名
Video selectById(@Param("video_id") int videoId);
/**
* 查找全部视频成列表
* @return
*/
@Select("Select * from video") // 使用注解实现sql绑定
List<Video> selectList();
}
namespace:名称空间,一般保持全局唯一,最好能与dao曾的java接口一致,将自动关联相关方法
可以映射相关的sql语句到对应的方法和参数、返回类型
id:当前mapper下唯一
resultType:sql查询结果集的封装对象
传参与入参
在接口的方法里使用@Param为参数指定别名
配置问文件SQL语句使用#{ } 或者 ${ } 引用参数
推荐使用#{ } ,因为${ } 实际上是拼接SQL命令,有面临QL注入的风险
<mapper namespace="site.leric.mybatis.dao.VideoMapper">
<select id="selectById" resultType="site.leric.mybatis.domain.Video">
select * from video where id = #{video_id}
select>
<select id="selectListByXML" resultType="site.leric.mybatis.domain.Video">
select * from video
select>
mapper>
<select id="selectById" parameterType="java.lang.Integer" resultType="site.leric.mybatis.domain.Video">
select * from video where id = #{videoid,jdbcType=INTEGER}
select>
使用paramaterType指定自定义参数类型
在select语句中若传入封装类,需要取得封装类的字段作为参数,在sql语句中需要pojo.field
<select id="selectByPointAndTitleLikeOnVideo" parameterType="site.leric.mybatis.domain.Video" resultType="site.leric.mybatis.domain.Video">
select * from video where point = #{video.point} and title like concat('%',#{video.title},'%')
select>
模糊查询
模糊查询不能使用字符串拼接,需要使用自带函数concat(‘%’,paramater,‘%’)
<select id="selectByPointAndTitleLike" resultType="site.leric.mybatis.domain.Video">
select * from video where point = #{point} and title like concat('%',title,'%')
select>
<mapper namespace="site.leric.mybatis.dao.VideoMapper">
<sql id="base_select" >
id,summary,title
sql>
<select id="selectById" resultType="video">
select <include refid="base_select"/> from video where id = #{video_id}
select>
mapper>
插入一条数据并回填主键
插入数据若参数类型是自定义的封装类,则入参是只需要调用封装对象的属性名即可
<insert id="add" parameterType="site.leric.mybatis.domain.Video" useGeneratedKeys="true" keyProperty="id"
keyColumn="id">
INSERT INTO `springboot`.`video`(`title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES
(#{title,jdbcType=VARCHAR},#{summary,jdbcType=VARCHAR},#{coverImg,jdbcType=VARCHAR},
#{price,jdbcType=INTEGER},#{createTime,jdbcType=TIMESTAMP},#{point,jdbcType=DOUBLE});
insert>
批量插入多条数据
参数类型是集合,在MyBatis中需要使用
并且在入参时,需要使用index.field 取得封装对象的属性
useGeneratedKeys=“true” 开启主键回填
keyColumn=“id” 指定数据库中的主键列名
keyProperty=“id” 指定映射主键的field
INSERT INTO `springboot`.`video`(`title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES
(#{video.title,jdbcType=VARCHAR},#{video.summary,jdbcType=VARCHAR},#{video.coverImg,jdbcType=VARCHAR},
#{video.price,jdbcType=INTEGER},#{video.createTime,jdbcType=TIMESTAMP},#{video.point,jdbcType=DOUBLE})
<update id="updateVideo" parameterType="site.leric.mybatis.domain.Video">
UPDATE `springboot`.`video`
SET
`title` = #{title,jdbcType = VARCHAR},
`summary` = #{summary,jdbcType = VARCHAR},
`cover_img` = #{coverImg,jdbcType = VARCHAR},
`price` = #{price,jdbcType = INTEGER},
`create_time` = #{createTime,jdbcType = TIMESTAMP},
`point` = #{point,jdbcType = DOUBLE}
WHERE `id` = #{id,jdbcType = VARCHAR};
update>
更新添加记录
trim用于判断字符串的开始与结尾且可以指定字符穿前缀以及去除最后一个字符的后缀
if 标签可以通过判断传⼊的值来确定查询条件,test 指定⼀个OGNL表达式
使用if标签,判断的时候需要依靠pojo的数据类型来判断条件,基本数据类型默认值与引用数据类型的默认是是不一样的
<update id="updateVideoSelective" parameterType="video">
UPDATE `springboot`.`video`
<trim prefix="set" suffixOverrides=",">
<if test="title !=null">`title` = #{title,jdbcType = VARCHAR},if>
<if test="summary !=null">`summary` = #{summary,jdbcType = VARCHAR},if>
<if test="coverImg !=null">`cover_img` = #{coverImg,jdbcType = VARCHAR},if>
<if test="price !=0">`price` = #{price,jdbcType = INTEGER},if>
<if test="createTime !=null">`create_time` = #{createTime,jdbcType = TIMESTAMP},if>
<if test="point != 0.0">`point` = #{point,jdbcType = DOUBLE},if>
trim>
WHERE `id` = #{id,jdbcType = VARCHAR}
update>
<delete id="deleteByCreateTimeAndPrice" parameterType="java.util.Map">
delete from video where create_time > #{createTime} and price #{price}
delete>
由于MyBatis的sql写在XML⾥⾯, 有些sql的语法符号和xml⾥⾯的冲突
⼤于等于 = ]]>
⼩于等于
typeAlias 配置全局别名
别名配置之后使用全类名依然能正常使用
<typeAliases>
<!--<typeAlias type="net.xdclass.online_class.domain.Video"
alias="Video"/>-->
<!-- 指定domain包下的全部类都以类名作为别名-->
<package name="net.xdclass.online_class.domain"/>
</typeAliases>
Mybatis的SQL语句返回结果有两种
<resultMap id="VideoResultMap" type="Video">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="video_title" property="title" jdbcType="VARCHAR"/>
<result column="summary" property="summary" jdbcType="VARCHAR"/>
<result column="cover_img" property="coverImg" jdbcType="VARCHAR"/>
resultMap>
程序经常要调⽤的对象存在内存中,⽅便其使⽤时可以快速调⽤,不必去数据库或者其他持久化设备中查询,主要就是提⾼性能
// 演示一级缓存
for (int i = 0; i < 2; i++) {
Video video = mapper.selectBseFieldByIdWithResultMap(36);
System.out.println(video.toString());
}
==============只发送了一次sql==============
DEBUG [main] - ==> Preparing: SELECT id , title AS video_title,summary,cover_img from video where id = ?
DEBUG [main] - ==> Parameters: 36(Integer)
DEBUG [main] - <== Total: 1
Video{id=36, title='19年录制ES6教程ES7ES8实战应用', summary='https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/es67/es67_detail.png
', CoverImg='https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/es67/es.png
', price=0, creteTime=null, point=0.0}
Video{id=36, title='19年录制ES6教程ES7ES8实战应用', summary='https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/es67/es67_detail.png
', CoverImg='https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/es67/es.png
', price=0, creteTime=null, point=0.0}
简介:⼆级缓存是namespace级别的,多个SqlSession去操作同⼀个namespace下的Mapper的sql语句,多个SqlSession可以共⽤
⼆级缓存,如果两个mapper的namespace相同,
(即使是两个mapper,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域中,但是建议是每个Mapper单独的名称空间)
基于PerpetualCache 的 HashMap本地缓存,可⾃定义存储源,如 Ehcache/Redis等
默认是没有开启⼆级缓存
操作流程:第⼀次调⽤某个namespace下的SQL去查询信息,查询到的信息会存放该mapper对应的⼆级缓存区域。
第⼆次调⽤同个namespace下的mapper映射⽂件中,相同的sql去查询信息,会去对应的⼆级缓存内取结果
失效策略:执⾏同个namespace下的mapepr映射⽂件中增删改sql,并执⾏了commit操作,会清空该⼆级缓存
注意:实现⼆级缓存的时候,MyBatis建议返回的POJO是可序列化的, 也就是建议实现Serializable接⼝
使用自定义存储源头必须实现序列化接口
缓存淘汰策略:会使⽤默认的 LRU 算法来收回(最近最少使⽤的)
如何开启某个⼆级缓存 mapper.xml⾥⾯配置
mapper配置
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
全局配置
<settings>
<setting name="cacheEnabled" value="true" />
settings>
演示代码
SqlSession session = factory.openSession();
VideoOrderMapper mapper1 = session.getMapper(VideoOrderMapper.class);
List<VideoOrder> videoOrders = mapper1.queryVideoOrderList();
System.out.println(videoOrders.size());
// 提交清空一级缓存
session.commit();
VideoOrderMapper mapper2 = session.getMapper(VideoOrderMapper.class);
List<VideoOrder> videoOrders2 = mapper2.queryVideoOrderList();
System.out.println(videoOrders2.size());
在没有开启二级缓存的情况下
DEBUG [main] - ==> Preparing: select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title, u.name,u.head_img,u.create_time,u.phone from video_order as o left join user as u on o.user_id = u.id
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 6
6
DEBUG [main] - ==> Preparing: select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title, u.name,u.head_img,u.create_time,u.phone from video_order as o left join user as u on o.user_id = u.id
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 6
6
==============发送了两次sql==============
开启了二级缓存后
DEBUG [main] - Cache Hit Ratio [site.leric.mybatis.dao.VideoOrderMapper]: 0.0
DEBUG [main] - ==> Preparing: select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title, u.name,u.head_img,u.create_time,u.phone from video_order as o left join user as u on o.user_id = u.id
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 6
6
DEBUG [main] - Cache Hit Ratio [site.leric.mybatis.dao.VideoOrderMapper]: 0.5
6
==============只发送一次sql==============
在关闭二级请求,并且清理一级缓存
VideoOrderMapper mapper1 = session.getMapper(VideoOrderMapper.class);
List<VideoOrder> videoOrders = mapper1.queryVideoOrderList();
System.out.println(videoOrders.size());
// 提交清空一级缓存
session.commit();
List<VideoOrder> videoOrders2 = mapper1.queryVideoOrderList();
System.out.println(videoOrders2.size());
优先查询二级缓存==>一级缓存==>数据库
DEBUG [main] - ==> Preparing: select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title, u.name,u.head_img,u.create_time,u.phone from video_order as o left join user as u on o.user_id = u.id
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 6
6
DEBUG [main] - ==> Preparing: select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title, u.name,u.head_img,u.create_time,u.phone from video_order as o left join user as u on o.user_id = u.id
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 6
6
==============发送了两次sql==============
控制方法是否使用二级缓存
<select id="queryVideoOrderList" resultMap="VideoOrderResultMap" useCache="false">
select o.id,o.user_id,o.create_time,o.state,o.total_fee,o.video_id,o.video_title,
u.name,u.head_img,u.create_time,u.phone
from video_order as o left join user as u on o.user_id = u.id
select>
什么是懒加载: 按需加载,先从单表查询,需要时再从关联表去关联查询,能⼤⼤提⾼数据库性能,并不是所有场景下使⽤懒加载都能提⾼效率
Mybatis懒加载: resultMap⾥⾯的association、collection有延迟加载功能
开启懒加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
<resultMap id="VideoOrderResultMapLazy" type="VideoOrder">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="out_trade_no" property="outTradeNo"/>
<result column="create_time" property="createTime"/>
<result column="state" property="state"/>
<result column="total_fee" property="totalFee"/>
<result column="video_id" property="videoId"/>
<result column="video_title" property="videoTitle"/>
<result column="video_img" property="videoImg"/>
<association property="user" javaType="User" column="user_id"
select="findUserByUserId"/>
resultMap>
<select id="queryVideoOrderListLazy" resultMap="VideoOrderResultMapLazy">
select
o.id id,o.user_id ,o.out_trade_no,o.create_time,o.state,o.total_fee,o.video_id,o.video_title,o.video_img
from video_order o
select>
<select id="findUserByUserId" resultType="User">
select * from user where id=#{id}
select>
使⽤JDBC的事务管理
使⽤ java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())
使⽤MANAGED的事务管理
MyBatis⾃身不会去实现事务管理,⽽让程序的容器如(Spring, JBOSS)来实现对事务的管理
使用MANAGED一般不设置自动提交
Mybatis事务⼯⼚TransactionFactory 的两个实现类,对应两种事务管理方式
<environment id="development">
<transactionManager type="JDBC"/>
<transactionManager type="MANAGED"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/xdclass?
useUnicode=true&characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="xdclass.net"/>
dataSource>
environment>
两种数据库引擎
区别项 | Innodb | myisam |
---|---|---|
事务 | ⽀持 | 不⽀持 |
锁粒度 | ⾏锁,适合⾼并发 | 表锁,不适合⾼并发 |
是否默认 | 默认 | ⾮默认 |
⽀持外键 | ⽀持外键 | 不⽀持 |
适合场景 | 读写均衡,写⼤于读场景,需要事务 | 读多写少场景,不需要事务 |
全⽂索引 | 可以通过插件实现, 更多使⽤ElasticSearch | ⽀持全⽂索引 |
MyISAM不⽀持事务,如果需要事务则改为innodb引擎 更改数据库的表⾥⾯的引擎
// 设置事务自动提交
SqlSession session = factory.openSession(true);