MyBatis

1. 简介

ROM框架发展:JDBC -> DbUtils(QueryRunner) -> JdbcTemplate -> Hibernate -> MyBatis

前三个只能称为简单的工具,sql语句都写在java源代码中,硬编码高耦合。

Hibernate : 全自动全映射ORM(Object Relation Mapping)框架,全部都封装好了,亦在消除sql

缺点:sql语句不能定制优化,由框架自动编写(学习HQL可以自己编写,但学习成本高)。查询一个字段把所有字段都映射了,性能不好。

MyBatis:半自动轻量级框架,把sql编写,放到配置文件中,其他操作框架封装好,sql与java编码分离。sql语句交给程序员编写,不失去灵活性。


2. 第一个程序

  1. 创建数据库对应的Bean实体类 Emp.java

    @Data
    public class Emp implements Serializable {
        private Integer id;
        private String lastName;   // 注意这个和数据库字段名不一样
        private String gender;     // 也可用char
        private String email;
    }
    
  2. 创建全局MyBatis配置文件,数据源环境信息,事务管理器信息…(类路径下) 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>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="9527"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 将写好的sql映射文件一定要注册到全局配置文件中!!!-->
        <mappers>
            <mapper resource="empMapper.xml"/>  <!-- 类路径下可以直接写-->
        </mappers>
    </configuration>
    
  3. 创建sql映射xml文件 ,配置每一个sql和结果的封装规则(类路径下)empMapper.xml

    <mapper namespace="com.sutong.dao.EmpMapping">
        <!-- namespace命名空间,
    		 id是sql的唯一标识,resultType返回值类型,
    		 中间是sql语句,数据库字段名和Bean不一样需要取别名,
    		 #{id}从传递过来的参数中取出id值 -->
        <select id="selectEmp" resultType="com.sutong.bean.Emp">
        	select `id`, `last_name` as lastName, `gender`, `email` from t_emp where id = #{id}
      	</select>
    </mapper>
    
  4. 获取执行sql的对象,去执行sql语句( SqlSession实例 - 这一个对象就代表和数据库的一次会话,用完关闭,和Connection一样都是线程不安全的,每次使用都应该去获取新的对象)

    public class HelloMyBatis01 {
        @Test
        public void test01() throws IOException {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            try {
                // 第一个参数是sql语句的唯一标识符!!!
                // (就是select标签里面的id,但多个文件id可能冲突,所以需要加上命名空间),第二个的执行sql需要的参数
                Emp emp = sqlSession.selectOne("com.sutong.dao.EmpMapping.selectEmp", 1);
                System.out.println(emp); 
            } finally {
                sqlSession.close(); // 始终都要关闭
            }
        }
    }
    
  5. 接口式编程,我们Dao层写的接口可以直接和MyBatis映射文件绑定!MyBatis自动为接口创建代理对象,去执行增删改查

    • namespace必须指定为接口的全类名
    • select标签的id属性是要绑定接口方法的名
    public interface EmpMapping {
        Emp getEmpById(Integer id);
    }
    
    <mapper namespace="com.sutong.dao.EmpMapping">
        <!-- namespace命名空间,接口的全类名!!!select标签的id需是接口方法的名称!!绑定 -->
        <select id="getEmpById" resultType="com.sutong.bean.Emp">
        	select `id`, `last_name` as lastName, `gender`, `email` from t_emp where id = #{id}
      	</select>
    </mapper>
    

    测试:

    //先获取SqlSession实例
    try {
        EmpMapping mapper = sqlSession.getMapper(EmpMapping.class); // 获取接口实现类,
        Emp emp = mapper.getEmpById(1);      // 实际调用接口代理的方法,就不用写上面一大堆sql标识了,还有类型检查!
        System.out.println(emp);
    } finally {
        sqlSession.close();
    }
    

3. 全局配置文件

  • configuration(配置)

    • properties(属性)

    • settings(设置)

    • typeAliases(类型别名)

    • typeHandlers(类型处理器)

    • objectFactory(对象工厂)

    • plugins(插件)

    • environments(环境配置)

      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)

    • mappers(映射器)

注意:配置的时候要按照上面这个顺序配置!!


3.1 引入DTD约束

MyBatis的xml配置文件约束(语法),(IDEA自带)

<!-- 全局配置文件-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!-- sql映射文件-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

3.2 properties标签

类路径下创建 dbconfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=9527

使用:

<!-- 因为外部properties配置文件内容,resource属性引入类路径下资源,url引入网络下或磁盘下-->
<properties resource="dbconfig.properties"/> 

<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

3.3 settings标签 ☢

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。里面很多设置。!!

  1. mapUnderscoreToCamelCase :是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。这样就可以不用查询的时候在sql语句中用as取别名了。默认值是false
  2. jdbcTypeForNull 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。默认是OTHER
  3. lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。默认是fasle
  4. aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。默认false(3.4.1前默认是true)
  5. cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认true
  6. localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
  7. …等多设置看MyBatis官网 ,官网:配置

原则:即使的默认的,我们确认开启的设置都要显示的配置出来!

<settings>
    <!-- name设置项,value设置项取值-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.4 typeAliases标签 ☢

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写

<!-- 别名处理器,给java类名取别名(别名不区分大小写)!!-->
<typeAliases>
    <!-- type是要取别名的全类名,默认名就是类名小写!!也可以alias属性指定别名-->
    <typeAlias type="com.sutong.bean.Emp"/>
    
    <!-- 批量区别名,指定包名- 当前包和后代包都起个默认别名-->
    <package name="com.bean"/>
</typeAliases>

使用:

<select id="getEmpById" resultType="emp">
    select * from t_emp where id = #{id}
</select>

批量区别名问题:当多包下有相同的类时,都取默认别名,则MyBatis就会报错,这时可以在重名类上面使用@Alias注解,给个别类单独起个别名 @Alias("emp01"),就不冲突了。

MyBatis为我们起好了一些别名:

  • 别名:_int <–> 映射的类型:int (基本类型就是前面加下划线)
  • integer <–> Integer (基本类型的包装类都是首字母小写)
  • string <–> String
    在这里插入图片描述

3.5 typeHandlers标签

类型处理器:在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都用类型处理器将获取到的值以合适的方式转换成 Java 类型。

Mybatis 3.4.5后添加Java8的新日期API支持

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjNJPdD2-1662964083312)(MyBatis.assets/image-20220123122933733.png)]
​ …


3.6 plugins标签 ☢

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

后面讲!!


3.7 environments标签

将 SQL 映射应用于多种数据库之中。记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<!-- 环境配置,default指定使用那个环境!!-->
<environments default="development">
    <!-- 每一个environment表示一个具体的环境信息,里面必须有transactionManager,dataSource标签-->
    <environment id="development">
        <!-- 事务管理器,type两种类型JDBC|MANAGED(两个别名),
             可以自定义,实现TransactionFactory接口,type就是自定义事务管理器的全类名-->
        <transactionManager type="JDBC"/>
        <!-- 数据源,type三种UNPOOLED|POOLED|JNDI(也是别名),POOLED是使用连接池,MyBatis自带的
             可以自定义实现DataSourceFactory接口,type就是自定义数据源的全类名-->
        <dataSource type="POOLED"> 
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>

    <environment id="test">
        <transactionManager type="..."/>
        <dataSource type="..."></dataSource>
    </environment>
</environments>

3.8 databaseIdProvider标签

可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。

<!-- 支持多数据库厂商,type是DB_VENDOR固定的,是个别名
	 作用得到数据库厂商的标识(驱动里有方法能获取),mybatis根据标识来执行不同的sql-->
<databaseIdProvider type="DB_VENDOR">
    <!-- 为不同的数据库厂商起别名-->
    <property name="MySQL" value="mysql"/>
    <property name="Oracle" value="oracle" />
    <property name="SQL Server" value="sqlserver"/>
    <property name="DB2" value="db2"/>
