MyBatis体系笔记(未完结)

打印 上一主题 下一主题

主题 860|帖子 860|积分 2580

MyBatis

什么是MyBatis

  • MyBatis是优秀的持久层框架
  • MyBatis使用XML将SQL与程序解耦,便于维护
  • MyBatis学习简单,执行高效,是JDBC的延伸
1.MyBatis开发流程


  • 引入MyBatis依赖
  • 创建核心配置文件
  • 创建实体(Entity)类
  • 创建Mapper映射文件
  • 初始化SessionFactory
  • 利用SqlSession对象操作数据
1.1引入MyBatis依赖

利用maven直接从仓库导入即可,有的小伙伴肯定不知道怎么去官网找,先来教一下好了。
官网链接:Maven Repository: maven (mvnrepository.com)
直接搜索mybatis然后点击第一个就好了

这里我们选择3.5.1版本

往下滑我们可以看到他的配置信息,复制到pom.xml即可

关于配置pom.xml时IDEA报错解决方案

有时候我们的idea会抽风,复制过来之后会报红或者显示无法在中央仓库中找到依赖,一般剪切掉它再贴一遍就好了,实在不行重启一下idea
  1. <dependency>
  2.  <groupId>org.mybatis</groupId>
  3.  <artifactId>Mybatis</artifactId>
  4.  <version>3.5.1</version>
  5. </dependency>
  6. //mysql包顺便也放这里了,如果使用mysql的同学记得加上
  7. <dependency>
  8.  <groupId>mysql</groupId>
  9.  <artifactId>mysql-connector-java</artifactId>
  10.  <version>5.1.47</version>
  11. </dependency>
复制代码
1.2创建核心配置文件

差不多和Spring的配置文件差不多格式
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3.        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4.        "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.    <environments default="development">
  7.        <environment id="development">
  8.            <transactionManager type="JDBC"/>
  9.            <dataSource type="POOLED">
  10.                <property name="driver" value="com.mysql.jdbc.Driver"/>
  11.                <property name="url" value="jdbc:mysql://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
  12.                <property name="username" value="root"/>
  13.                <property name="password" value="20030515"/>
  14.            </dataSource>
  15.        </environment>
  16.    
  17.        <environment id="produce">
  18.            <transactionManager type="JDBC"/>
  19.            <dataSource type="POOLED">
  20.                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  21.                <property name="url" value="jdbc:mysql://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
  22.                <property name="username" value="root"/>
  23.                <property name="password" value="20030515"/>
  24.            </dataSource>
  25.        </environment>
  26.    </environments>
  27.    <mappers>
  28.        
  29.        <mapper resource="mapper/ajaxdb.xml"/>
  30.    </mappers>
  31. </configuration>
复制代码
1.3创建实体(Entity)类

这里不再赘述,跟之前的JDBC连接数据库时要创建出一个跟表中字段相同的类是一样的。韩顺平Spring体系化笔记(内含ioc,aop,动态代理等底层原理) - 翰林猿 - 博客园 (cnblogs.com)
具体可以查看本文中第10小节,不过既然都学到了mybatis想必对此熟悉的不能再熟悉了。
1.4创建Mapper映射文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper
  3.        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4.        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="user">
  6.    
  7.    
  8.    <select id="selectAll" resultType="entity.ajaxdbEntity">
  9.        select * from user;
  10.    </select>
  11. </mapper>
复制代码
1.5初始化SessionFactory

