今でもあなたは私の光丶

Mybatis深入

Mybatis配置文件深入

核心配置文件层级关系

configuration
  • properties 属性实际开发,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件
  • settings 设置
    • cacheEnabled全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。
    • lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
    • aggressiveLazyLoading当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。
    • multipleResultSetsEnabled是否允许单一语句返回多结果集(需要驱动支持)。
    • useColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
    • useGeneratedKeys允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。
    • autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
    • autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或者未知属性类型)的行为。
      • NONE: 不做任何反应
      • WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
      • FAILING: 映射失败 (抛出 SqlSessionException)
    • defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
    • defaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数。
    • defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。
    • defaultResultSetTypeSpecifies a scroll strategy when omit it per statement settings. (Since: 3.5.2) 指定在忽略每个语句设置时的滚动策略。(自:3.5.2起)
    • safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。
    • safeResultHandlerEnabled允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。
    • mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
    • localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
    • jdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
    • lazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载。
    • defaultScriptingLanguage指定动态 SQL 生成的默认语言。
    • defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)
    • callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。
    • returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。(新增于 3.4.2)
    • logPrefix指定 MyBatis 增加到日志名称的前缀。
    • logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
    • proxyFactory指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。
    • vfsImpl指定 VFS 的实现
    • useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)
    • configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)
  • typeAliases 类型别名
  • typeHandlers 类型处理器无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
  • objectFactory 对象工厂
  • plugins 插件
  • environments 环境environment 环境变量
    • transactionManager 事务管理器type 事务管理类型
      • JDBC这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的链接来管理事务操作
      • MANAGED这个配置几乎没做什么,它从来不提交或回滚一个链接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文),默认情况下,它会关闭链接,然而一些容器并不希望这样,因此需要将closeConnection属性设置为false来阻止它默认的关闭行为
    • dataSource 数据源type 数据源类型
      • UNPOOLED这个数据源的实现只是每次被请求时打开和关闭链接
      • POOLED这个数据源的实现利用"池"的概念将JDBC链接对象组织起来
      • JNDI这个数据源的实现是为了能在EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用
  • databaseIdProvider 数据库厂商标识
  • mappers 映射器mapper标签作用:加载映射的方式
    • 使用相对与类路径的资源引用<mapper resource="cn/wxrwcz/builder/UserMapper.xml"/>
    • 使用完全限定资源定位符(URL)<mapper url="file:///var/mappers/UserMapper.xml"/>
    • 使用映射器接口实现类的完全限定类名<mapper class="org.mybatis.builder.AuthorMapper"/>
    • 将包内的映射器接口实现全部注册为映射器,xml文件和接口同胞同名<mapper name="org.mybatis.builder"/>

mapper.xml

动态sql语句
if标签

动态 SQL 通常要做的事情是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
    resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
  AND title like #{title}
</if>
</select>

这条语句提供了一种可选的查找文本功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会对“title”一列进行模糊查找并返回 BLOG 结果(细心的读者可能会发现,“title”参数值是可以包含一些掩码或通配符的)。

如果希望通过“title”和“author”两个参数进行可选搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

<select id="findActiveBlogLike"
    resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
 <if test="title != null">
  AND title like #{title}
 </if>
 <if test="author != null and author.name != null">
  AND author_name like #{author.name}
 </if>
</select>

自己编写的例子:

    <!--    多条件组合查询-->
   <select id="findByCondition" resultType="user" parameterType="user">
      select * from User
        <where>
            <if test="id != null ">
              and id = #{id}
            </if>
            <if test="name != null and name !=''">
                and name = #{name}
            </if>
        </where>
   </select>
foreach

属性

  • collection代表要遍历的集合元素,注意编写时不要写#{}
  • open代表语句的开始部分
  • close代表结束部分
  • item代表遍历集合的每个元素,生成的变量名
  • sperator代表分隔符

例子

    <!--    多值查询:演示foreach-->
   <select id="findByIds" resultType="user" parameterType="list">
      select * from User
       <where>
           <foreach collection="array" open="id in (" close=")" separator="," item="id">
              id
           </foreach>
       </where>
   </select>