</databaseIdProvider>

在sql映射文件select标签上加上databaseId属性,值为上面的别名

<!-- 则在MySQL环境下才发送(环境MyBatis自动识别,只需要上面写上支持导入依赖就行)-->
<select id="getEmpById" resultType="emp" databaseId="mysql">
	select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
</select>

<!-- 则在Oracle环境下才发送-->
<select id="getEmpById" resultType="emp" databaseId="oracle">
	select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
</select>

3.9 mappers标签 ☢

1.引用配置文件

将sql映射注册到全局配置中。

<mappers>
    <!-- resource引用类路径下的资源,url引用网络路径下的/或者磁盘下的,这两种都是引用配置文件-->
    <mapper resource="empMapper.xml"/> 
</mappers>

2.引用(注册)接口

mapper 还有一个属性class :引用(注册)接口

<mappers>
    <mapper class="com.sutong.dao.EmpMapping"/>
</mappers>
  • 如果有sql映射文件,映射文件名必须和接口同名,并且放在一个目录下

    IDEA中不会把java包下的.java以外的文件放到classes里面。可以在resources目录下,创建一个和java包下与dao层同名的包resources.com.sutong.dao.EmpMapper.xml,编译后会自动合并的

    // 接口main.java.com.sutong.dao.EmpMapping.java
    public interface EmpMapping {
        Emp getEmpById(Integer id);
    }
    
    <!-- sql映射文件:main.resources.com.sutong.dao.EmpMapper.xml-->
    <mapper namespace="com.sutong.dao.EmpMapping">
        <select id="getEmpById" resultType="emp" databaseId="mysql">
            select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
        </select>
    </mapper>
    
  • 如果没有映射文件,所有的sql语句都是利用注解写在接口上(这种还不如xml,如果改还要动源码,而且sql长了就麻烦了)

    public interface EmpMapping {
        // Update,Delete等注解都有...
        @Select("select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}")
        Emp getEmpById(Integer id);
    }
    

3.批量注册

<mappers>
    <!-- 使用注解好理解,不用说,
		 使用配置文件则需要把sql映射文件放到dao包下,即在Idea中这种创建resources.com.sutong.dao--> 
    <package name="com.sutong.dao"/>
</mappers>

总结:比较重要的复杂的Dao接口来写sql映射文件,不重要的简单的Dao接口为了开发快速可以使用注解!


4. 映射文件

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

4.1 增删改查

接口:

public interface EmpMapper {
    Emp getEmpById(Integer id);

    void addEmp(Emp emp);

    void updateEmp(Emp emp);

    void deleteEmp(Integer id);
}

映射文件:

<mapper namespace="com.sutong.dao.EmpMapper">
    <select id="getEmpById" resultType="com.sutong.bean.Emp" databaseId="mysql">
        select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
    </select>
    
    <!-- parameterType指定参数类型,可以省略的。#{lastName}就是直接取出对象中lastName属性的值-->
    <insert id="addEmp" parameterType="com.sutong.bean.Emp">
        insert t_emp(`last_name`, `gender`, `email`) values(#{lastName}, #{gender}, #{email})
    </insert>

    <!-- 参数类型这里省略了-->
    <update id="updateEmp">
        update t_emp
        set `last_name` = #{lastName}, `gender` = #{gender}, `email` = #{email}
        where `id` = #{id}
    </update>

    <delete id="deleteEmp">
        delete from t_emp where `id` = #{id}
    </delete>
</mapper>

测试:

// 先获取sqlSessionFactory...

// openSession()方法可以传个boolean值,代表是否自动提交,默认不自动提交!!
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // 获取接口实现类
    Emp emp = mapper.getEmpById(1); // 查
    mapper.addEmp(new Emp(null, "sutong", "1", "sotong@qq.com")); // 增
    mapper.updateEmp(new Emp(1, "Tom", "1", "Tom123@qq.com")); // 改
    mapper.deleteEmp(4); // 删

    sqlSession.commit(); // 手动提交!!
} finally {
    sqlSession.close();
}

Mybati允许增删改直接定义 Integer,Long,Boolean,void 类型的返回值,会帮我们自动封装好!!

例如:Long addEmp(Emp emp); 映射文件中不用写关于返回值的配置!


4.2 insert获取自增主键值

<!-- mysql支持自增主键,可以获取插入这条数据的自增后的主键值(底层是利用Statement对象里面的getGeneratedKeys()方法)
     useGeneratedKeys="true"使用自增主键获取主键策略!!
     keyProperty="id"指定获取到主键信息后,将这个值封装给JavaBean的哪个属性!!
     -->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
    insert t_emp(`last_name`, `gender`, `email`) values(#{lastName}, #{gender}, #{email})
</insert>

测试:

Emp e = new Emp(null, "haha", "0", "haha@qq.com");
mapper.addEmp(e); // 增
System.out.println(e.getId()); // id就有值了

Oracle不支持自增,Oracle使用序列来模拟自增,insert标签里面需要使用一个selectKey标签…


4.3 参数处理 ☢

1.单个参数,MyBatis不会做特殊处理

#{参数名} 取出(参数名可以随便写,因为就一个参数!!)

当一个参数是Collection类型(List,Set)或者是数组类型就会特殊处理了

Collection类型的key:collection #{collection[0]} ,使用#{param1}不行

List类型的key:list #{list[0]}

数组类型:array #{array[0]}

单个参数加了@Param注解也会特殊处理

2.多个参数,特殊处理。

// 接口方法
Emp getEmpByIdAndLastName(Integer id, String lastName);

<select id="getEmpByIdAndLastName" resultType="emp">
    select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id} and `last_name` = #{lastName}
</select>

// 测试
Emp emp = mapper.getEmpByIdAndLastName(1, "Tom"); // 报错

上面这样会报错!因为多个参数会被封装为一个Map,规则:key就是param1...paramN(或者参数的索引也可以),value就是传入的参数值

<select id="getEmpByIdAndLastName" resultType="emp">
    select `id`, `last_name`, `gender`, `email` 
    from t_emp where id = #{param1} and `last_name` = #{param2}
</select>

3.命名参数:明确封装时Map的key,接口方法参数使用注解@Param

Emp getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
<select id="getEmpByIdAndLastName" resultType="emp">
    select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id} and `last_name` = #{lastName}
</select>
<!-- 这样使用就不会报错的,使用param1..也行-->

4.如果多个参数正好是业务逻辑的数据模型,直接传入pojo就行了

#{属性名} 就可以取出pojo的属性值

5.如果不是业务路径数据模型,我们可以直接传入Map

接口方法:Emp getEmpByMap(Map<String, Object> map);

#{key}直接取map中的value就行

6.不是业务路径数据模型,而且经常使用,推荐编写一个TO(Transfer Object)数据传输对象,例如Page对象等


4.4 参数值的获取

两者效果一样的,但有一点区别

  1. #{} 是以预编译的形式,将参数设置到sql语句中,PreparedStatement?占位符,可以防止SQL注入
  2. ${} 取出是值直接拼装在sql语句中,Statement,会有安全问题

大多数情况下我们取参数值都应该取使用#{}。原生Jdbc不支持占位符的地方,需要sql拼接是时候可以使用$(),

例如分表拆分 select * from ${year}_salary where xxx,排序select * from t_user oder by ${f_name} ${order}等。

#{} 的丰富用法:

规定参数的一些规则:

javaType,jdbcType,mode(存储过程),numericScale

resultMap,typeHandler,jdbcTypeName,expression(未来准备支持的)

jdbcType(数据库类型)通常在某种情况下需要被设置,我们在数据为null的时候有些数据库可能不能识别mybatis对null的默认处理,例如Oracle(报错),jdbcType OTHER无效的类型。