在这里我们顺便编写一个工具类
  1. package utils;
  2. import entity.ajaxdbEntity;
  3. import org.apache.ibatis.io.Resources;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.apache.ibatis.session.SqlSessionFactory;
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  7. import org.junit.Test;
  8. import java.io.IOException;
  9. import java.io.Reader;
  10. import java.util.List;
  11. /**
  12. * @Author: 翰林猿
  13. * @Description: TODO
  14. **/
  15. public class MyBatisUtils {
  16.    /**
  17.     * MyBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
  18.     */
  19.    //利用static(静态)属于类不属于对象,且全局唯一
  20.    private static SqlSessionFactory sqlSessionFactory = null;
  21.    //利用静态块在初始化类时实例化sqlSessionFactory
  22.    static {
  23.        Reader reader = null;
  24.        try {
  25.            这里的Resources
  26.            reader = Resources.getResourceAsReader("mybatisConfig.xml");
  27.            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  28.        } catch (IOException e) {
  29.            e.printStackTrace();
  30.            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
  31.            throw new ExceptionInInitializerError(e);
  32.        }
  33.    }
  34.    /**
  35.     * openSession 创建一个新的SqlSession对象
  36.     * SqlSession对象类似于JDBC中的Connection
  37.     * @return SqlSession对象
  38.     */
  39.    public static SqlSession openSession() {
  40.        return sqlSessionFactory.openSession();
  41.    }
  42.    /**
  43.     * 释放一个有效的SqlSession对象
  44.     *类似于JDBC中释放获取到的Connection
  45.     * @param session 准备释放SqlSession对象
  46.     */
  47.    public static void closeSession(SqlSession session) {
  48.        if (session != null) {
  49.            session.close();
  50.        }
  51.    }
  52.    
  53.    //简单测试一下
  54.     @Test
  55.    public void test(){
  56.        //类似于获取Connection
  57.        SqlSession sqlSession = MyBatisUtils.openSession();
  58.        List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectAll");
  59.        for (ajaxdbEntity dao : ajaxdbDaoList) {
  60.            System.out.println(dao.getId());
  61.            System.out.println(dao.getUsername());
  62.        }
  63.    }
  64. }
复制代码
mybatis底层实现(造轮子)


 
 
2.SQL

SQL传参及查询

回忆之前的JDBC中在组织sql语句时 使用问号动态传入参数 进行查询的操作,同样我们mybatis也有。
  1. <select id="selectList" resultType="entity.ajaxdbEntity" parameterType="Map">
  2.        select * from user
  3.        where id between #{min} and #{max}
  4.        order by id;
  5.    </select>
复制代码
这里我们使用map的形式作为参数传递,那么怎么使用呢,修改一下我们的 Test 函数作为示例。
  1. @Test
  2.    public void test(){
  3.        //类似于获取Connection
  4.        SqlSession sqlSession = MyBatisUtils.openSession();
  5.        HashMap map = new HashMap();
  6.        map.put("min",1);
  7.        map.put("max",3);
  8.        //mybatis底层会将传入的map解析,找到#{key}对应的value填入sql语句中
  9.        List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectList",map);
  10.        for (ajaxdbEntity map : ajaxdbDaoList) {
  11.            System.out.println(map);
  12.        }
  13.    }
复制代码
多表查询

多表查询时我们不再需要填入parameterType,而且resultType使用Map或者LinkHashMap
使用Map的话,查询出来的字段顺序是混乱的,具体看Map的底层原理,而LinkHashMap是按顺序的。
但是这种查询方式有一个很大的缺点,你应该也已经发现了,为什么我们的resultType不再是实体Entity类了?
这种查询方式不需要经过验证,他什么东西都可以直接查询出来。所以在企业中开发大型项目还是一般不使用这种方式。
  1.    <select id="selectTwice"  resultType="Map">
  2.        select * from user,user2;
  3.    </select>
复制代码
所以我们使用ResultMap结果映射,来解决一下这个问题。
ResultMap结果映射查询

作用:首先是完成数据库字段与实体类字段的映射(类似之前为了解决数据库字段和实体类字段不相同采用AS语句完成映射),其次就是解决上面所提到的问题。
说白了,还是Entity那套,再创建一个类,作为多表查询的实体类,只不过这个类里有多个entity的字段罢了。那么我们来写一下这个字段吧。
  1. package entity;
  2. /**
  3. * @Author: 翰林猿
  4. * @Description: 多表查询实体类
  5. **/
  6. public class UserDTO {
  7.    private user user = new user();     //user表
  8.    private String cn;                 //user2表中的cn字段(ChineseName的缩写)
  9.    @Override
  10.    public String toString() {
  11.        return "UserDTO{" +
  12.                "user=" + user +
  13.                ", cn='" + cn + '\'' +
  14.                '}';
  15.    }
  16.    public user getUser() {
  17.        return user;
  18.    }
  19.    public void setUser(user user) {
  20.        this.user = user;
  21.    }
  22.    public String getCn() {
  23.        return cn;
  24.    }
  25.    public void setCn(String cn) {
  26.        this.cn = cn;
  27.    }
  28.    public UserDTO() {
  29.    }
  30.    public UserDTO(user ajaxdbEntity, String cn) {
  31.        user = ajaxdbEntity;
  32.        this.cn = cn;
  33.    }
  34. }