sql
    <!--抽取sql片段-->
   <sql id="selectUser">
      select * from User
   </sql>
include
    <!--    多值查询:演示foreach-->
   <select id="findByIds" resultType="user" parameterType="list">
       <include refid="selectUser"/>
       <where>
           <foreach collection="array" open="id in (" close=")" separator="," item="id">
              id
           </foreach>
       </where>
   </select>
choose, when, otherwise

有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若两者都没有提供,就返回所有符合条件的 BLOG(实际情况可能是由管理员按一定策略选出 BLOG 列表,而不是返回大量无意义的随机结果)。

<select id="findActiveBlogLike"
    resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
 <choose>
   <when test="title != null">
    AND title like #{title}
   </when>
   <when test="author != null and author.name != null">
    AND author_name like #{author.name}
   </when>
   <otherwise>
    AND featured = 1
   </otherwise>
 </choose>
</select>
trim, where
<select id="findActiveBlogLike"
    resultType="Blog">
SELECT * FROM BLOG
 <where>
   <if test="state != null">
        state = #{state}
   </if>
   <if test="title != null">
      AND title like #{title}
   </if>
   <if test="author != null and author.name != null">
      AND author_name like #{author.name}
   </if>
 </where>
</select>

where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。

set

类似的用于动态更新语句的解决方案叫做 set。set 元素可以用于动态包含需要更新的列,而舍去其它的。比如:

<update id="updateAuthorIfNecessary">
update Author
  <set>
    <if test="username != null">username=#{username},</if>
    <if test="password != null">password=#{password},</if>
    <if test="email != null">email=#{email},</if>
    <if test="bio != null">bio=#{bio}</if>
  </set>
where id=#{id}
</update>

这里,set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。(译者注:因为用的是“if”元素,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留)

若你对 set 元素等价的自定义 trim 元素的代码感兴趣,那这就是它的真面目:

<trim prefix="SET" suffixOverrides=",">
...
</trim>

注意这里我们删去的是后缀值,同时添加了前缀值。

resultType

mybatis自动按照字段名和属性名完成映射封装

resultMap

手动配置实体属性和表字段的映射关系

复杂映射开发

一对一查询 MapperScannerConfigurer

用户表和订单表,一个用户有多个订单,一个订单只从属于一个用户 一对一查询的需求:查询一个订单,与此同时查询出该订单所属用户

SQL

select * from orders o,user u where o.uid=u.id;
代码
  • 创建实体类
    • Orders.javapackage cn.wxrwcz.pojo;

      public class Orders {
         private Integer id;
         private Integer uid;
         private String time;
         private Double total;

         public User getUser() {
             return user;
        }

         public void setUser(User user) {
             this.user = user;
        }

         //表明该订单属于哪个用户
         private User user;

         public Integer getId() {
             return id;
        }

         public void setId(Integer id) {
             this.id = id;
        }

         public Integer getUid() {
             return uid;
        }

         public void setUid(Integer uid) {
             this.uid = uid;
        }

         public String getTime() {
             return time;
        }

         public void setTime(String time) {
             this.time = time;
        }

         public Double getTotal() {
             return total;
        }

         public void setTotal(Double total) {
             this.total = total;
        }

         @Override
         public String toString() {
             return "Orders{" +
                     "id=" + id +
                     ", uid=" + uid +
                     ", time='" + time + '\'' +
                     ", total=" + total +
                     ", user=" + user +
                     '}';
        }
      }
    • User.javapackage cn.wxrwcz.pojo;

      public class User {
         private Integer id;
         private String name;
         private String password;
         private String birthday;

         public Integer getId() {
             return id;
        }

         public void setId(Integer id) {
             this.id = id;
        }

         public String getName() {
             return name;
        }

         public void setName(String name) {
             this.name = name;
        }

         public String getPassword() {
             return password;
        }

         public void setPassword(String password) {
             this.password = password;
        }

         public String getBirthday() {
             return birthday;
        }

         public void setBirthday(String birthday) {
             this.birthday = birthday;
        }

         @Override
         public String toString() {
             return "User{" +
                     "id=" + id +
                     ", name='" + name + '\'' +
                     ", password='" + password + '\'' +
                     ", birthday='" + birthday + '\'' +
                     '}';
        }
      }
  • 创建接口package cn.wxrwcz.mapper;

    import cn.wxrwcz.pojo.Orders;

    import java.util.List;

    public interface IOrdersMapper {
       //查询订单同时还查询该订单所属用户
       public List<Orders> findOrderAndUser();

    }
  • 配置xml<?xml version="1.0" encoding="UTF-8" ?>
    <!--配置头-->
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="cn.wxrwcz.mapper.IOrdersMapper">
       <resultMap id="ordersMap" type="cn.wxrwcz.pojo.Orders">
           <result property="id" column="id"/>
           <result property="uid" column="uid"/>
           <result property="time" column="time"/>
           <result property="total" column="total"/>
           <association property="user" javaType="cn.wxrwcz.pojo.User">
               <result property="id" column="uid"/>
               <result property="name" column="name"/>
               <result property="password" column="password"/>
               <result property="birthday" column="birthday"/>
           </association>
       </resultMap>
       <select id="findOrderAndUser" resultMap="ordersMap">
          select * from orders o,user u where o.uid=u.id;
       </select>
    </mapper>