因为mybatis对所有的null都默认映射为原生的jdbc OTHER 类型,Oracle不能正确。

<insert id="addEmp" parameterType="com.sutong.bean.Emp">
    insert t_emp(`last_name`, `gender`, `email`) 
    values(#{lastName}, #{gender}, #{email, jdbcType=NULL})    
</insert>

或者改全局设置<setting name="jdbcTypeForNull" value="NULL"/> 这个值默认是OTHER


4.5 select返回集合 ☢

返回List, Map类型!!

public interface EmpMapper {
    
    List<Emp> getEmpsByLastNameLike(String lastName);
    
    // 返回一条记录的Map,key是列名,value就是该字段对应的值
    // 例如:{gender=1, last_name=Tom, id=1, email=Tom123@qq.com}
    Map<String, Object> getEmpByIdReturnMap(Integer id);
    
    // 多条记录封装为一个Map,key是这条记录的主键(下面注解可指定),value是该记录封装后的JavaBean
    // 例如:{2=Emp{id=2, lastName='Jack', gender='1', email='jack@163.com'}, 
    // 			5=Emp{id=5, lastName='haha', gender='0', email='haha@qq.com'}}
    @MapKey("id")  // 告诉mybatisMap的封装这个map的时候那个属性作为map的key
    Map<Integer, Emp> getEmpsByLastNameReturnMap(String lastName);
}
<mapper namespace="com.sutong.dao.EmpMapper">
    <!-- 如果返回的是一个集合,要写集合中元素的类型!!!-->
    <select id="getEmpsByLastNameLike" resultType="com.sutong.bean.Emp">
        select * from t_emp where `last_name` like #{lastNamr}
    </select>

    <!-- 一条记录返回Map类型,resultType就需要写map了(map是个别名,mybatis起好了)-->
    <select id="getEmpByIdReturnMap" resultType="map">
        select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
    </select>
    
    <!-- resultType还是map集合value元素的类型-->
    <select id="getEmpsByLastNameReturnMap" resultType="com.sutong.bean.Emp" >
        select * from t_emp where `last_name` like #{lastName}
    </select>
</mapper>    

4.6 自定义映射规则 ☢

resultMap 可以自定义映射规则(1.as起别名 2.设置setting 3.使用resultMap)

public interface EmpMapperPlus {
    Emp getEmpById(Integer id);
}

映射文件:

<mapper namespace="com.sutong.dao.EmpMapperPlus">

    <!-- id是规则标识方便引用,type是自定义规则的Java类型-->
    <resultMap id="MyEmp" type="com.sutong.bean.Emp">
        <!-- id标签是指定主键映射,column是数据库的那一列,property是JavaBean的哪一个属性-->
        <id column="id" property="id"/>
        <!-- result标签定义普通列-->
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!-- 如果不指定其他列,自动对应封装(推荐写了resultMap就把所有的映射规则都写上)-->
    </resultMap>

    <!-- resultMap自定义结果映射规则-->
    <select id="getEmpById" resultMap="MyEmp">
        select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
    </select>
</mapper>

关联查询⭐

resultMap 更强大的功能:关联查询

例如:查询每个员工Emp对应的部门Dept信息:

public class Dept {
    private Integer id;
    private String deptName;
}

public class Emp {
    private Integer id;
    private String lastName;  
    private String gender;   
    private String email;
    private Dept dept;
}

// Dao接口方法
public interface EmpMapperPlus {
    Emp getEmpAndDept(Integer id);
}

映射文件:

<mapper namespace="com.sutong.dao.EmpMapperPlus">
    
	<!-- id  last_name  gender  email  d_id  dept_name 后两个列属于Dept对象, -->
    <resultMap id="MyDifEmp" type="com.sutong.bean.Emp">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <result column="d_id" property="dept.id"/>  <!-- 注意:使用级联属性封装,即属性.属性-->
        <result column="dept_name" property="dept.deptName"/>
        
        
        <!--映射规则的后两列还可以使用association标签:(相当于嵌套了) !!!!
			指定联合的JavaBean对象,property指定那个属性是联合的对象,javaType指定这个属性的类型(不能省略)-->
        <association property="dept" javaType="com.sutong.bean.Dept">
            <!--这个property="id"和最上面那个不一样,这个是在Dept里面的id属性-->
            <id column="d_id" property="id"/> 
            <result column="dept_name" property="deptName"/>
        </association>
    </resultMap>
    
    
    <select id="getEmpAndDept" resultMap="MyDifEmp">
        select 
        	e.id, e.last_name, e.gender, e.email, e.d_id, d.dept_name
        from  
        	t_emp e
        join 
        	t_dept d
        on 
        	e.d_id = d.id
        where 
        	e.id = #{id}
    </select>
</mapper>

分步查询⭐

association标签还能分步查询:类似子查询!!组合已有的方法完成复杂功能

  1. 先根据员工id查询员工信息
  2. 根据员工信息中的d_id值取部门表查出部门信息
  3. 部门信息设置到员工信息中
<mapper>
	<!-- id, last_name, gender, email, d_id -->
    <resultMap id="MyEmpStep" type="com.sutong.bean.Emp">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>

        <!-- 表示dept属性是根据select指定的方法查询得到的,column指定将那一列的值传给指定的方法
             流程:使用select指定的方法查出对象封装给property指定的属性!!-->
        <association property="dept" select="com.sutong.dao.DeptMapper.getDeptById" column="d_id"/>
    </resultMap>

    <select id="getEmpByIdStep" resultMap="MyEmpStep">
    	select `id`, `last_name`, `gender`, `email`, `d_id` from t_emp where id = #{id}
    </select>
</mapper>

前提是必须Dept的Dao层得有getDeptById这个方法和对应的映射文件,这个在实际中基本都有的!!

在分步查询过程中,如果需要多列值传入过去,怎么办呢?则需要将多列的值封装到Map中传递,

column="{key1=column1, key2=column2}" (= 换成:也行)


延迟加载⭐

分布查询还能实现 延迟加载/按需加载/懒加载!!

每次查询Emp对象的时候,Dept都跟着一起查出来了

延迟加载:Dept信息在我们使用的时候再去查询!!只需要在分步查询的基础上加上两个配置就能完成

全局配置:

<settings>
    <setting name="lazyLoadingEnabled" value="true"/> 
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

collection标签

collection标签定义关联集合封装规则!!!

例如:查询部门的时候把部门下的所有员工都查出来!!

public class Dept {
    private Integer id;
    private String deptName;
    private List<Emp> emps;
}

// 接口方法:
public interface DeptMapper {
    Dept getDeptByIdPlus(Integer id);
    
    Dept getDeptByIdStep(Integer id);
}

1.关联查询的映射文件(嵌套结果集的方式):

<mapper>
    <!-- d_id  dept_name  |  e_id  last_name  gender  email-->
    <resultMap id="MyDept" type="com.sutong.bean.Dept">
        <id column="d_id" property="id"/>
        <result column="dept_name" property="deptName"/>
        <!-- collection定义关联集合类型封装规则!ofType指定集合里面元素的类型-->
        <collection property="emps" ofType="com.sutong.bean.Emp">
            <!-- 定义这个集合中元素的封装规则-->
            <id column="e_id" property="id"/>
            <result column="last_name" property="lastName"/>
            <result column="gender" property="gender"/>
            <result column="email" property="email"/>
        </collection>
    </resultMap>

    <select id="getDeptByIdPlus" resultMap="MyDept">
        select
        	d.id as d_id, d.dept_name, e.id as e_id, e.last_name, e.gender, e.email
        from
        	t_dept d
        left join
        	t_emp e
        on
        	d.id = e.d_id
        where
        	d.id = #{id}
    </select>
</mapper>

2.分布查询的映射文件:(只要上面那两个设置没关闭是有延迟查询的)

  • 先在EmpMapperPlus.xml里面定义一个使用d_id查询员工返回List<Emp> 的方法

    <select id="getEmpsByDeptId" resultType="com.sutong.bean.Emp">
        select `id`, `last_name`, `gender`, `email`, `d_id` from t_emp where d_id = #{deptId}
    </select>
    
  • 封装到Dept的List集合中

    <mapper>
        <!-- id dept_name -->
        <resultMap id="MyDeptStep" type="com.sutong.bean.Dept">
            <id column="id" property="id"/>
            <result column="dept_name" property="deptName"/>
            <!-- collection里面也有select属性和colume属性,和上面一样的意思!!-->
            <collection property="emps" 
                        select="com.sutong.dao.EmpMapperPlus.getEmpsByDeptId" 
                        column="id"/>
        </resultMap>
    
        <select id="getDeptByIdStep" resultMap="MyDeptStep">
            select id, dept_name from t_dept where id = #{id}
        </select>
    </mapper>
    

collectionassociation标签里还有个属性fetchType="lazy" 表示延迟加载 ,如果这条查询不需要延迟则可设置为eager


discriminator标签

discriminator:鉴别器。可以根据鉴别器根据某列的值改变封装行为。(了解即可)

例如:查Emp,如果查出的是女生就把部分信息查询出来,否则不查询。

下面利用分步查询的基础上加了个鉴别器!!

<resultMap id="MyEmpDis" type="com.sutong.bean.Emp">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="email" property="email"/>

    <!-- 鉴别器,column指定判定的列名,javaType是列值对应的java类型-->
    <discriminator javaType="string" column="gender">
        <!-- 女生,resultType指定封装的类型(不能省略)-->
        <case value="0" resultType="com.sutong.bean.Emp">
            <association property="dept" select="com.sutong.dao.DeptMapper.getDeptById" column="d_id"/>
        </case>
        <!-- 男生,不查Dept信息-->
        <case value="1" resultType="com.sutong.bean.Emp"/> 
    </discriminator>
</resultMap>

5. 动态SQL

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

5.1 if/where

例如:查询员工,要求如果携带了id字段和lastName字段则按照这两个查询,两者携带那一个就按哪一个查询。

if其实就是根据条件拼接SQL语句!

<mapper namespace="com.sutong.dao.EmpMapperDynamicSQL">
    <select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
        select `id`, `last_name`, `gender`, `email` from t_emp
        where 1 = 1 
        <!-- test:判断表达式(OGNL表达式,使用和EL类似),表达式里面的值的是从参数里面取的,
			 遇见特殊字符应该使用转义字符。-->
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="lastName != null and lastName != ''">
            and last_name = #{lastName}
        </if>
    </select>
</mapper>

id不传可能会导致语SQL法错误,解决方案:

1.where后面写个1=1,后面所有条件都加上and(看上面)

2.使用where标签将所有的查询条件包括(MyBatis推荐),MyBatis会帮我们第一个多出来的and或者or

<select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
    select `id`, `last_name`, `gender`, `email` from t_emp
    <where>
        <if test="id != null">  
            id = #{id}
        </if>
        <if test="lastName != null and lastName != ''">
            and last_name = #{lastName}
        </if>
    </where>
</select>

还能这样写:(OGNL表达式甚至还能调用静态方法使用@,可看官方文档)

<if test="email != null and email.trim() != ''">
   	and email = #{email}
</if>

/* gender是字符串可以直接和数组比较,会自动转换*/
<if test="gender == 0 or gender == 1">
   	and gender = #{gender}
</if>

5.2 trim

可以用trim标签解决后面多出的and或者or!!(了解)

<select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
    select `id`, `last_name`, `gender`, `email` from t_emp
    <!-- trim标签体里的整个拼接后的字符串,
			 prefix加前缀,prefixOverrides前缀覆盖,去掉整个字符串多余的字符,
             suffix加后缀,suffixOverrides去掉字符串后面多余的字符 -->
    <trim prefix="where" suffixOverrides="and">
        <if test="id != null">
            id = #{id} and
        </if>
        <if test="lastName != null and lastName != ''">
            last_name = #{lastName}
        </if>
    </trim>
</select>

5.3 choose

类似Java中的swtich-case,只会选一个!!

<select id="..." resultType="com.sutong.bean.Emp">
    select `id`, `last_name`, `gender`, `email` from t_emp
    <where>
        <choose>
            <when test="id != null">
                id = #{id}
            </when>
            <when test="lastName != null">
                last_name = #{lastName}
            </when>
            <otherwise>  <!-- 上面都不满足则查所有-->
                1 = 1
            </otherwise>
        </choose>
    </where>
</select>

5.4 set

带那一列的值就更新那一列(当然这个set也能用trim标签替换)

<update id="updateEmp">
    update t_emp
    <!-- 不用担心多出来的逗号了-->
    <set>
        <if test="lastName != null">
            last_name = #{lastName},
        </if>
        <if test="gender == 0 or gender = 1">
            gender = #{gender},
        </if>
        <if test="email != null">
            email = #{email}
        </if>
    </set>
    where id = #{id}
</update>

5.5 foreach

例如:查询指定id的员工,可多个!

public interface EmpMapperDynamicSQL {
    // 批量查询
    List<Emp> getEmpsByConditionForEach(@Param("ids") List<Integer> ids);
    
    // 批量插入
    void addEmps(@Param("emps") List<Emp> emps);
}

批量查询:

<select id="getEmpsByConditionForEach" resultType="com.sutong.bean.Emp">
    select `id`, `last_name`, `gender`, `email` from t_emp where `id` in
    <!-- collection是要遍历的集合,如果是List类型参数会特殊处理Key默认是list,我们加了注解可以用注解写的名字!!
         item是当前遍历的元素,separator表示每个元素之间的分隔符,
         open是遍历的结果拼接一个开始的字符,close结束字符,
         index是遍历List类型是元素的索引,Map类型表示map的Key,item就是map的Value -->
    <foreach collection="ids" item="item_id" separator="," open="(" close=")">
        #{item_id}
    </foreach>
</select>

批量保存:

  1. 第一种(推荐)

    <!-- MySQL下,支持values(),(),()语法 -->
    <insert id="addEmps">
        insert into t_emp(`last_name`, `gender`, `email`, `d_id`) values
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
        </foreach>
    </insert>
    
  2. 第二种(这种还可以用于其他的批量操作!!删除修改都行)

     jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
    
    <!-- 这样也行,但在MySQL需要开启,在一条语句中用';'来分割多条查询,即在配置文件中加上allowMultiQueries=true-->
    <insert id="addEmps">
        <foreach collection="emps" item="emp" separator=";">
            insert into t_emp(`last_name`, `gender`, `email`, `d_id`)
            values(#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id});
        </foreach>
    </insert>
    

Oracle不支持values(),(),()语法。支持这种:

#1.多条insert放到begin end之间
begin
	insert into t_emp(`id`, `last_name`, `gender`, `email`)
	values(t_emp_seq.nextVal, 'test01', '1', 'test01@qq.com');
	insert into t_emp(`id`, `last_name`, `gender`, `email`)
	values(t_emp_seq.nextVal, 'test02', '0', 'test02@qq.com');
end;
#2.利用中间表
insert into t_emp(`id`, `last_name`, `gender`, `email`)
	select t_emp_seq.nextVal, `last_name`, `gender`, `email` from(
        select 'test01' as last_name , '1' as gender, 'test01@qq.com' as email from dual
        union
        select 'test02' as last_name, '0' as gender, 'test02@qq.com' as email  from dual
    )

5.6 两个内置参数

_parameter代表整个参数。

如果单个参数则_parameter就代表这个参数。如果多个参数底层封装为Map,则_parameter就代表这个Map

_databaseId:如果配置了DatabaseIdProvider标签(支持多厂商数据库的标签),那么就代表当前厂商数据库的别名。

<select id="getEmps" resultType="com.sutong.bean.Emp">
    <if test="_databaseId == 'mysql'">
        select * from t_emp
        <if test="_parameter != null">  <!-- 传入参数才按照参数查-->
            where id = #{id}
        </if>
    </if>
    
    <if test="_databaseId == 'oracle'">
        select * from emps
    </if>
</select>

5.7 bind

在增删改查标签里面,还有个bind标签,可以OGNL表达式的值绑定到一个变量中,方便后来引用这个变量。

<select id="getEmp" resultType="com.sutong.bean.Emp">
    <bind name="_lastName" value="'%' + lastName + '%'"/>
    select * from e_emp where last_name like #{_lastName}  
    <!-- 模糊查询需要在我们传参的时候加上%。 这样是不行的:'%#{lastName}%'-->
</select>

但还是推荐在传参是时候加上模糊查询规则:

Emp emp = mapper.getEmp("%a%");


5.8 sql

sql可以抽取可重用的sql片段,方便后面引用。经常用于抽取查询插入时的很多字段。

<!-- sql标签里面也能写if等和上面的两个参数判断等-->
<sql id="insertColumn">
    `last_name`, `gender`, `email`, `d_id`
</sql>

<insert id="addEmp">
    <!-- include引入定义的sql,该标签里面还能给sql标签传参!${propName}取值-->
    insert into t_emp( <include refid="insertColumn"></include> ) 
    values (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
</insert>

6. 缓存机制

MyBatis包含了一个非常强大的查询缓存特性,它可以非常方便的配置和定制。极大提升了查询效率。默认定义了两级缓存。


6.1 一级缓存

一级缓存(本地缓存):与数据库同一次会话期间查询到的数据会放到本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

也叫sqlSession级别的一个Map(每个sqlSession都有一个缓存),是一直存在的,我们没办法关闭。

try {
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp01 = mapper.getEmpById(1);
    System.out.println(emp01);

    Emp emp02 = mapper.getEmpById(1);
    System.out.println(emp02);// 打印了两次,但只发送了一次sql
    System.out.println(emp01 == emp02); // true 
} finally {
    sqlSession.close();
}

⭐一级缓存失效情况:

  • sqlSession不同
  • sqlSession相同,查询条件不同(原因:当前一级缓存中还没有这个数据)
  • sqlSession相同,两次查询期间执行了增删改操作(增删改可能对当前数据有影响)
  • sqlSession相同,手动清除了一级缓存,sqlSession.clearCache()

6.2 二级缓存

二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。

工作机制:

  1. 一个会话,查询一条数据,这个数据就会放到当前会话的一级缓存中

  2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中(注意会话关闭才会写入

  3. 开启新的会话,查新信息的时候就会参考二级缓存中的内容

  4. sqlSession --> EmpMapper -> Emp

    ​ --> DeptMapper -> Dept

    不同的namespace查出的数据会放到自己对应的缓存中(Map中)

⭐使用:

1.开启全局二级缓存配置(默认是true)

<setting name="cacheEnabled" value="true"/>

2.取每个mapper.xml中配置使用二级缓存

<!-- eviction缓存的回收策略(LRU|FIFO|SOFT|WEAK)
     flushInterval缓存刷新间隔毫秒为单位(多长时间情况一次,默认不清空)
     readOnly缓存是否只读,
只读:mybatis认为所有从缓存中获取的数据都是只读操作,认为不会修改数据。所以为了加快速度,则直接把数据在缓存中的引用交给用户。
非只读:获得来的数据可能会被修改,mybatis会利用反序列化拷贝一份数据给用户(默认)
     size缓存存放多少元素
     type指定自定义缓存的全类名(实现Cache接口)-->
<cache eviction="LRU" flushInterval="60000" readOnly="false" size="1024"/>
属性可以不写都使用默认的 <cache></cache>

3.因为安全问题,底层使用序列化和反序列化技术,我们的pojo需要实现序列化接口Serializable

注意:查询的数据都会默认放在一级缓存中,只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中。

和缓存相关的设置/属性:

  • cacheEnabled 如果设置为false,关闭的是二级缓存
  • 每个select标签都有一个useCache="true"属性,默认true。设置为false则关闭的还是二级缓存
  • 每个增删改标签都有一个flushCache="true"属性,默认true,代表增删改执行完之后都要清除缓存(一级二级缓存都会被清空),其实查询标签里面也要这个属性,但默认是false。
  • sqlSession.clearCache() 只会清除当前sqlSession的一级缓存。
  • localCacheScope 本地缓存作用域(影响的是一级缓存),取值SESSION (默认) | STATEMENT (对相同 SqlSession 的不同查询将不会进行缓存。相当于可以禁用一级缓存)

图解(查询缓存的顺序:二级缓存 -> 一级缓存 -> 数据库):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-janiFRDk-1662964083313)(MyBatis.assets/缓存图解.jpg)]


6.3 第三方缓存

MyBatis缓存底层就是个简单是Map,有个Cache接口供第三方拓展(Redis,EHCache)

// EHCache依赖
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.9</version>
</dependency>

// EhCache和MyBatis整合包(即官网已经根据ehcache实现了CaChe接口,自定义缓存)
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

使用:

  1. 给每个映射文件里加入cache标签

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
    
    <!-- 别的映射文件下也可用 引用缓存!!namespace指定和那个名称空间下的缓存一样-->
    <cache-ref namespace="com.sutong.dao.EmpMapper"/>
    
  2. 需要在类路径下放一个ehcache.xml文件(这里暂不研究,代码里面或百度有解释)

    ?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
        <!-- 磁盘保存路径 -->
        <diskStore path="D:\Learning\Java_Practice\Java Frame\ehcache"/>
    
        <defaultCache
                maxElementsInMemory="10000"
                maxElementsOnDisk="10000000"
                eternal="false"
                overflowToDisk="true"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>
    
  3. 然后开启了二级缓存就可以直接使用了(pojo记得实现序列化接口)

    @Test
    public void test02() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
    
        try {
            EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
            EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
    
            Emp e1 = mapper1.getEmpById(1);
            System.out.println(e1);
            sqlSession1.close();  // 关闭才能放到二级缓存中
    
            Emp e2 = mapper2.getEmpById(1);
            System.out.println(e2);
            sqlSession2.close();  // 可以看到上面我们配置的文件夹里面有缓存的文件了
        } finally {
            //sqlSession.close();
        }
    }
    

7. MyBatis-Spring整合

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

依赖:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

7.1 编写数据源配置

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value=".." />
    <property name="username" value=".." />
    <property name="password" value=".." />
    <property name="driverClassName" value=".." />
</bean>

7.2 SqlSessionFactoryBean

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

还有一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。

还有个mapperLocations指定sql映射文件的位置(在xml和类不在一个文件夹的时候使用)

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

7.3 SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <!-- 构造器注入,里面没得set方法-->
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

7.4 加实现类

需要给接口加实现类,注入到Spring中。

里面获取sqlSession,获取对应的Mapper代理,调用对应的方法就行了。最后注入Spring,后面的Service层就能用了。

public class UserDaoImpl implements UserDao {

    private SqlSession sqlSession;
    public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession; }

    public User getUser(String userId) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.getUser(userId);
    }
}
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSession" ref="sqlSession" />
</bean>