复制代码
好,我们再来看看基本使用方法,我们要定义一个resultMap标签,id值就是作为这段resultMap的别名,type是最后查询返回的类的全路径,然而里面又有很多种标签,这里我们就介绍2个,其他的请大家自己前往mybatis官网查看教程。
我们的id和result标签中又包括两个参数,property以及column

  • property :填写的必须与上面的type中的类的字段完全相同,(也就是UserDTO中的字段),其中有一个字段是user类的,那么为了拿到user类中的字段直接使用 . 运算符即可。
  • column :数据库中的列名,或者是列的别名。
  1.    <resultMap id="selectDTO" type="entity.UserDTO">
  2.        <id property="user.id" column="id"></id>
  3.        <result property="user.username" column="username"/>
  4.        <result property="user.pwd" column="pwd"/>
  5.        <result property="user.email" column="email"/>
  6.        <result property="cn" column="cn"/>
  7.         property对应实体类的属性 ,colum对应数据库的字段
  8.    </resultMap>
  9.    <select id="selectTwice"  resultMap="selectDTO">
  10.        select user.* , cn from user,user2;
  11.    </select>
复制代码
  1. /**
  2. * @Author: 翰林猿
  3. * @Description: 多表查询实体类,测试
  4. **/
  5. @Test
  6.    public void test(){
  7.        //类似于获取Connection
  8.        SqlSession sqlSession = MyBatisUtils.openSession();
  9.        List<UserDTO> ajaxdbDaoList = sqlSession.selectList("User.selectTwice");
  10.        for (UserDTO map : ajaxdbDaoList) {
  11.            System.out.println(map);
  12.        }
  13.    }
复制代码
 
 
SQL插入