一对多查询的模型

用户表和订单表,一个用户有多个订单,一个订单只从属于一个用户 一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

sql

select *,o.id oid from user u left join orders o on u.id=o.uid;
代码
  • 修改实体类Orders.javapackage cn.wxrwcz.pojo;

    public class Orders {
       private Integer id;
       private Integer uid;
       private String time;
       private Double total;

       public User getUser() {
           return user;
      }

       public void setUser(User user) {
           this.user = user;
      }

       //表明该订单属于哪个用户
       private User user;

       public Integer getId() {
           return id;
      }

       public void setId(Integer id) {
           this.id = id;
      }

       public Integer getUid() {
           return uid;
      }

       public void setUid(Integer uid) {
           this.uid = uid;
      }

       public String getTime() {
           return time;
      }

       public void setTime(String time) {
           this.time = time;
      }

       public Double getTotal() {
           return total;
      }

       public void setTotal(Double total) {
           this.total = total;
      }

       @Override
       public String toString() {
           return "Orders{" +
                   "id=" + id +
                   ", uid=" + uid +
                   ", time='" + time + '\'' +
                   ", total=" + total +
                   '}';
      }
    }User.javapackage cn.wxrwcz.pojo;


    import java.util.ArrayList;
    import java.util.List;

    public class User {
       private Integer id;
       private String name;
       private String password;
       private String birthday;
       private List<Orders> ordersList = new ArrayList<>();

       public Integer getId() {
           return id;
      }

       public void setId(Integer id) {
           this.id = id;
      }

       public String getName() {
           return name;
      }

       public void setName(String name) {
           this.name = name;
      }

       public String getPassword() {
           return password;
      }

       public void setPassword(String password) {
           this.password = password;
      }

       public String getBirthday() {
           return birthday;
      }

       public void setBirthday(String birthday) {
           this.birthday = birthday;
      }

       public List<Orders> getOrdersList() {
           return ordersList;
      }

       public void setOrdersList(List<Orders> ordersList) {
           this.ordersList = ordersList;
      }

       @Override
       public String toString() {
           return "User{" +
                   "id=" + id +
                   ", name='" + name + '\'' +
                   ", password='" + password + '\'' +
                   ", birthday='" + birthday + '\'' +
                   ", ordersList=" + ordersList +
                   '}';
      }
    }
  • 创建接口package cn.wxrwcz.mapper;

    import cn.wxrwcz.pojo.User;

    import java.util.List;

    public interface IUserMapper {
       //查询所有用户,同时查询每个用户关联的订单信息
       public List<User> findAll();
    }
  • 配置xml<?xml version="1.0" encoding="UTF-8" ?>
    <!--配置头-->
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="cn.wxrwcz.mapper.IUserMapper">
       <resultMap id="userMap" type="cn.wxrwcz.pojo.User">
           <result property="id" column="id"/>
           <result property="name" column="name"/>
           <result property="password" column="password"/>
           <result property="birthday" column="birthday"/>
           <collection property="ordersList" ofType="cn.wxrwcz.pojo.Orders">
               <result property="id" column="oid"/>
               <result property="uid" column="uid"/>
               <result property="time" column="time"/>
               <result property="total" column="total"/>
           </collection>
       </resultMap>
       <select id="findAll" resultMap="userMap">
          select *,o.id oid from user u left join orders o on u.id=o.uid
       </select>
    </mapper>