或者使用 SqlSessionDaoSupport 抽象类:SqlSessionDaoSupport 用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {

    // 这样我们的SqlSessionTemplate可以不注入到Spring中,我们可以直接getSqlSession();
    // 但他的父类需要注入一个SqlSessionFactory 
    public User getUser(String userId) {
        SqlSession sqlSession = getSqlSession();
        return sqlSession.getMapper(UserMapper.class).getUser(userId);
    }
}
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

(这个了解,重要是SSM整合)


上面的我们手动写实现类,下面可以让Spring帮我们!!!⭐

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.sutong.dao"/>
</bean>

<!-- 和上面的MapperScannerConfigurer作用一样(上面的老项目用得多)-->
<!-- <mybatis-spring:scan base-package="com.sutong.dao"/> -->

8. 逆向工程(MBG)

MyBatis Generator(MBG):专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,以及QBC风格的条件查询。但表连接存储过程等复杂的SQL的定义需要我们手工编写

依赖(当然还需要MySQL驱动和MyBatis依赖):

<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>

8.1 配置 ☢

详细可以看官网

src/main/resources/generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- context 指定MyBatis的运行环境
         MyBatis3Simple是生成简单版的CRUD,MyBatis3复杂一点带动态标签的查询添加等!!会多几个类-->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        
        <!-- 去掉自动生成的注释-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>  <!-- 去掉生成日期那行注释-->
            <property name="suppressAllComments" value="true" /> <!-- 去掉所有的-->
        </commentGenerator>
        
        <!-- jdbcConnection 指定如何连接到目标数据库-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="9527">
        </jdbcConnection>

        <!-- java类型解析器,不加使用默认的也行-->
        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- 指定JavaBean生成策略,targetPackage生成的目标包名,targetProject指定目标工程, .\当前工程的src下-->
        <javaModelGenerator targetPackage="com.sutong.bean" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- sql映射文件生成策略,(可以生成在dao包下,然后批量注册)-->
        <sqlMapGenerator targetPackage="mybatis.mapper"  targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!-- 指定Mapper接口所在的位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.sutong.dao"
                             targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!-- 指定要逆向分析的那些表,根据表创建JavaBean,domainObjectName生成Bean的名字-->
        <table tableName="t_emp" domainObjectName="Emp"></table>
        <table tableName="t_dept" domainObjectName="Dept"></table>

    </context>