我们在写sql插入语句的时候有时候要写很多列名,真的是非常令人烦恼啊,很显然,mybais开发人员也想到了这点,用foreach标签节约开发时间。
foreach元素的属性主要有 item,index,collection,open,separator,close。

  • item表示集合中每一个元素进行迭代时的别名,
  • index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,
  • open表示该语句以什么开始,
  • separator表示在每次进行迭代之间以什么符号作为分隔符,
  • close表示以什么结束。
  1.    
  2.    
  3.    <insert id="batchInsert" parameterType="java.util.List">
  4.        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
  5.        VALUES
  6.        <foreach collection="list" item="item" index="index" separator=",">
  7.            (#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
  8.        </foreach>
  9.    </insert>
复制代码
注意新增数据之后,要记得提交事务
  1.    /**
  2.     * 新增数据,示范用例
  3.     */
  4.    @Test
  5.    public void testInsert() throws Exception {
  6.        SqlSession session = null;
  7.        try{
  8.            session = MyBatisUtils.openSession();
  9.            Goods goods = new Goods();
  10.            goods.setTitle("测试商品");
  11.            goods.setSubTitle("测试子标题");
  12.            goods.setOriginalCost(200f);
  13.            goods.setCurrentPrice(100f);
  14.            goods.setDiscount(0.5f);
  15.            goods.setIsFreeDelivery(1);
  16.            goods.setCategoryId(43);
  17.            //insert()方法返回值代表本次成功插入的记录总数
  18.            int num = session.insert("goods.insert", goods);
  19.            session.commit();//提交事务数据
  20.            System.out.println(goods.getGoodsId());
  21.        }catch (Exception e){
  22.            if(session != null){
  23.                session.rollback();//回滚事务
  24.            }
  25.            throw e;
  26.        }finally {
  27.            MyBatisUtils.closeSession(session);
  28.        }
  29.    }
复制代码
SQL删除

删除同理
  1. <delete id="batchDelete" parameterType="java.util.List">
  2.     DELETE FROM t_goods WHERE goods_id in
  3.     <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
  4.         #{item}
  5.     </foreach>
  6. </delete>
复制代码
  1. /**
  2. * 删除数据
  3. */
  4. @Test
  5. public void testDelete() throws Exception {
  6.    SqlSession session = null;
  7.    try{
  8.        session = MyBatisUtils.openSession();
  9.        int num = session.delete("goods.delete" , 739);
  10.        session.commit();//提交事务数据
  11.    }catch (Exception e){
  12.        if(session != null){
  13.            session.rollback();//回滚事务
  14.        }
  15.        throw e;
  16.    }finally {
  17.        MyBatisUtils.closeSession(session);
  18.    }
  19. }
复制代码
动态SQL

在我们逛淘宝的时候,是否有发现在搜索商品时有很多标签可以选择,比如说指定某个品牌,某种类型等等。其实从开发者的角度上来看,是不是还是一个SQL语句罢了,只不过多加了几个参数,但是问题就在于,这些参数如何动态的加载进去,在想要的时候才用他呢?
这里引出mybatis的动态SQL语句技术,不过是加个标签罢了。底层依旧是之前那套,dom4j+动态代理+反射完成的。
  1. <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
  2.    select * from t_goods
  3.    <where>                                 //SQL种的where改成使用where标签
  4.      <if test="categoryId != null">          //如果map里有这个参数,就加上下面这句
  5.          and category_id = #{categoryId}
  6.      </if>
  7.      <if test="currentPrice != null">
  8.          and current_price < #{currentPrice}
  9.      </if>
  10.    </where>
  11. </select>
复制代码
  1. /**
  2. * 动态SQL语句
  3. */
  4. @Test
  5. public void testDynamicSQL() throws Exception {
  6.    SqlSession session = null;
  7.    try{
  8.        session = MyBatisUtils.openSession();
  9.        Map param = new HashMap();
  10.        param.put("categoryId", 44);
  11.        param.put("currentPrice", 500);
  12.        //查询条件
  13.        List<Goods> list = session.selectList("goods.dynamicSQL", param);
  14.        for(Goods g:list){
  15.            System.out.println(g.getTitle() + ":" +
  16.                    g.getCategoryId()  + ":" + g.getCurrentPrice());
  17.        }
  18.    }catch (Exception e){
  19.        throw e;
  20.    }finally {
  21.        MyBatisUtils.closeSession(session);
  22.    }
  23. }
复制代码
3.Mybatis二级缓存机制

为了提高查询效率,减少数据库的访问次数,Mybatis采用了两层缓存机制,并分为一级缓存和二级缓存
因为一级缓存的命中率可能较低,所以还有一层二级缓存

  • 一级缓存默认开启,缓存范围SqlSession

    • (换句话说就是,只在SqlSession session = MyBatisUtils.openSession()的这个session里有效,当我们写了两遍这个代码,就是两个不同的SqlSession对象)
    • 拿上面的代码举例,就是你使用这个session不管查询多少次相同的语句,都是从缓存里拿出来的一个结果
    1. session.selectList("goods.dynamicSQL", param);
    2. session.selectList("goods.dynamicSQL", param);
    3. session.selectList("goods.dynamicSQL", param);
    4. //最后都是相同的结果,内存地址都是一样的
    复制代码
  • 二级缓存手动开启,属于范围Mapper Namespace

    • (换句话说,就是只要是使用了之前我们定义mapper的namespace中的SQL语句里都有效)
    1. <mapper namespace="goods">
    2. ....各种SQL....
    3. </mapper>
    复制代码

注意:

当调用SqlSession的修改、添加、删除、commit()、close()等方法时,为了保证数据的一致性,mybatis会强制清空一级缓存。
如何理解这句话?用代码举例一下
  1. session = MyBatisUtils.openSession();
  2. Goods goods = session.selectOne("goods.selectById" , 1603);
  3. session.commit();       //commit提交时对该namespace缓存强制清空
  4. Goods goods2 = session.selectOne("goods.selectById" , 1603);
复制代码
这个时候,因为提交过一次事务,所以第二次的查询goods2时的hashcode与第一次的goods其实是不一样的,本质上是2次查询。而不是直接拿出goods已经查询出来的内容赋给goods2
开启二级缓存

在xml中的mapper配置一句话即可
  1.                <mapper namespace="goods">
  2. ....各种SQL....
  3. </mapper>        
复制代码
缓存大量数据性能问题

既然我们开启了缓存,那么就要考虑一下如果缓存了大量数据影响性能怎么办,其实对于查询大量数据的语句可以不使用缓存,而且在实际过程中,这种大量的查询也不会经常复用。
  1. <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
  2.    select * from t_goods order by goods_id desc limit 10
  3. </select>
复制代码
  1. <select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
  2.    select g.* , c.category_name,'1' as test from t_goods g , t_category c
  3.    where g.category_id = c.category_id
  4. </select>
复制代码
4.对象关联查询(一对多、多对一)

我们知道,一个表内有很多个实体,可能有一个实体对应了很多个对象,有一些则是一对一,多对一,多对多等等。

所以引出我们的关联查询
一对多查询:

先来举个例子:比如说我们要查询一个商品,但是一个商品Goods又对应了很多个商品细节GoodsDetail(一对多)
所以我们的商品细节表GoodsDetail要持有我们的商品表Goods的主键 goodsId,那么除掉建表的过程,先写一下对应的实体类吧
  1. public class Goods {
  2.    private Integer goodsId;//商品编号
  3.    private String title;//标题
  4.    private String subTitle;//子标题
  5.    private Float originalCost;//原始价格
  6.    private Float currentPrice;//当前价格
  7.    private Float discount;//折扣率
  8.    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
  9.    private Integer categoryId;//分类编号
  10.    private List<GoodsDetail> goodsDetails;
  11. }
  12. //省略getter,setter,构造器等等
复制代码
  1. public class GoodsDetail {
  2.    private Integer gdId;
  3.    private Integer goodsId;
  4.    private String gdPicUrl;
  5.    private Integer gdOrder;
  6.    private Goods goods;
  7. }
  8. //省略getter,setter,构造器等等
复制代码
那么我们要如何实现一对多的查询呢,我们来看看本质是什么,不过就是去Goods里面找GoodsDetail,再去GoodsDetail里面找具体的属性罢了。
所以,还是使用我们的resultMap进行映射就好了,只要将goodsDetails的属性映射到Goods里面,不就相当于Goods拥有了goodsDetails的属性了嘛,可以理解为类似下面这个类。
  1. public class GoodsAfterMapping {
  2.    private Integer goodsId;//商品编号
  3.    private String title;//标题
  4.    private String subTitle;//子标题
  5.    private Float originalCost;//原始价格
  6.    private Float currentPrice;//当前价格
  7.    private Float discount;//折扣率
  8.    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
  9.    private Integer categoryId;//分类编号
  10.        private Integer gdId;
  11.        private Integer goodsId;
  12.        private String gdPicUrl;
  13.        private Integer gdOrder;
  14.        private Goods goods;
  15. }
  16. //省略getter,setter,构造器等等
复制代码
ok,有了思路,来具体实现一下吧,写好查询语句
  1. <mapper namespace="goodsDetail">
  2. <select id="selectOneToMany" resultMap="RmGoods1">
  3.    select * from t_goods limit 0,10
  4. </select>
  5. </mapper>
复制代码
想一想我们的思路,是不是要利用这句select语句查询出来的ID,去我们的我们商品细节表GoodsDetail里把其他属性也查出来,所以我们再写一个select语句用于查询商品细节表GoodsDetail
  1. <mapper namespace="goodsDetail">
  2. <select id="selectByGoodsId" parameterType="Integer"
  3.        resultType="com.imooc.mybatis.entity.GoodsDetail">
  4.    select * from t_goods_detail where goods_id = #{value}
  5. </select>
  6. </mapper>
复制代码
然后开始配置我们的 resultMap 取名为 RmGoods1
  1. <mapper namespace="goodsDetail">
  2. <resultMap id="RmGoods1" type="com.imooc.mybatis.entity.Goods">
  3.    
  4.    <id column="goods_id" property="goodsId"></id>
  5.    
  6.    
  7.    
  8.    <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
  9. </resultMap>
  10. </mapper>
复制代码
为了好看放一下全部配置吧
  1.     查询Goods表        <mapper namespace="goodsDetail">
  2. <select id="selectOneToMany" resultMap="RmGoods1">
  3.    select * from t_goods limit 0,10
  4. </select>
  5. </mapper>        利用上句查询出来的ID去GoodsDetail查询        <mapper namespace="goodsDetail">
  6. <select id="selectByGoodsId" parameterType="Integer"
  7.        resultType="com.imooc.mybatis.entity.GoodsDetail">
  8.    select * from t_goods_detail where goods_id = #{value}
  9. </select>
  10. </mapper>        配置resultMap            
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表