多对多查询的模型

创建表
CREATE TABLE `role` (
`id` int(8) NOT NULL AUTO_INCREMENT COMMENT 'id',
`roleName` varchar(50) DEFAULT '' COMMENT '角色名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4
/*------- CREATE SQL---------*/
CREATE TABLE `user_role` (
`uid` int(8) DEFAULT NULL,
`rid` int(8) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

SQL

select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.uid inner join role r on ur.rid=r.id;
代码
  • 创建及修改实体类package cn.wxrwcz.pojo;

    public class Role {
       private Integer id;
       private String roleName;

       public Integer getId() {
           return id;
      }

       public void setId(Integer id) {
           this.id = id;
      }

       public String getRoleName() {
           return roleName;
      }

       public void setRoleName(String roleName) {
           this.roleName = roleName;
      }

       @Override
       public String toString() {
           return "Role{" +
                   "id=" + id +
                   ", roleName='" + roleName + '\'' +
                   '}';
      }
    }package cn.wxrwcz.pojo;


    import java.util.ArrayList;
    import java.util.List;

    public class User {
       private Integer id;
       private String name;
       private String password;
       private String birthday;
       private List<Orders> ordersList = new ArrayList<>();
       //用户的角色信息
       private List<Role> roles = new ArrayList<>();
       public Integer getId() {
           return id;
      }

       public void setId(Integer id) {
           this.id = id;
      }

       public String getName() {
           return name;
      }

       public void setName(String name) {
           this.name = name;
      }

       public String getPassword() {
           return password;
      }

       public void setPassword(String password) {
           this.password = password;
      }

       public String getBirthday() {
           return birthday;
      }

       public void setBirthday(String birthday) {
           this.birthday = birthday;
      }

       public List<Orders> getOrdersList() {
           return ordersList;
      }

       public void setOrdersList(List<Orders> ordersList) {
           this.ordersList = ordersList;
      }

       @Override
       public String toString() {
           return "User{" +
                   "id=" + id +
                   ", name='" + name + '\'' +
                   ", password='" + password + '\'' +
                   ", birthday='" + birthday + '\'' +
                   ", roles=" + roles +
                   '}';
      }
    }
  • 修改接口package cn.wxrwcz.mapper;

    import cn.wxrwcz.pojo.User;

    import java.util.List;

    public interface IUserMapper {
       //查询所有用户,同时查询每个用户关联的订单信息
       public List<User> findAll();
       //查询所有用户,同时查询用户关联的角色信息
       List<User> findAllUserAndRole();
    }
  • 修改mapper.xml<?xml version="1.0" encoding="UTF-8" ?>
    <!--配置头-->
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="cn.wxrwcz.mapper.IUserMapper">
       <resultMap id="userMap" type="cn.wxrwcz.pojo.User">
           <result property="id" column="id"/>
           <result property="name" column="name"/>
           <result property="password" column="password"/>
           <result property="birthday" column="birthday"/>
           <collection property="ordersList" ofType="cn.wxrwcz.pojo.Orders">
               <result property="id" column="oid"/>
               <result property="uid" column="uid"/>
               <result property="time" column="time"/>
               <result property="total" column="total"/>
           </collection>
       </resultMap>
       <select id="findAll" resultMap="userMap">
           select *,o.id oid from user u left join orders o on u.id=o.uid
       </select>
       <resultMap id="userRoleMap" type="cn.wxrwcz.pojo.User">
           <result property="id" column="id"/>
           <result property="name" column="name"/>
           <result property="password" column="password"/>
           <result property="birthday" column="birthday"/>
           <collection property="roles" ofType="cn.wxrwcz.pojo.Role">
               <result property="id" column="rid"/>
               <result property="roleName" column="role_name"/>
           </collection>
       </resultMap>
       <select id="findAllUserAndRole" resultMap="userRoleMap">
           select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.uid inner join role r on ur.rid=r.id
       </select>
    </mapper>

MyBatis注解开发

常用注解

  • @Insert 实现新增
  • @Update 实现更新
  • @Delete 实现删除
  • @Select 实现查询
  • @Result 实现结果集封装代替了id标签和Result标签@Result属性介绍column:数据库的列名 property:需要装配的属性名 one:需要使用的@One注解(@Result(one=@One)) many:需要使用的@Many注解(@Result(many=@Many))
  • @Results 可以与@Result一起使用,封装多个结果集代替的是标签<resultMap>该注解可以使用单个@Result注解,也可以使用@Result集合,使用格式:@Results({@Result(),@Result()})或者@Results(@Result())
  • @One 实现一对一结果集封装
  • @Many 实现一对多结果集封装

例子

  • 常用代码块    @Insert("insert into user values(#{id},#{name},#{password},#{birthday})")
       void addUser(User user);
       @Update("update user set name = #{name} where id = #{id}")
       void updateUser(User user);
       @Select("select * from user")
       List<User> selectAll();
       @Delete("delete from user where id = #{id}")
       void deleteUser(Integer id);
  • 一对一代码块IOrdersMapper.java    //查询订单同时还查询该订单所属用户
       @Results({
               @Result(property = "id",column = "id"),
               @Result(property = "uid",column = "uid"),
               @Result(property = "time",column = "time"),
               @Result(property = "user",column = "uid" ,javaType= User.class,
               one = @One(select = "cn.wxrwcz.mapper.IUserMapper.findUserById")
              )
      })
       @Select("select * from orders")
       public List<Orders> findAll();IUserMapper.java    @Select("select * from user where id = #{id}")
       User findUserById(Integer id);
  • 一对多代码块IUserMapper.java    //查询所有用户,同时查询每个用户关联的订单信息
       @Results({
               @Result(property = "id",column = "id"),
               @Result(property = "name",column = "name"),
               @Result(property = "password",column = "password"),
               @Result(property = "birthday",column = "birthday"),
               @Result(property = "ordersList",column = "id" , javaType = Orders.class,
                       many = @Many(select = "cn.wxrwcz.mapper.IOrdersMapper.selectAllByUserId")),
      })
       @Select("select * from user")
       public List<User> findAllAnnotation();IOrdersMapper.java    @Select("select * from orders where uid =#{uid}")
       List<Orders> selectAllByUserId(Integer uid);
  • 多对多代码块IUserMapper.java    //查询所有用户,同时查询用户关联的角色信息
       @Results({
               @Result(property = "id",column = "id"),
               @Result(property = "name",column = "name"),
               @Result(property = "password",column = "password"),
               @Result(property = "birthday",column = "birthday"),
               @Result(property = "ordersList",column = "id" , javaType = Role.class,
                       many = @Many(select = "cn.wxrwcz.mapper.IUserMapper.findRolesByUserId")),
      })
       @Select("select * from user")
       List<User> findAllUserAndRoleByAnnotation();
       @Select("select * from role r, user_role " +
               "ur where r.id = ur.rid and ur.uid = #{id}")
       List<Role> findRolesByUserId(Integer id);

MyBatis缓存

一级缓存

缓存刷新

刷新缓存是清空这个SqlSession的所有缓存,不单单是某个键

见一级缓存流程图
源码分析
底层是不是HashMap

是,PerpetualCache

private Map<Object, Object> cache = new HashMap();
什么时候创建

查询时创建

流程什么样子
  1. 查询数据时,先根据分页、参数、namespace.id、环境等生成CacheKey
  2. 根据CacheKey查询一级缓存中有没有数据
  3. 有,返回对应数据
  4. 没有,查询数据,并保存到一级缓存中
入口:sqlSession

二级缓存

原理

二级缓存和一级缓存原理一样,第一次查询,会将数据存入缓存中,然后第二次查询则会直接去缓存中取出。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中

开启

二级缓存需要手动开启

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

IUserMapper.xml

<cache></cache>

注解 IUserMapper.xml

@CacheNamespace
public interface IUserMapper

实体类实现Serializable序列化接口

public class User implements Serializable
useCache

设置指定查询是否禁用二级缓存

xml文件

<select id="selectUserByUserId" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
select * from user where id=#{id}
</select>

注解

    @Options(useCache = false)
   @Select("select * from user where id = #{id}")
   User findUserById(Integer id);
flushCashe

在mapper的同一个namespace中,如果有其它的insert、update、delete操作数据后需要刷新缓存,如果不刷新,则会出现脏读。 flushCache默认为true,改为false则不会刷新

xml文件

<select id="selectUserByUserId" flushCache="true" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
select * from user where id=#{id}
</select>

注解

    @Options(useCache = false,flushCache = Options.FlushCachePolicy.FALSE)
   @Select("select * from user where id = #{id}")
   User findUserById(Integer id);
测试1
  • 在第一个SqlSession中,查询出user对象,此时发送了SQL语句;
  • student更改了user属性
  • SqlSession再次查询出user1对象,此时不发送SQL语句,日志中打印了[Cache Hit Ratio],代表二级缓存使用了,但是没有命中,因为一级缓存先作用了
  • 由于是一级缓存,因此两个对象是相通的
  • 调用了sqlSession.close(),此时将数据序列化并保持到二级缓存中
测试2
  • 先创建一个sqlSession.close()对象;
  • 查询出user2对象,直接从二级缓存中拿了数据,因此没有发送SQL语句,此时查了3个对象,但只有一个命中因此命中率是1/3=0.3333;
  • 查询出user3对象,直接从二级缓存中拿了数据,因此没有发送SQL语句,此时查了4个对象,但只有一个命中,因此命中率是2/4=0.5
  • 由于readOnly="true",因此user2和user3都是反序列化得到的,为不同的实例

二级缓存整合redis

自带的二级缓存
redis二级缓存
使用redis二级缓存
  • 导入依赖
 <dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
  • 配置文件IUserMapper 注解@CacheNamespace(implementation = RedisCache.class)
    public interface IUserMapperIUserMapper xml<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lagou.mapper.IUserMapper">
    <cache type="org.mybatis.caches.redis.RedisCache" />
    <select id="findAll" resultType="com.lagou.pojo.User" useCache="true">
    select * from user
    </select>reids.propertieshost=129.204.13.142
    port=6380
    connectionTimeout=5000
    password=
    database=0
  • 测试代码    @Test
       public void secondLevelCacheRedis(){
           SqlSession sqlSession1 = factory.openSession();
           SqlSession sqlSession2 = factory.openSession();
           SqlSession sqlSession3 = factory.openSession();

           IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
           IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
           IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);

           User user1 = mapper1.findUserById(1);
           sqlSession1.close();//清空一级缓存
    //
    //       User user = new User();
    //       user.setId(1);
    //       user.setName("张三三");
    //       mapper3.updateUser(user);
    //       sqlSession3.commit();

           User user2 = mapper2.findUserById(1);
           sqlSession2.close();
           System.out.println(user1 == user2);

      }

redis二级缓存源码分析

自带的二级缓存
redis二级缓存

在不同的服务器之间,使用第三方缓存框架,将缓存都放到这个第三方框架中,无论有多少台服务器,都能从缓存中读取数据

使用redis二级缓存
  • 导入依赖 <dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
    </dependency>
  • 配置文件IUserMapper注解@CacheNamespace(implementation = RedisCache.class)
    public interface IUserMapperxml<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lagou.mapper.IUserMapper">
    <cache type="org.mybatis.caches.redis.RedisCache" />
    <select id="findAll" resultType="com.lagou.pojo.User" useCache="true">
    select * from user
    </select>reids.propertieshost=129.204.13.142
    port=6380
    connectionTimeout=5000
    password=
    database=0
  • 测试代码    @Test
       public void secondLevelCacheRedis(){
           SqlSession sqlSession1 = factory.openSession();
           SqlSession sqlSession2 = factory.openSession();
           SqlSession sqlSession3 = factory.openSession();

           IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
           IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
           IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);

           User user1 = mapper1.findUserById(1);
           sqlSession1.close();//清空一级缓存
    //
    //       User user = new User();
    //       user.setId(1);
    //       user.setName("张三三");
    //       mapper3.updateUser(user);
    //       sqlSession3.commit();

           User user2 = mapper2.findUserById(1);
           sqlSession2.close();
           System.out.println(user1 == user2);

      }