</generatorConfiguration>

pom.xml

<build>
  <plugins>
    <plugin>
      <!--Mybatis-generator插件,用于自动生成Mapper和POJO-->
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.0</version>
      <configuration>
        <!--配置文件的位置-->
        <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
        <verbose>true</verbose>
        <overwrite>true</overwrite> <!-- 如果存在就覆盖-->
      </configuration>
      <executions>
        <execution>
          <id>Generate MyBatis Artifacts</id>
          <goals>
            <goal>generate</goal>
          </goals>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>org.mybatis.generator</groupId>
          <artifactId>mybatis-generator-core</artifactId>
          <version>1.4.0</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

还要有全局配置文件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>

    <!-- 因为外部properties配置文件内容,resource属性引入类路径下资源,url引入网络下或磁盘下-->
    <properties resource="dbconfig.properties"/>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 将写好的sql映射文件一定要注册到全局配置文件中!!!-->
    <mappers>
        <mapper resource="mybatis/mapper/DeptMapper.xml"/>
        <mapper resource="mybatis/mapper/EmpMapper.xml"/>
    </mappers>
</configuration>

8.2 运行

public class RunMybatisGenerator {

    @Test
    public void run() throws InterruptedException, SQLException, 
    IOException, XMLParserException, InvalidConfigurationException {

        List<String> warnings = new ArrayList<>();
        boolean overwrite = true;
        File configFile = new File("src/main/resources/generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}

或者直接右上角Maven的点mybatis-generator命令就可以运行了!!!

它自动把数据库里面的下划线命名法换位Java的驼峰命名法!!

运行后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BK7zQbZb-1662964083313)(MyBatis.assets/image-20220127143306885.png)]


8.3 测试

@Test
public void test01() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // MyBatis3Simple
    try {
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        System.out.println(mapper.selectAll());  // 没有自动生成toString()
    } finally {
        sqlSession.close();
    }
    
    
    
