什么是mybatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
mybatis开发流程
- 引入依赖
- 创建核心配置文件
- 创建实体类
- 创建Mapper映射文件
- 初始化SessionFactory
- 利用sqlSession对象操作数据
引入依赖
pom.xml
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
创建核心配置文件
resources/mybatis-config.xml
<?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>
<!-- 驼峰命名设置 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
</plugin>
</plugins>
<!-- 设置默认指向的数据库 -->
<environments default="dev">
<!-- 配置环境,不同的环境不同的id名字 -->
<environment id="dev">
<!-- 采用JDBC的方式对数据库事务进行commit/rollback-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="xxxx"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/goods.xml"/>
</mappers>
</configuration>
创建实体类
public class Goods {
private Integer goodsId; //商品编号
private String title; // 标题
private String subTitle; //子标题
private Float originalCost; // 原始价格
private Float currentPrice; // 当前价格
private Float discount; // 折扣率
private Integer isFreeDelivery; // 是否包邮 1-包邮 0-不包邮
private Integer categoryId; // 分类编号
private List<GoodsDetail> goodsDetails;
//省略getter/setter
}
public class GoodsDetail {
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
private Goods goods;
}
public class Category {
private Integer categoryId;
private String categoryName;
private Integer parentId;
private Integer categoryLevel;
private Integer categoryOrder;
}
public class GoodsDTO {
private Goods goods = new Goods();
private Category category;
private String test;
}
创建mapper映射文件
resources/mapper/goods.xml
<?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="goods">
<!-- 开启二级缓存
eviction策略
LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval /ms
-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
<select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods order by goods_id desc limit 10
</select>
<!-- 单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数 -->
<select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods where goods_id = #{value}
</select>
<!-- 多参数传递,使用parameterType指定Map接口,SQL中#{key}提取参数 -->
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
where
current_price between #{min} and #{max}
order by current_price
limit 0, #{limit}
</select>
<!-- 利用LinkedHashMap保存多表关联结果
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名 value是字段对应的值 , 字段类型根据表结构进行自动判断
优点: 易于扩展,易于使用
缺点: 太过灵活,无法进行编译时检查
-->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
<!--结果映射-->
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
<!--设置主键字段与属性映射-->
<id property="goods.goodsId" column="goods_id"></id>
<!--设置非主键字段与属性映射-->
<result property="goods.title" column="title"></result>
<result property="goods.originalCost" column="original_cost"></result>
<result property="goods.currentPrice" column="current_price"></result>
<result property="goods.discount" column="discount"></result>
<result property="goods.isFreeDelivery" column="is_free_delivery"></result>
<result property="goods.categoryId" column="category_id"></result>
<result property="category.categoryId" column="category_id"></result>
<result property="category.categoryName" column="category_name"></result>
<result property="category.parentId" column="parent_id"></result>
<result property="category.categoryLevel" column="category_level"></result>
<result property="category.categoryOrder" column="category_order"></result>
<!-- <result property="categoryName" column="category_name"/>-->
<result property="test" column="test"/>
</resultMap>
<select id="selectGoodsDTO" resultMap="rmGoods">
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
insert into
t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
values
(#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
<update id="update" parameterType="com.imooc.mybatis.entity.Goods">
update t_goods
set
title = #{title}
where
goods_id = #{goodsId}
</update>
<delete id="delete" parameterType="Integer">
delete from t_goods where goods_id = #{value}
</delete>
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
<where>
<if test="categoryId != null">
and category_id = #{categoryId}
</if>
<if test="currentPrice != null">
and current_price < #{currentPrice}
</if>
</where>
</select>
<!--
resultMap可用于说明一对多或者多对一的映射逻辑
id 是resultMap属性引用的标志
type 指向One的实体(Goods)
-->
<resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
<!-- 映射goods对象的主键到goods_id字段 -->
<id column="goods_id" property="goodsId"></id>
<!--
collection的含义是,在
select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
将得到的"商品详情"集合赋值给goodsDetails List对象.
-->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,10
</select>
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods where current_price < 1000
</select>
<insert id="batchInsert" parameterType="java.util.List">
insert into
t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.title}, #{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
</foreach>
</insert>
</mapper>
初始化SessionFactory(已整合到工具类)
SqlSessionFactory根据官方建议,使用单例模式保持全局唯一。
SqlSession则保证生命周期只在一次请求之中
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
public static void closeSession(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
}
}
}
利用sqlSession对象操作数据
public class MyBatisTestor {
@Test
public void testSqlSessionFactory() throws IOException {
//利用Reader加载classpath下的mybatis-config.xml核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//初始化sqlsessionFactory对象,同时解析mybatis-config.xml文件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
Connection connection = sqlSession.getConnection();
System.out.println(connection);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (sqlSession != null) {
//如果type="POLLED",代表使用连接池,close则是将连接回收到连接池中
//如果type="UNPOLLED",代表直连,close则会调用connection.close直接将连接关闭
sqlSession.close();
}
}
}
@Test
public void testMybatisUtils() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Connection connection = sqlSession.getConnection();
System.out.println(connection);
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testSelectAll() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<Goods> goodsList = sqlSession.selectList("goods.selectAll");
for (Goods goods : goodsList) {
System.out.println(goods.getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testSelectById() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 739);
System.out.println(goods.getTitle());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testSelectByPriceRange() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Map param = new HashMap();
param.put("min", 100);
param.put("max", 500);
param.put("limit", 10);
List<Goods> goodsList = sqlSession.selectList("goods.selectByPriceRange", param);
for (Goods goods : goodsList) {
System.out.println(goods.getTitle() + ":" + goods.getCurrentPrice());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
/**
* 利用Map接收关联查询结果
* @throws Exception
*/
@Test
public void testSelectGoodsMap() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
List<Map> list = session.selectList("goods.selectGoodsMap");
for(Map map : list){
System.out.println(map);
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testSelectGoodsDTO() {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
for(GoodsDTO dto : list){
System.out.println(dto.getGoods().getTitle() + ":" + dto.getCategory().getCategoryName());
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testInsert() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("testTitle");
goods.setSubTitle("testSubTitle");
goods.setOriginalCost(200f);
goods.setCurrentPrice(100f);
goods.setDiscount(0.5f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
int num = sqlSession.insert("goods.insert", goods);
sqlSession.commit();
System.out.println(goods.getGoodsId());
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testUpdate() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 2677);
goods.setTitle("sdfkssdfksdk");
int num = sqlSession.update("goods.update", goods);
sqlSession.commit();
System.out.println(goods.getGoodsId());
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testDelete() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
int num = sqlSession.update("goods.delete", 2677);
sqlSession.commit();
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testDynamicSQL() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Map param = new HashMap();
param.put("categoryId", 44);
param.put("currentPrice", 500);
List<Goods> list = sqlSession.selectList("goods.dynamicSQL", param);
for (Goods g : list) {
System.out.println(g.getTitle() + " : " + g.getCategoryId() + " : " + g.getCurrentPrice());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
/**
* 一对多对象关联查询
* @throws Exception
*/
@Test
public void testOneToMany() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.openSession();
List<Goods> list = session.selectList("goods.selectOneToMany");
for(Goods goods:list) {
System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testManyToOne() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.openSession();
List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
for(GoodsDetail goodsDetail : list) {
System.out.println(goodsDetail.getGdPicUrl() + ":" + goodsDetail.getGoods().getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testSelectPage() {
SqlSession session = null;
try {
session = MyBatisUtils.openSession();
// startPage方法会自动将下一次查询进行分页
PageHelper.startPage(2, 10);
Page<Goods> page = (Page) session.selectList("goods.selectPage");
System.out.println("总页数:" + page.getPages());
System.out.println("总记录:" + page.getTotal());
System.out.println("当前页码:" + page.getPageNum());
List<Goods> data = page.getResult();
for (Goods g : data) {
System.out.println(g.getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testBatchInsert() {
SqlSession session = null;
try {
long st = System.currentTimeMillis();
session = MyBatisUtils.openSession();
List list = new ArrayList();
for (int i=0; i < 10000; i++) {
Goods goods = new Goods();
goods.setTitle("测试商品");
goods.setSubTitle("测试子标题");
goods.setOriginalCost(200f);
goods.setCurrentPrice(100f);
goods.setDiscount(0.5f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
// insert()方法返回值代表本次成功插入的记录总数
list.add(goods);
}
session.insert("goods.batchInsert", list);
session.commit();
long et = System.currentTimeMillis();
System.out.println("执行时间:" + (et - st) + "毫秒");
} catch (Exception e) {
if (session != null) {
session.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(session);
}
}
}
细节补充
selectKey和useGenerateKeys属性的用法和区别
selectKey以标签形式应用于xml的执行语句标签中,需要明确指定获取属性值,适合所有关系型数据库
useGenerateKeys作为标签属性,使用起来较为方便,但是只能适用于支持“自增主键”的数据库。
mybatis的两种传值方式
${value}
这种方式会直接做字符替换,因此有sql注入的风险,使用建议尽量在代码中由开发者使用,而不要通过用户输入来决定输入值,如果一定要使用用户输入的方案,需要对输入字符串进行校验和转义
#{value}
这种方式会预编译传值,会将字符转义后插入sql,比较安全,也是平时常用的方案
mybatis的一些高级用法
日志
日志用于记录操作事件的时间和执行动作,可以用来查看历史操作数据以及诊断问题
在mybatis中,如果没有引入日志相关的jar包,会自动忽略这个功能,如果有引入jar包,则会根据jar包提供的日志功能打印相关操作信息
关于日志的详细,可以参考日志
动态SQL
如果需要灵活的使用sql,而不必纠结于各种动态使用sql中的种种细节,就需要使用mybatis提供的动态sql功能。
官方文档 针对 if, where, choose, foreach等标签都有详细解释
缓存
利用好缓存,可以高效利用mybatis查询数据而不必频繁访问数据库
mybatis中对缓存的支持分为一级缓存和二级缓存,一级缓存是会话级别的,随着会话的结束而被清理。如果需要全局缓存的情况下,就需要手动开启二级缓存(全局缓存也只是针对mybatis3 所提出的命名空间的全局缓存)
详见文档