MyBatis技术总结

1.什么是MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2.MyBatis中的主要组成部分

  • SqlSessionFactoryBuilder( 工厂构造器 ) :根据配置文件来生成SqlSessionFactory
    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
    SqlSessionFactoryBuilder创建代码示例:

    SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
    
  • SqlSessionFactory(SqlSession工厂):创建SqlSession。
    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
    SqlSessionFactory创建代码示例:

    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    
  • SqlSession(会话):执行SQL并返回结果。
    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
    SqlSession创建代码示例:

    try (SqlSession session = sqlSessionFactory.openSession()) {
    // 应用逻辑代码
    }
    
  • SQL Mapper:由一个Java接口和XML文件(或注解)构成。接口负责定义数据访问接口,
    XML文件(或注解)负责定义SQL和映射规则。

    映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:
    代码示例:

    try (SqlSession session = sqlSessionFactory.openSession()) {
          BlogMapper mapper = session.getMapper(BlogMapper.class);
         // 你的应用逻辑代码
    }
    

3.使用单例设计模式(懒汉式)设计一个用于生成SqlSession的MyBatis工具类

代码实现:

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisUtils {
    //静态字符串保存主配置文件的路径
    private static final String CONFIG = "mybatis-config.xml";
    //声明静态SqlSessionFactory确保一个程序中只会有一个工厂类
    private static SqlSessionFactory factory;
    //创建同步锁确保线程安全
    private static final Class<MyBatisUtils02> CLASS_LOCK = MyBatisUtils02.class;
    //静态代码块创建工厂类
    static {
        factory = initSqlSessionFactory();
    }
    private MyBatisUtils02() {
    }
    /**
     * 返回创建的工厂
     * @return SqlSessionFactory对象
     */
    private static SqlSessionFactory initSqlSessionFactory() {
        if(factory == null) {
            //加同步锁确保只会创建一个工厂。
            synchronized (CLASS_LOCK) {
                try (InputStream in = Resources.getResourceAsStream(CONFIG)){
                    factory = new SqlSessionFactoryBuilder().build(in);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return factory;
    }
    /**
     * @return 一个个SqlSession对象
     */
    public static SqlSession openSqlSession() {
        if(factory == null) {
            initSqlSessionFactory();
        }
        return factory.openSession();
    }
}

4.MyBatis配置

  • 引入外部的properties文件

主要作用:加载外部的properties文件
引入文件(properties)代码示例:

<properties resource="jdbc.properties"/>

properties文件中的内容:
driver_class = com.mysql.cj.jdbc.Driver
jdbc_url = jdbc:mysql://服务器IP地址/数据库名称?useSSL=false&serverTimezone=UCT
db_username = 用户账号
db_password = 用户密码

  • 设置(settings)

主要作用:MyBatis框架运行规则配置
设置(settings)代码示例:

<settings>
  <!-- 开启日志 -->
  <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • 类型别名(typeAliases)

主要作用:设置别名(为实体类设置别名
类型别名(typeAliases)代码示例:

<typeAliases>
<!-- 使用 typeAlias 将实体类进行单一的重命名-->
      <typeAlias type="com.apesource.entity.Employee"alias="EmployeeMapper" />
  <!-- 设置实体类包,为该package中的每个实体类自动设置别名 -->
  <package name="com.apesource.entity"/>
</typeAliases>
  • 环境配置(environments)

主要作用:配置当前的环境(开发环境,测试环境等等),数据库连接信息。
环境配置(environments)代码示例:

<environments default="development">
  <!-- 环境1:开发环境 -->
  <environment id="development">
      <!-- 使用JDBC的事物管理机制 -->
      <transactionManager type="JDBC" />
      <!-- 启用数据库连接池 -->
      <dataSource type="POOLED">
          <!-- 如果加载了外部的 Properties 文件则可以直接使用这样方式赋值数据库参数  -->
          <!-- 注意value大括号中的内容和 Properties 文件中的键对应-->
          <property name="driver" value="${driver_class}" />
          <property name="url" value="${jdbc_url}" />
          <property name="username" value="${db_username}" />
          <property name="password" value="${db_password}" />
      </dataSource>
  </environment>
</environments>
  • 映射器(mappers)

主要作用:传递映射器的地址。

映射器(mappers)代码示例:

<mappers>
   <!-- 映射XML文件 -->
  <mapper resource="com/apesource/dao/mapper/EmployeeMapper.xml" />
   <!-- 映射接口 -->
  <mapper class="com.apesource.dao.mapper.OrderMapper" />
</mappers>

5.MyBatis XML 映射器

1. 常用节点作用总结

  • select :用SQL语句查询数据库中的数据。
  • update:用SQL语句修改数据库中的数据。
  • delete:用SQL语句删除数据库中的数据。
  • insert:用SQL语句向数据库添加数据。

2.常用属性作用总结

  • namespace属性:mapper节点中对应映射器接口的完全限定名。
  • id 属性:接口中的方法名。
  • resultType 属性:该方法的返回类型。
  • parameterType 属性:该方法的返回参数类型。
  • useGeneratedKeys属性:是否开启主键回填。
  • keyProperty属性:主键回填后保存的位置。

3.常见SQL映射示例

示例1:普通增加

<insert id="insertAnswerRecord" parameterType="AnswerRecord" useGeneratedKeys="true" keyProperty="recordId">
  INSERT INTO answer_record(respondent,question,right_answer,submit_answer,submit_datetime)
  VALUES(#{respondent},#{question},#{rightAnswer},#{submitAnswer},#{submitDatetime})
</insert>
接口方法定义:int insertAnswerRecord(AnswerRecord answerRecord);

示例2:批量增加

<!-- foreach 用于动态SQL的循环遍历,Collection 指定遍历的集合类型,item为每次循环遍历的元素命名 -->
<insert id="insertAnswerRecordBatch" parameterType="list">
  INSERT INTO answer_record(respondent,question,right_answer,submit_answer,submit_datetime)
  VALUES
  <foreach collection="list" item="record" separator=",">
      (
          #{record.respondent},
          #{record.question},
          #{record.rightAnswer},
          #{record.submitAnswer},
          now()
      )
  </foreach>
</insert>
接口方法定义:int insertAnswerRecordBatch(List<AnswerRecord> answerRecordsList);

示例3:普通删除

<delete id="deleteAnswerRecord">
  DELETE FROM answer_record WHERE record_id LIKE #{recordId}
</delete>
接口方法定义:int deleteAnswerRecord(int recordId);

示例4:批量删除

<!-- 批量删除答题记录 -->
<update id="deleteAnswerRecordBatch" parameterType="list">
  DELETE FROM answer_record WHERE record_id IN 
  <foreach collection="list" item="rid" open="(" close=")" separator=",">
      #{rid}
  </foreach>
</update>
接口方法定义:int deleteAnswerRecordBatch(List<Integer> count);

示例5:动态修改

<update id="updateAnswerRecord" parameterType="AnswerRecord">
  UPDATE answer_record
  <set>
      <if test="respondent!=null">respondent = #{respondent},</if>
      <if test="question!=null">question = #{question},</if>
      <if test="rightAnswer!=null">right_answer = #{rightAnswer},</if>
      <if test="submitAnswer!=null">submit_answer = #{submitAnswer},</if>
      submit_datetime = now()
  </set>
  WHERE record_id = #{recordId}
</update>
接口方法定义:int updateAnswerRecord(AnswerRecord answerRecord);

示例6:动态查询

<select id="listAnswerRecordByCondition" resultType="AnswerRecord" parameterType="AnswerRecord">
  SELECT  record_id as recordId,
          respondent as respondent,
          question as question,
          right_answer as rightAnswer,
          submit_answer as submitAnswer,
          submit_datetime as submitDatetime
  FROM answer_record
  <where>
      <if test="respondent!=null">AND respondent = #{respondent}</if>
      <if test="question!=null">AND question LIKE concat('%',#{question},'%')</if>
      <if test="rightAnswer!=null">AND right_answer = #{rightAnswer}</if>
      <if test="submitAnswer!=null">AND submit_answer = #{submitAnswer}</if>
  </where>
</select>
接口方法定义:List<AnswerRecord> listAnswerRecordByCondition(AnswerRecord answerRecord);

示例7:查询结果封装为Map

<select id="countAnswerRecordDataByRespondent" resultType="map">
  SELECT  COUNT(record_id) AS sum,
          (SELECT COUNT(record_id) FROM answer_record 
          WHERE right_answer = submit_answer AND respondent = #{respondent}) AS correct,
          (SELECT COUNT(record_id) FROM answer_record 
          WHERE right_answer != submit_answer AND respondent = #{respondent}) AS total 
  FROM answer_record
  WHERE respondent = #{respondent}
</select>
接口方法定义:Map<String,Integer> countAnswerRecordDataByRespondent(String respondent);

6.MyBatis的缓存机制

  • Mybatis的一级缓存是SqlSession
  • MyBatis的二级缓存是SqlSessionFactory

如何设置二级缓存
主配置文件配置

<!-- MyBatis框架运行规则配置 -->
<settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

mapper映射文件配置

<!--
    eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
        (1)LRU,最近最少使用的,回收最长时间不用的对象 
        (2)FIFO,先进先出,按对象进入缓存的顺序来移除他们
        (3)SOFT,软引用,移除基于垃圾回收器状态和软引用规则对象
        (4)WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。
    这里采用的是LRU,移除最长时间不用的对象
        
    flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新;
                 如果没有配置,当SQL被执行的时候才会去刷新缓存。
                         
    size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。
        设置过大会导致内存溢出。这里配置的是1024个对象
        
    readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,
               缺点是我们没办法修改缓存,他的默认值是false,不允许我们修改。
-->
 <!-- cache节点:配置当前命名空间下的mapper的缓存配置 -->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024" />
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342