    // MyBatis3
    try {
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        // xxExample就是封装查询条件的
        List<Emp> allEmp = mapper.selectByExample(null); // 查询所有

        // (查询员工字母带a的,而且性别的1的) 或者 (Email里面带e的)
        EmpExample empExample = new EmpExample();             // 封装查询条件的Example
        EmpExample.Criteria c1 = empExample.createCriteria(); // criteria就是拼装条件的
        c1.andLastNameLike("%a%").andGenderEqualTo("1");    

        EmpExample.Criteria c2 = empExample.createCriteria();
        c2.andEmailLike("%e%");
        
        empExample.or(c2); // 把或条件拼装上去

        List<Emp> empsByExample = mapper.selectByExample(empExample); // 条件查询
    } finally {
        sqlSession.close();
    }
}

MyBatis3生产的是GBC风格的查询!!


9. MyBatis工作原理

⭐重要,对后面的插件开发有用


9.1 分层架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NncVCxlz-1662964083314)(MyBatis.assets/分层架构.jpg)]


9.2 运行流程

1.根据全局配置文件,创建SqlSessionFactory
2.获取SqlSession实例
3.获取接口代理对象 (MapperProxy对象)
4.执行增删改查

①SqlSessionFactory的获取:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSSjAWG2-1662964083314)(MyBatis.assets/创建SqlSessionFactory.jpg)]

其中的每个MapperStatement代表一个增删查改标签的详细信息

Configuration包括 全局配置文件+所有mapper映射文件 的详细信息。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

最终得到的是一个DefalutSqlSessionFactory 对象(里面包含了Configuration对象)

总结:把所有配置文件信息解析保存到Configuration对象中,然后返回包含了ConfigurationDefalutSqlSessionFactory对象中

②sqlSession的获取:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3MQjxWg-1662964083314)(MyBatis.assets/创建SqlSession.jpg)]

上图的第七步很重要,是执行所有拦截器的拦截方法的。

SqlSession sqlSession = sqlSessionFactory.openSession();

即调用DefalutSqlSessionFactory方法创建返回一个DefalutSqlSession 对象,里面包含了 ConfigurationExecutor对象!!!(Executor对象会在这一步创建)

总结:返回一个DefalutSqlSession 对象,包含了 ConfigurationExecutor

③获取接口代理对象
在这里插入图片描述

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

返回接口代理对象,里面包含了DefalutSqlSession对象,所以可以用来执行增删查改。

总结:使用MapperProxyFactory 创建对应代理对象,代理对象包含了DefalutSqlSession对象

④执行增删查改
在这里插入图片描述

代理对象 -> DefaultSqlSession -> Executor -> StatementHandler (处理sql语句预编译,设置参数等工作)

-> ParameterHandler (设置预编译参数用的)

-> ResultSetHandler (处理结果集)

(设置参数和处理结果都用到了TypeHandler来做的,处理数据库和Java类型的映射)

查询总结图解:
在这里插入图片描述

重要了解四大对象的作用和执行流程就行