redis二级缓存源码分析

redis的数据结构使用了hash

key

569531815:1495613170:cn.wxrwcz.mapper.IUserMapper.findUserById:0:2147483647:select * from user where id = ?:1:development

MyBatis插件

一般情况下,开源框架都会提供插件或者其他形式的拓展点,供开发者自行拓展。我们可以基于mybatis插件机制实现分页、分表、监控等功能,由于插件和业务无关,业务也无法感知插件的存在,因此可以无感植入插件,在无形中增强功能

好处

  • 开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作
  • 增加了框架的灵活性

mybatis四个组件

Executor

执行器Executor(update、query、commit、rollback等方法)

StatementHandler

SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法)

ParameterHandler

参数处理器ParameterHandler(getParameterObject、setParameters方法)

ResultSetHandler

结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)

原理

  1. 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
  2. 获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用interceptor.plugin(target);返回target包装后的对象
  3. 插件机制,可以使用插件为目标对象创建一个代理对象;AOP(面向切面)我们的插件可以为四大对象创建出来代理对象,代理对象就可以拦截到四大对象的每一次执行

使用

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object object, BoundSql sql, InterceptorChain interceptorChain){
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
parameterHandler = (ParameterHandler)
interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

interceptorChain保存了所有拦截器(interceptors),是mybatis初始化时创建的,调用拦截器链中的拦截器依次对目标拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四大对象,返回的target是被重重代理后的对象

拦截Executor的query方法
@Intercepts({
      @Signature(type = Executor.class,
              method = "query",
              args = {MappedStatement.class,Object.class, RowBounds.class,
                      ResultHandler.class
      }
)})
public class ExamplePlugin implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
      return null;
  }

  @Override
  public Object plugin(Object o) {
      return null;
  }

  @Override
  public void setProperties(Properties properties) {

  }
}

