MyBatis学习笔记

一、MyBatis简介

MyBatis是一个半自动化的持久层框架。

  • MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。
  • MyBatis避免了几乎所有JDBC代码和手动设置参数以及获取结果集。
  • MyBatis可以使用间的的xml或注解用于配置和原始映射,将接口和java的POJO映射成数据库的记录。

二、MyBatis与JDBC、Hibernate

jdbc:

  • sql夹在java代码块中,耦合度高。
  • 维护不易,sql时常变化,需要平凡修改

Hibernate:

  • 长难复杂的sql,Hibernate也不容易处理;
  • 内部会自动生成sql,不容易做优化;
  • 基于全映射的全自动框架,进行部分映射比较困难,导致数据库性能下降。

Mybatis是一个半自动框架,使得sql与java编码分开,一个专注业务,一个专注数据。

三、MyBatis配置文件

  1. 全局配置文件:mybatis-config.xml

    • 指导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>
    
    <!--
            和Spring的context、property-placeholder相似,都是引用外部配置文件
            resource:从类路径下开始引用
            url:引用磁盘路径或网络路径的资源
    -->
    <!--    <properties url=""></properties>-->
        <properties resource="dbconfig.properties"></properties>
    
    <!--    setting是MyBatis中极为重要的调整设置,其中的属性会改变MyBatis的运行时行为-->
        <settings>
    <!--        name:配置项的key;
                value:配置项的值
                mapUnderscoreToCamelCase:开启驼峰命名自动映射(如果javabean的属性和数据库的字段满足驼峰命名法)
    -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    
    
    <!--    typeAliases(类型别名),为常用的类型起别名-->
        <typeAliases>
    <!--        typeAlias:为一个javaBean起别名,别名默认就是类名(不区分大小写)-->
    <!--        alias:指定一个别名-->
    <!--        <typeAlias type="com.bean.Employee" alias="emp"></typeAlias>-->
    
    <!--       批量起别名 ,默认别名就是类名-->
            <package name="com.bean"/>
    <!--        一般使用全类名,能快速锁定-->
        </typeAliases>
    
    <!--    插件是mybatis的强大功能-->
    
    <!--    -->
    
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driverClassName}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                </dataSource>
            </environment>
        </environments>
    <!--    引入自己编写的每一个接口的实现文件-->
    <!--    写好的sql映射文件需要使用mappers注册进来-->
        <mappers>
    <!--
            resource:在类路径下找sql映射文件
            url:从磁盘或者网络路径引用
            class:直接引用接口的全类名
                    1.可以将xml放在dao接口同目录下,而且文件名和接口名一致
                    2.配合注解使用
    -->
    <!--        <mapper class="com.dao.EmployeeDao"></mapper>-->
    <!--        <mapper url=""></mapper>-->
    <!--        <mapper resource="com/dao/EmployeeDao.xml"/>-->
    <!--        <mapper class="com.dao.EmployeeDaoAnnotation"></mapper>-->
            
    <!--        批量注册-->
            <package name="com.dao"/>
        </mappers>
    </configuration>
    

    注:当使用mappers批量注册时,需要将xxxDao.xml与dao接口放在同一个包下,且名字相同。

    MyBatis学习笔记

  2. SQL映射文件:xxxDao.xml

    • 相当于是对Dao接口的一个实现描述。
    <?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:名称空间,写接口的全类名,相当于告诉mybatis这个配置文件是实现哪个接口的
    
    -->
    <mapper namespace="com.dao.EmployeeDao">
    <!--    用来定义一个查询操作
            id:方法名,相当于这个配置是对于某个方法的实现
            resultType:指定方法运行后的返回值类型(查询操作必须指定的)
    -->
        <select id="getEmpById" resultType="com.bean.Employee">
        select * from t_employee where id = #{id}
      </select>
    
        <select id="getEmpByName" resultType="com.bean.Employee">
            select * from t_employee where empName=#{name}
        </select>
    
    <!--    增删改不用写返回值类型,增删改是返回影响多少行
            MyBatis自动判断,如果是数字(int.long)
            如果是Boolean(影响0行自动封装false,否则true)
            #{属性名}:从传入的参数对象中取出对应属性的值
    -->
    
        <update id="updateEmployee" >
            update t_employee
            set empname=#{empName},gender=#{gender},email=#{email}
            where id=#{id}
        </update>
    
        <insert id="insertEmployee">
            insert into t_employee(empname,gender,email)
            values (#{empName},#{gender},#{email})
        </insert>
    
        <delete id="deleteEmployee">
            delete from t_employee
            where id=#{id}
        </delete>
    </mapper>
    

四、plugins插件

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为、插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

五、注解的使用

MyBatis支持注解,可以直接在dao接口的方法上面标注注解,而不用写配置文件。

示例代码:

public interface EmployeeDaoAnnotation {
    @Select("select * from t_employee where id = #{id}")
    public Employee getEmpById(Integer id);
    @Select("select * from t_employee where empName=#{name}")
    public Employee getEmpByName(String name);

    @Select("update t_employee\n" +
            "        set empname=#{empName},gender=#{gender},email=#{email}\n" +
            "        where id=#{id}")
    public int updateEmployee(Employee employee);

    @Select("delete from t_employee\n" +
            "        where id=#{id}")
    public Boolean deleteEmployee(Integer id);

    @Select("insert into t_employee(empname,gender,email)\n" +
            "        values (#{empName},#{gender},#{email})")
    public int insertEmployee(Employee employee);
}

mybatis全局配置文件中声明一下:

<mappers>
<!--
        resource:在类路径下找sql映射文件
        url:从磁盘或者网络路径引用
        class:直接引用接口的全类名
                1.可以将xml放在dao接口同目录下,而且文件名和接口名一致
                2.配合注解使用
-->
<!--        <mapper class="com.dao.EmployeeDao"></mapper>-->
<!--        <mapper url=""></mapper>-->
        <mapper resource="EmployeeDao.xml"/>
        <mapper class="com.dao.EmployeeDaoAnnotation"></mapper>
    </mappers>

注:重要的dao可以写配置,简单的可以使用注解

六、简单使用

首先引入全局配置文件mybatis-config.xml,然后创建一个SqlSessionFactory,用于生产SqlSession,通过SqlSession获取dao接口的实现,然后就可以操作数据库。

@Test
public void test1() throws IOException {
    //根据全局配置文件创建出一个sqlSessionFactory
    //SqlSessionFactory:是sqlSession工厂,负责创建sqlSession对象
    //sqlSession:sql会话(代表和数据库的一次会话)
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession=null;
    Employee emp=null;
    try {
        //获取和数据库的一次会话:getConnection()
        sqlSession = sqlSessionFactory.openSession();

        //使用sqlSession操作数据库,获取dao接口的实现
        EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
        emp = employeeDao.getEmpById(1);
    } finally {
        sqlSession.close();
    }
    System.out.println(emp);
}

七、参数的各种取值

  1. 单个参数(基本数据类型)

    取值:#{随便写}

  2. 多个参数:

    取值:可以用0,1(参数索引)或者param1,param2…

    原因:只要传入的多个参数,mybatis会自动的将这些参数封装在一个map中,封装时使用的key就是参数的索引和参数的第几个表示。

    Map<String,Object> map=new HashMap<>();
    
    map.put("1",传入的值);
    map.put("2",传入的值);
    
  3. @param:为参数指定key,命名参数;

    可以告诉mybatis,封装参数map的时候使用我们指定的key

  4. 传入pojo

    取值:#{pojo的属性值}

  5. 传入map:将多个要使用的参数封装起来

    取值:#{key}

    EmployeeDao:

    public Employee getEmpByIdAndName(@Param("name") String name, Integer id);
    public Employee getEmployeeByIdAndName(Map<String,Object> map);
    

    EmployeeDao.xml:

    <select id="getEmpByIdAndName" resultType="com.bean.Employee" >
        select * from t_employee where empName=#{name} and id=${1}
    </select>

    <select id="getEmployeeByIdAndName" resultType="com.bean.Employee" >
        select * from t_employee where empName=#{name} and id=${id}
    </select>

Test:

import com.bean.Employee;
import com.dao.EmployeeDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class MyBatisTest {
    SqlSessionFactory sqlSessionFactory;
    @Before
    public void getSqlSessionFactory() throws IOException {
        String resource="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    
    @Test
    public void test4(){
        SqlSession sqlSession=null;
        try {
            sqlSession = sqlSessionFactory.openSession(true);
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee emp = employeeDao.getEmpByIdAndName("admin", 1);
            System.out.println(emp);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void test5(){
        SqlSession sqlSession=null;
        try {
            sqlSession = sqlSessionFactory.openSession(true);
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Map<String,Object> map=new HashMap<>();
            map.put("id","1");
            map.put("name","admin");
            Employee employee = employeeDao.getEmployeeByIdAndName(map);
            System.out.println(employee);
        } finally {
            sqlSession.close();
        }
    }
}

八、#{}与${}的区别

在mybatis中,两种取值方式:

  • #{属性名}:是参数预编译的方式,参数的位置都是用”?“替代,参数后来都是预编译社会设置进去的;安全,不会有sql注入问题,推荐使用
  • ${属性名}:不是参数预编译,而是直接和sql语句拼串;不安全

sql语句只有参数位置时支持预编译的。

一般都是使用#{},安全;在不支持参数预编译的位置要进行取值就用${},比如表名。

九、联合查询与分布查询

我们可以使用association与collection标签进行复杂对象的映射(对象的属性包含有对象或集合)。

<!--自定义封装规则,使用级联属性封装联合查询出的结果-->
    <resultMap id="myResult" type="com.bean.Key">
        <id property="id" column="id"></id>
        <result property="keyName" column="key_name"></result>
        <result property="lock.id" column="lid"></result>
        <result property="lock.LockName" column="lock_name"></result>
    </resultMap>

使用association标签:

<!--    使用mybatis推荐的association标签-->
    <resultMap id="myResult" type="com.bean.Key">
        <id property="id" column="id"></id>
        <result property="keyName" column="key_name"></result>
<!--        javaType:指定这个属性的类型-->
        <association property="lock" javaType="com.bean.Lock">
            <id property="id" column="lid"></id>
            <id property="LockName" column="lock_name"></id>
        </association>
    </resultMap>

字段丢失的解决办法:不要直接用select * ,而是把每个字段都写出来,并起别名与javaBean的属性名相对应。

十、动态sql

if标签

where标签与if标签配合,可以动态查询。

where标签可以帮我们去除sql语句中多余的and,但是and要写在每一个sql语句的前面。

if标签中的test写判断语句,有些符号要使用转义字符。

<select id="getStudentByCondition" resultMap="studentMap">
    select * from student
    <where>
        <if test="id!=null and !id.equals(&quot;&quot;)">
            id>#{id}
        </if>
        <if test="name!=null">
           and student_name like #{name}
        </if>
        <if test="english!=null and english>=0 and english &lt;=100">
            and english &lt; #{english};
        </if>
    </where>
</select>

forEach标签

<!--
        collection:指定要遍历的集合的key
        open:以什么开始
        close:以什么结束
        separator:以什么作为分隔符
        item:给每次遍历出来的元素起一个变量名
-->
    <select id="getStudentByCollection" resultMap="studentMap">

        select * from student where id in
        <foreach collection="ids" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </select>

choose标签

<!--    choose标签,当进入任何一个一个when标签的时候,就不会进入其它标签了-->
    <select id="getStudentByConditionChoose" resultMap="studentMap">
        select * from student
        <where>
            <choose>
                <when test="id!=null and !id.equals(&quot;&quot;)">
                    id=#{id}
                </when>
                <when test="name!=null and !name.equals(&quot;&quot;)">
                    student_name like #{name}
                </when>
                <when test="chinese">
                    chinese>#{chinese}
                </when>
                <otherwise>
                    1=1
                </otherwise>
            </choose>
        </where>
    </select>

set标签

<!--    测试动态更新,set标签可以去掉多余的,-->
    <update id="updateStudent">
        update student
        <set>
            <if test="name!=null and !name.equals(&quot;&quot;)">
                student_name = #{name},
            </if>
            <if test="chinese!=null and chinese>=0 and chinese &lt;=100">
                chinese = #{chinese},
            </if>
            <if test="math!=null and math>=0 and math &lt;=100">
                math = #{math},
            </if>
            <if test="english!=null and english>=0 and english &lt;=100">
                english = #{english}
            </if>
        </set>
        <where>
            id=#{id}
        </where>
    </update>

十一、缓存机制

MyBatis缓存简介

MaBatis缓存机制:Map,能保存查询出的一些数据。

  • 一级缓存:线程级别的缓存,本地缓存,sqlSession级别的缓存,不同的sqlSession使用不同的一级缓存。
  • 二级缓存:全局范围的缓存,除过当前线程、sqlSession能用外其他也可以调用。

机制:只要之前查询过数据,mybatis就会保存在一个缓存中(Map),下次获取直接从缓存中拿,但是如果在这之间执行了其它增删改操作,mybatis就会清空一级缓存。

一级缓存失效的情况

  1. 不同的sqlSession使用不同的一级缓存。
  2. 同一个方法,不同的参数,可能之前没查询过,会重新发sql
  3. 在这个sqlSession期间执行任何一次增删改操作,此sqlSession的一级缓存就会被清空。
  4. 手动清空缓存。

二级缓存

二级缓存:全局缓存,namespace级别的缓存。

sqlSession关闭或提交以后,一级缓存会放在二级缓存中,mybatis默认是没有使用的。

二级缓存的使用:

  1. 全局配置开启二级缓存

    <!--        开启全局缓存开关-->
            <setting name="cacheEnabled" value="true"></setting>
    
  2. 配置某个dao.xml文件,让其使用二级缓存

    <!--    使用二级缓存-->
        <cache></cache>
    
  3. 让 对应的javaBean实现序列化,即实现Serializable接口。

缓存的查询数据

  1. 不会出现一级缓存和二级缓存中有同一个数据
    • 一级缓存关闭就会有二级缓存
    • 查一个数据,二级缓存中没有此数据,就会看一级缓存,一级缓存没有就会查数据库,数据库查询后的结果放在一级缓存中。
  2. 任何时候都是先看二级缓存,再看一级缓存,如果都没有,就会去数据库查询。

MyBatis学习笔记

缓存有关的设置

MyBatis学习笔记

十二、整合第三方缓存

整合Ehcache

  1. 导包

    ehcache-core-2.6.8.jar
    log4j-1.2.17.jar
    mybatis-3.4.1.jar
    mybatis-ehcache-1.0.3.jar
    mysql-connector-java-8.0.24.jar
    slf4j-api-1.6.1.jar
    slf4j-log4j12-1.6.2.jar

  2. ehcache得有一个配置文件,文件名叫ehcache.xml,放在类路径的根目录下

  3. 在mapper.xml中配置使用自定义缓存

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
    
  4. 如果别的dao还要用这个缓存

    <cache-ref namespace="com.dao.StudentDao"/>
    
上一篇:MyBatis-底层源码解析-(详细),Java开发三年月薪才12K


下一篇:mybatis 第一个实例