9.3 总结 ☢

  1. 根据所有配置文件,初始化出Configuration对象

  2. 创建一个DefaultSqlSession 对象,里面包含ConfigurationExecutor

    (根据配置文件中的defaultExecutorType设置创建对应的Executor)

  3. getMapper() ,拿到Mapper接口对应的MapperProxy,里面包含DefaultSqlSession

  4. 执行增删改查

    • 调用DefaultSqlSession中的Excutor的增删改查
    • 会创建一个StatementHandler对象(同时创建ParameterHandlerRestSetHandler
    • 调用StatementHandler预编译以及利用ParameterHandler设置sql的参数
    • 调用StatementHandler的增删查改方法
    • RestSetHandler封装结果

四大对象创建的时候都会有:interceptorChain.plugAll(xxxx) !!!


10. 插件开发


10.1 插件原理

  1. 四大对象创建不是直接返回的而是先调用interceptorChain.plugAll(xxxx) 方法

  2. plugAll()方法里面是拿到所有的Interceptor(即拦截器,我们需要实现的接口),然后调用每个拦截器的plugin()方法,target = interceptor.plugin(target) ,执行完所有拦截器,后返回target 包装后的对象。

  3. 插件机制,我们我们可以使用插件为目标对象创建一个代理对象:AOP

我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行方法


10.2 插件实现

  1. 编写Interceptor实现类
  2. 使用Intercepts注解
  3. 注册到全局配置文件中

MyFirstPlugin.java

package com.sutong.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Statement;
import java.util.Properties;

// Intercepts注解完成插件的签名(告诉MyBatis当前插件拦截那个对象的那个方法),里面是Signature数组,
// Signature里面有type属性是要拦截对象(四大对象之一),method属性是要拦截该对象的那个方法,args属性是指定该方法是参数列表
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyFirstPlugin implements Interceptor {

    /**
     * intercept() - 拦截目标对象目标方法的执行
     * @param invocation 目标方法
     * @return 返回执行后的返回值
     */
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MyFirstPlugin - intercept -> " + invocation.getMethod());
        Object result = invocation.proceed(); // 执行目标方法,前后可以做一些事
        return result;
    }

    /**
     * plugin() - 包装目标对象 (包装:为目标对象插件代理对象)
     * 四大个对象的创建都会调用这个方法,只有是我们要拦截的对象才会创建代理对象!否则直接返回
     * @param target 目标对象
     * @return 返回当前target插件的动态代理
     */
    public Object plugin(Object target) {
        System.out.println("MyFirstPlugin - plugin -> 将要包装的对象" + target);
        Object wrap = Plugin.wrap(target, this); // Mybatis为了我们使用方便,包装了Proxy生成代理对象
        return wrap;
    }

    /**
     * setProperties() - 将插件注册时的 property 属性设置进来
     * @param properties 插件的配置信息
     */
    public void setProperties(Properties properties) {
        System.out.println("插件的配置信息:" + properties);
    }
}

mybatis-config.xml

<!-- 注册插件-->
<plugins>
    <plugin interceptor="com.sutong.interceptor.MyFirstPlugin">
        <property name="username" value="sutong"/>
        <property name="password" value="666"/>
    </plugin>
</plugins>

10.3 多个插件 ☢

如果多个插件拦截同一个对象的同一个方法,则:

plugin()则和注册插件的顺序有关!有几个插件包装几次!!多层代理… (正序)

intercept() 由于的多层代理,一进来是先进入最后一个包装的代理,执行其intercept方法,然后进入第二层代理执行方法…(反序)

创建插件动态代理的时候,是按照插件配置顺序创建代理对象,执行目标方法的时候按照逆向顺序执行的。


10.4 简单使用

动态改变一下sql运行的参数!!

@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyFirstPlugin implements Interceptor {

    // 偷梁换柱
    public Object intercept(Invocation invocation) throws Throwable {

        // 动态改变一下sql运行的参数:以前查1号员工,我们实际从数据库查2号员工!!偷梁换柱
        // 拿到StatementHandler -> ParameterHandler -> parameterObject值
        Object target = invocation.getTarget(); // 我们在拦截的对象
        MetaObject metaObject = SystemMetaObject.forObject(target);   // 拿到target的元数据

        //metaObject.getValue("parameterHandler.parameterObject");    // 获取sql语句用的参数
        metaObject.setValue("parameterHandler.parameterObject", 2);   // 修改sql的参数

        // 执行目标方法
        Object result = invocation.proceed();
        return result;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {}
}

测试:

@Test
public void test01() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    try {
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        System.out.println(mapper.getEmpById(1)); // 则这里返回的是2号员工的信息!!!!
    } finally {
        sqlSession.close();
    }
}

10.5 插件应用

  • PageHelper插件进行分页
  • 批量操作
  • 存储过程
  • typeHandler处理枚举

PageHelper ☢

PageHelper分页插件的使用(支持多种数据库)

1.依赖:

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
</dependency>

2.配置拦截器

  • 在MyBatis中配置

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
            <property name="param1" value="value1"/>
    	</plugin>
    </plugins>
    
  • 或 在 Spring 配置文件中配置拦截器插件!!

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <!-- 注意其他配置dataSource,mapperLocations等.. -->
      <property name="plugins">
        <array>
          <bean class="com.github.pagehelper.PageInterceptor">
            <property name="properties">
              <!--使用下面的方式配置参数,一行配置一个 -->
              <value>
                params=value1
                helperDialect=mysql
              </value>
            </property>
          </bean>
        </array>
      </property>
    </bean>
    

3.使用

接口:

// 接口
public interface EmpOther {
    List<Emp> getEmps();
}

映射文件:

<mapper namespace="com.sutong.dao.EmpOther">
    <select id="getEmps" resultType="com.sutong.bean.Emp">
        select `id`, `last_name`, `gender`, `email` from t_emp
    </select>
</mapper>

测试:

@Test
public void test01() throws IOException {
	// ...
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        // 只调用这一个方法进行,第一个是第几页,第二个是每页记录数,返回一个分页的详细信息对象
        Page<Object> pageHandler = PageHelper.startPage(2, 2);
        
        EmpOther mapper = sqlSession.getMapper(EmpOther.class);
        for (Emp emp : mapper.getEmps()) {
            System.out.println(emp);
        }
        System.out.println("当前页码: " + pageHandler.getPageNum());
        System.out.println("总页码: " + pageHandler.getPages());
        System.out.println("总记录数: " + pageHandler.getTotal());
        System.out.println("每页记录数: " + pageHandler.getPageSize()); // 还有...
    } finally {
        sqlSession.close();
    }
}



// 或者使用PageInfo获得信息,更详细!!PageInfo<Emp> info = new PageInfo<>(list); 对结果进行包装
// 上面的那几个信息都有,还要 info.isIsFirstPage(),isIsLastPage()...等等!!
// 还可以这样new PageInfo<>(list, navigatePages); navigatePages是个int ,代表连续显示几页!!
public void test02() throws IOException {
   	// ...
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        PageHelper.startPage(2, 2);  // 开始分页
        
        EmpOther mapper = sqlSession.getMapper(EmpOther.class);
        List<Emp> list = mapper.getEmps();               // 查询
        PageInfo<Emp> info = new PageInfo<Emp>(list, 5); // 告诉插件我们要连续5页!!!
        for (Emp emp : list) {
            System.out.println(emp);
        }

        System.out.println("是否是第一页: " + info.isIsFirstPage());
        int[] nums = info.getNavigatepageNums(); // 连续显示的页码,就不用我们处理了!!直接返回前端取出
        System.out.println(Arrays.toString(nums));
    } finally {
        sqlSession.close();
    }
}

批量处理

上面的动态SQL使用的foreach严格一样上不算批量处理,那上面只是拼一个很长的SQL一起发送服务器,如果太长了服务器就不能接受了。

有个设置defaultExecutorType 是配置默认的执行器。默认是SIMPLE简单执行器, BATCH 执行器不仅能重用语句还会执行批量更新。