sqlMapConfig.xml

    <plugins>
      <plugin interceptor="cn.wxrwcz.plugins.ExamplePlugin">
      </plugin>
  </plugins>

MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中,待准备工作完成时,MyBatis就会处于就绪状态,执行SQL时,需要先通过DefaultSqlSessionFactory创建SqlSession。Executor实例会在创建SqlSession的过程中被创建,Executor实例创建完毕后,MyBatis会通过JDK动态代理为实例生成代理类,这样插件逻辑即可在Executor相关方法被调用前执行

自定义mybatis插件

mybatis插件接口-Interceptor
  • Intercept方法,插件的核心方法
  • plugin方法,生成target的代理对象
  • setProperties方法,传递插件所需参数
自定义插件代码

ExamplePlugin.java

package cn.wxrwcz.plugins;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
       @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class,Integer.class
      }
)})
public class ExamplePlugin implements Interceptor {
   /*
   拦截方法,只要被拦截的目标对象方法被执行时,每次都会运行intercetp
    */
   @Override
   public Object intercept(Invocation invocation) throws Throwable {
       System.out.println("对方法增强");
       return invocation.proceed();//原方法执行
  }
   /*
   主要为了把当前的拦截器生成的代理存到拦截器中
    */
   @Override
   public Object plugin(Object o) {
       System.out.println("代理存到拦截器链");
       Object wrap = Plugin.wrap(o, this);
       return wrap;
  }
   /*
   获取配置文件参数
    */
   @Override
   public void setProperties(Properties properties) {
       System.out.println("获取到配置文件的参数是" + properties);
  }
}

sqlMapConfig.xml

    <typeAliases>
       <!--给单独的实体起别名-->
       <!-- <typeAlias type="com.lagou.pojo.User" alias="user"></typeAlias>-->
       <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
       <package name="cn.wxrwcz.pojo"/>
   </typeAliases>
   <plugins>
       <plugin interceptor="cn.wxrwcz.plugins.ExamplePlugin">
           <property name="name" value="tom"/>
       </plugin>
   </plugins>
执行插件逻辑

Plugin实现了InvocationHandler接口,因此他的invoke方法会拦截所有方法的调用,invoke方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。plugin逻辑如下

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       try {
           Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
           return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
      } catch (Exception var5) {
           throw ExceptionUtil.unwrapThrowable(var5);
      }
  }

首先,invoke方法会检测被拦截方法是否配置在插件的@Signature注解中,若是,则执行插件逻辑,否则执行被拦截方法。插件逻辑封装在intercept中,该方法的参数类型为Invocation。Invocation主要用于存储目标类,方法以及方法参数列表代码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Invocation {
   private final Object target;
   private final Method method;
   private final Object[] args;

   public Invocation(Object target, Method method, Object[] args) {
       this.target = target;
       this.method = method;
       this.args = args;
  }

   public Object getTarget() {
       return this.target;
  }

   public Method getMethod() {
       return this.method;
  }

   public Object[] getArgs() {
       return this.args;
  }

   public Object proceed() throws InvocationTargetException, IllegalAccessException {
       return this.method.invoke(this.target, this.args);
  }
}