如果在全局配置文件改的话所有是SQL都会变为批量的。我们可以单独设置(在获取sqlSession传入一个参数)。

  1. Mybatis使用

    @Test
    public void testBatch() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
        // 传入ExecutorType,就可以获得一个批量操作的sqlSession!!!!
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            EmpOther mapper = sqlSession.getMapper(EmpOther.class);
            List<Emp> list = new ArrayList<Emp>();
            list.add(new Emp(null, UUID.randomUUID().toString(), "1", "test@qq.com"));
            list.add(new Emp(null, UUID.randomUUID().toString(), "0", "test@qq.com"));
            list.add(new Emp(null, UUID.randomUUID().toString(), "1", "test@qq.com"));
            // ....
            
            // 调用多次添加就行了,时间很短的。
            // 非批量的每执行一个增删改查就发送一个SQL到数据库,预编译(n次) -> 设置参数(n次) -> 执行(n次)
            // 批量一起发送执行,预编译(1次) -> 设置参数(n次) -> 执行(1次)
            for (Emp emp : list) {   
                mapper.addEmp(emp);
            }
            sqlSession.commit();
        } finally {
            sqlSession.close();
        }
    }
    
  2. Spring中使用

    配置 spring-dao.xml里面:

    <!-- 配置一个可以批量执行的sqlSession-->
    <bean id="batchSqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" value="sqlSessionFactory"/>
        <constructor-arg name="executorType" value="BATCH"/>
    </bean>
    

    使用:

    public class EmpServiceImpl {
        private BookMapper bookMapper;
        
        @Autowired
        private SqlSession sqlSession;
        
        public void testBatch() {
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // 然后调用里面的增删改查方法就是批量的了
        }
    }
    
    
  • 非批量:预编译(n次) -> 设置参数(n次) -> 执行(n次)
  • 批量:预编译(1次) -> 设置参数(n次) -> 执行(1次)

存储过程

存储过程:很多sql语句是集合,编译一次,保存在数据库中的。MyBatis支持对存储过程的调用。

先学习一下存储MySQL中的存储过程:

  • 入参存储过程(入参类似java方法中的形参)

    # 参数写法:输入输出类型 参数名称 参数数据类型
    
    # 入参存储过程,例如:根据传来的数字,动态赋值name
    create procedure add_name(in target int)  #in代表形参,out代表返回值
    begin  # begin 和 end 之间写我们的逻辑
    	declare emp_name varchar(11);  #定义一个变量
    	if target = 1 then
    		set emp_name = '一号员工';   # 赋值需要加个set,否则的判断
    		else
    		set emp_name = '其他员工';
    	end if;
    	insert into t_emp(`last_name`, `gender`, `email`) values(emp_name, '1', 'test@qq.com');
    end
    
    # 调用存储过程:
    call add_name(1);
    
    # 删除存储过程:
    drop procedure add_name;
    
  • 出参存储过程(出参类似java方法中的返回值)

    # 出参存储过程,例如:统计t_emp表总条数
    create procedure count_emp(out count_num int)
    begin
    	# 直接把查询出来的结果赋值给count_num
    	select count(*) into count_num from t_emp; 
    end;
    
    # 定义变量前面需要加上@,这样是不显示东西的,需要去查询这个变量
    call count_emp(@count_num);
    select @count_num;
    
    drop procedure count_emp;
    

MyBatis中存储过程的调用:

接口:

public interface EmpOther {
    void testProcedure(Integer num);
}

映射文件:

<!-- 调用存储过程(首先要得有!),要使用select标签,例如调用上面创建的add_name存储过程!!
	 statementType="CALLABLE"是表示要调用存储过程,默认的PREPARED。 mode=IN代表是入参-->
<select id="testProcedure" statementType="CALLABLE"> 
    {call add_name( #{num, mode=IN, jdbcType=INTEGER} )} 
</select>

测试:

@Test
public void test03() throws IOException {
    // ...
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        EmpOther mapper = sqlSession.getMapper(EmpOther.class);
        mapper.testProcedure(10);  // 调用
    } finally {
        sqlSession.close();
    }
}

typeHandler处理类型

学习写枚举的使用

@Test
public void testEnumUse() {
        EmpStatus login = EmpStatus.LOGIN;
        System.out.println(login.ordinal()); // 枚举索引
        System.out.println(login.name()); // 枚举名字
}

测试默认的枚举处理:

public enum EmpStatus {
    LOGIN, LOGOUT, REMOVE
}

public class Emp {
    private Integer id;
    private String lastName;   
    private String gender;   
    private String email;
    private EmpStatus empStatus; // 员工状态
}
<insert id="addEmp">
    insert into t_emp(`last_name`, `gender`, `email`, `empStatus`) 
    values (#{lastName}, #{gender}, #{email}, #{empStatus})
</insert>

测试默认的枚举处理:

try {
    EmpOther mapper = sqlSession.getMapper(EmpOther.class);
    mapper.addEmp(new Emp(null, "enum", "1", "enum@qq.com", EmpStatus.LOGIN));

    sqlSession.commit();
} finally {
    sqlSession.close();
}

数据库默认保存的是枚举的名字:LOGIN,即使用EnumTypeHandler处理的!!

我们可以改变让数据库存储枚举索引,使用EnumOrdinalTypeHandler,要设置全局配置文件

<typeHandlers>
    <!-- handler指定要用的类型处理器,javaType是指定要处理的类型(不写对所有满足的都用这个)-->
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                 javaType="com.sutong.bean.EmpStatus"/>
</typeHandlers>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzyWTd6I-1662964083316)(MyBatis.assets/image-20220128190140331.png)]


上面这样使用的并不多,自定义处理才是使用最多的:

/**
 * 希望数据库保存的是状态码,而不是默认的索引或者枚举名!
 */
public enum EmpStatus {
    LOGIN(100, "用户登录"), LOGOUT(200, "用户登出"), REMOVE(300, "用户不存在");

    private Integer code;
    private String msg;
    private EmpStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    
    // 按照状态码返回枚举对象
    public static EmpStatus getEmpStatusByCode(Integer code) {
        switch (code) {
            case 100:
                return EmpStatus.LOGIN;
            case 200:
                return EmpStatus.LOGOUT;
            case 300:
                return EmpStatus.REMOVE;
            default:
                return EmpStatus.LOGOUT;
        }
    }
    
    // 下面还有get/set
}

自定义类型处理器要TypeHandler接口或者继承BaseTypeHandler

// 自定义的枚举类型处理器
public class MyEnumEmpStatusHandler implements TypeHandler<EmpStatus> {

    // 定义当前数据如何保存到数据库中
    public void setParameter(PreparedStatement preparedStatement, int i, 
                             EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
        
        preparedStatement.setString(i, empStatus.getCode().toString());  // 保存状态码
    }

    public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
        int code = resultSet.getInt(s); // s是数据库字段名
        // 根据从数据库拿到的状态码,返回一个枚举对象
        return EmpStatus.getEmpStatusByCode(code);
    }

    public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
        int code = resultSet.getInt(i); // i是数据库字段索引
        return EmpStatus.getEmpStatusByCode(code);
    }

    public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
        int code = callableStatement.getInt(i); // i是数据库字段索引,是存储过程中拿!!
        return EmpStatus.getEmpStatusByCode(code);
    }
}

全局配置:

<typeHandlers>
    <typeHandler handler="com.sutong.handler.MyEnumEmpStatusHandler"
                 javaType="com.sutong.bean.EmpStatus"/>
</typeHandlers>

<!-- 也可以在处理每个字段的时候告诉MyBatis用那个类型处理器-->
保存时:#{empStatus,typeHandler=com.sutong.handler.MyEnumEmpStatusHandler}
查询时:就要使用ResultMap了,自定义,<result column="empStatus" property="empStatus" typeHandler=".."/>
我们应该保证在查询和保存使用的的类型处理器是一样的!!!这样才不会出错

测试:

try {
    EmpOther mapper = sqlSession.getMapper(EmpOther.class);
    mapper.addEmp(new Emp(null, "myEnum", "0", "myEnum@qq.com", EmpStatus.REMOVE));

    sqlSession.commit();
} finally {
    sqlSession.close();
}


Emp emp = mapper.getEmpById(14);
System.out.println(emp.getEmpStatus()); // 查询出来的时候会封装为EmpStatus对象!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Ua9jwCM-1662964083316)(MyBatis.assets/image-20220128192439653.png)]

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值