常用的第三方插件

pageHelper分页插件

开发步骤

  • 导入pageHelper坐标<!--分页助手-->
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
    </dependency>
    <dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
    </dependency>
  • 在mybatis核心配置文件中配置pageHelper插件<!--注意:分页助手的插件,配置在通用mapper之前-->
    <plugin interceptor="com.github.pagehelper.PageHelper">
    <!--指定方言-->
    <property name="dialect" value="mysql"/>
    </plugin>
  • 测试分页数据获取    @Test
       public void testPageHelper(){
           PageHelper.startPage(1,2);
           List<User> select = userMapper.selectAll();
           for(User user : select){
               System.out.println(user);
          }
           PageInfo<User> pageInfo = new PageInfo<>(select);
           System.out.println("总条数" + pageInfo.getTotal());
           System.out.println("总页数" + pageInfo.getPages());
           System.out.println("当前页" + pageInfo.getPageNum());
           System.out.println("每页显示条数" + pageInfo.getPageSize());
      }
通用mapper
  • 导入通用mapper坐标<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>3.1.2</version>
    </dependency
  • MyBatis配置文件中完成配置    <plugins>
           <plugin interceptor="cn.wxrwcz.plugins.ExamplePlugin">
               <property name="name" value="tom"/>
           </plugin>
           <plugin interceptor="com.github.pagehelper.PageHelper">
               <property name="dialect"
                         value="mysql"/>
           </plugin>
           <plugin
                   interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
               <!--通用mapper接口,多个通用接口用,号隔开-->
               <property name="mappers"
                         value="tk.mybatis.mapper.common.Mapper"/>
           </plugin>
       </plugins>
  • 实体类设置主键@Table(name = "user")
    public class User implements Serializable {
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Integer id;
       //省略代码
    }@Transient 表示该属性不是表中的列
  • 定义通用mapperIUserMapper.javapublic interface IUserMapper extends Mapper<User>{}
  • 测试    @Test
       public void test8(){
           System.out.println(userMapper.selectByPrimaryKey(1));
           System.out.println(userMapper.selectCount(new User()));
           Example example = new Example(User.class);
           example.createCriteria().andEqualTo("id",1);
           userMapper.selectByExample(example).stream().forEach(System.out::println);
      }