Mybatis-mappers

select

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

类似于PreparedStatement

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

这个#{id}当然就是?占位符了

insert, update, delete

这三个类似

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

insert复杂一些,要看已存在的数据

所以insert标签里有属性useGeneratedKeyskeyProperty,用来确定是否用自动生成的属性,和自动生成的属性是什么

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

这样会自动生成id,只能在一些支持自动生成的数据库中使用,比如mysqlsql server
一些不支持的,也可以自定义一些自动生成方法:

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

这里的selectKey标签由于order=before所以会在insert语句之前执行,keyProperty=id所以select得到的数据会赋值给id并且传入到下面的insert语句中

sql

就是一句sql语句的一部分,可以复用

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

refid这个属性中,也可以赋值一些property,如${include_target}

parameters

就像之前的#{id}一样,这些就是参数,这些参数在Java程序中使用时传入进去

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

这里的parameterType设置为int
这种原始的和简单的数据类型int/String没有相关的属性,传入的时候会直接完全替代相应的形参。
而使用一些复杂的数据类型的时候就不一样了:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

这样子会把User类的id, username, password 属性找到,并且替代这些形参

形参也可以额外添加属性

#{property,javaType=int,jdbcType=NUMERIC}

javaType大部分都可以直接从参数类型确定,除非这个参数类型是HashMap,这样子需要手动添加javaType来确保TypeHandler是正确的。
可以自定义TypeHandler

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

遇到数字类型的还可以添加小数点位置:

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

还有个属性叫modemode=[IN | OUT | INOUT],如果是OUT | INOUT的话,那么参数输出的值会发生变化,这个我也不懂,估计是说javaType会转化成jdbcType吧。大致就是这么写的。
mode=OUT | INOUT 而且jdbcType=CURSOR,这时候就必须定义resultMap来匹配resultSet

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

mybatis还有structs等高级等数据类型,但是必须设置jdbcTypeName

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

不过绝大多数时间不需要这么多属性,最多给一些nullable字段设置个jdbcType

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

String Substitution

${columnName}作为形参,可以减少代码

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

这里${column}会被直接替代,然后#{value}就预备了

User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

Result Maps

一次查询,会把查询结果result set返回,通过resultMapresult set的字段一一映射到Javabean中。

这是一个完整的resultMap

<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

resultMap的各个标签

  • constructor - 用来把resultMap的返回结果插入到对应Java classconstructor中。
    • idArg - id参数
    • arg - 普通参数
  • result - 普通的字段或者JavaBean属性
  • id - id型的result,将result定义为id 会加快运行速度
  • association - 一个复杂的type,比如这里author显然是一个复杂的类型,他表示另外一个表/Javabean。这个标签里可以有很多个result
    • 嵌套result mappings - 一个association本身就是一个resultMap,或者可以指向一个resultMap
  • collection - 复杂type的集合
    • 他也是嵌套result mappings
  • discriminator - 辨别器,可以理解为,也是一个resultMap,但他本身在Javabean和数据库里面的数据类型都不是引用类型,我们通过这个字段值的不同来确定不同的resultMap
    • case - 是一个根据某些值的变化而变化的resultMap
      • 这个case也是一个嵌套result mappings

column就是查询出来的结果的字段名,property就是Type: Java class中的属性,resultMap就是将查询出来的column一个个对应传参给property,创建一个Javabean实例。

resultMap的属性

  • id - 这个resultMap的id,供其他引用
  • type - Java class路径或type alias,表示对应的Java class
  • autoMapping - 自动匹配字段名,该属性默认是unset

不要一次性建造一个像上面一样那么大的resultMap,这样子容易出错,循序渐进的写,并且边写边进行单元测试

id&result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

这两个都是表示表格的一个字段,简单的数据类型的字段。区别就是id可以把这个result标记为id在对象实例中进行比较。

属性

  • property - 字段名的别名,如果该别名有相同的Javabean属性,那么就会用这个属性;否则会在字段中去寻找对应的名字。两种情况都可以用带.来连接的字段名,简单的如username,复杂的如client.account.username
  • column - 数据库里的字段名,或者他的昵称
  • javaType - Java的类名,或昵称。如果对应的是一个Javabean,mybatis一般会自动解决这个参数;如果对应的是一个HashMap,那就要明确的指定这个Javatype
  • jdbcType - 只有在对nullable的字段进行增删改的时候在需要指定这个参数
  • typeHandler - 顾名思义呗。可以是全路径也可以是昵称。

constructor

跟Java里的constructor差不多

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>

name使这些参数可以按照任意的顺序排序
constructor里面的标签的属性和resultid差不多,有这些多出来的:

  • select - 这是其他的语句的id,会得到复杂的type。这个标签的column属性得到的value会传递给这个相应的select语句,得到的complex type就是这个字段了。
    比如select * from author where id=#{id},这个#{id}就是通过这个标签的column属性在调用的时候被传递实参,进行select,返回的是一个author对象,这个author对象就是这个标签的字段了。
  • resultMap - 这是一个resultMap的id,用来管理嵌套的返回值。比如上面的select就可以用他来管理返回的author对象的格式。有了这个可以将多表查询的结果放在一个resultMap里。
  • name - 使这些参数可以按照任意的顺序排序

association

<association property="author" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

association标签用来解决has-one关系
可以通过以下两个方法来得到association

  • nested select - 嵌套的select,执行一个sql,返回需要的复杂类型type
  • nested results - 嵌套的result,通过用嵌套的result mappings来处理重复的多表查询结果

nested select for association

参数

  • column - 在这里的column会被作为参数传入到嵌套的语句中,比如select, 可以设置多个columncolumn="{prop1=col1,prop2=col2}"
  • select - 一个select标签的ID,接收column的实参进行操作,
  • fetchType - optional。 lazy | eager
<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

author这个字段是selectAuthor的结果,其他的字段会自动导入。

问题就是会害怕N+1 problem
由于mybatis可以lazy load这些查询语句,所以效率还可以,但是如果我们要查询一堆数据,然后马上对这一堆数据进行遍历获取嵌套的数据的话,就要引用所有的这些lazy loads了,就会很慢
所以有下面的方法:

nested results for association

属性:

  • resultMap - 一个resultMap的ID
  • columnPrefix - 为了防止字段名的重复,加个前缀
  • notNullColumn - 默认情况下,嵌套result至少有一个字段有值的时候,才会创建这个嵌套result。这个notNullColumn,可以指定确定的某个字段要有值,才会创建嵌套result。可以设置多个字段。
  • autoMapping - 是否将result与这个字段自动匹配。
<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>

这里设置了昵称,保证查出来的每个字段名都有不重复的,确定的名字。这样子我们可以轻松的匹配这些结果:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" resultMap="authorResult" />
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

这里的column就是我们查询出来的结果,我们用Javabean的每一个属性来匹配,这样子就可以得到Java object instance了。

当然我们可以给association加上属性columnPrefix="author_",这样子就不需要在另一个resultMap里每个标签都加上了。

当然association可以不用在外面写resultMap,直接在association标签里写:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>

这样子就是不能复用。

multiple resultsets for association

在有些数据库(比如mysql)中,可以设置stored procedure,一次返回多个结果集。这样子可以通过访问一次数据库,返回多个相关数据,而不需要使用join

  • column - 本表所返回的结果集中,外键的字段名
  • foreignColumn - 本表的外键所对应的另外那个表的主键
  • resultSet - 根据resultSets定义的名字,得到相应的结果集。
  • resultSets - 用来给返回的结果集定义名字。用,分隔,不要加空格!

我们这样子定义一个stored procedure

CREATE DEFINER=`root`@`localhost` PROCEDURE `getUsersAndAccounts`(idx int)
BEGIN
select * from account where user_id=idx;
select * from user where id=idx;
END

以上的procedure返回两个结果集,一个是account表的数据,一个是user表的数据。

我们通过这个select标签调用这个procedure,并且命名两个resultSets

<select id="selectua" resultMap="ua" statementType="CALLABLE" resultSets="accounts,users">
        {call getUsersAndAccounts(#{id, jdbcType=INTEGER, mode=IN})}
    </select>

上面的resultSets表示将这两个结果集分别命名为accounts和users,以后使用到这个select的时候,就可以用resultSet=accounts/users来分别得到这两个结果集了。

注意没有加空格,要是加空格了的话,引用的时候也要加空格。。。
名字无所谓,到时候resultMap里使用的时候就是根据顺序引用的

以上的select结果集由ua这个resultMap来处理

<resultMap id="ua" type="Account">
        <id column="id" property="id"/>
        <result column="balance" property="balance"/>
        <association property="user" resultSet="users" javaType="User" column="user_id" foreignColumn="id">
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="email" property="email"/>
        </association>
    </resultMap>

这里的association用了users这个resultSet,在select中定义的是第二个,所以得到的也就是第二个resultSet
columnforeignColumn要同时存在。这里其实不需要,因为只剩下一个字段了。

collection

用来处理has many情况。
创建一个Group,在Javabean里有private List<User> users
一个group有多个user
user里加上private int group_id

<resultMap id="gu" type="Group">
        <id property="id" column="id"/>
        <collection property="users" javaType="ArrayList" column="id" ofType="User" select="selectUsersFromGroup"/>
    </resultMap>

这里的collection可以这么理解:

A collection of users in an ArrayList of type User

javaType可以省略,因为懂得都懂

这里就是select属性所指向的select标签:

<select id="selectUsersFromGroup" resultType="User">
        select * from user where group_id=#{id}
    </select>

通过这句select,在Java中调用:

<select id="selectGroup" resultMap="gu">
        select * from `group` where id=#{id}
    </select>

nested results for collection

association一样,多了个ofType罢了。不写了

multiple resultSets for collection

也一样

discriminator

我就叫他 区分器
作用类似于Java里的switch

<resultMap id="vehiclers" type="Vehicle">
        <id column="id" property="id"/>
        <result column="speed" property="speed"/>
        <result column="year" property="year"/>
        <result column="make" property="make"/>
        <discriminator javaType="int" column="type">
            <case value="1" resultMap="car"/>
            <case value="2" resultMap="truck"/>
            <case value="3" resultMap="suv"/>
        </discriminator>
    </resultMap>

一个discriminator标签里有columnJavaType属性。用来告诉mybatis去resultset里的哪一个字段去找。
以上就是如果vehicle_type字段如果是1,那么就以carResult来映射这一行数据的所有的字段,2 就truckResult。。。
这样子的话其他的column就不能被映射了。就像这样:

Car{color:yellow,doorCount:4}

可以用

<resultMap id="car" type="Car" extends="vehiclers">
    <result column="color" property="color"/>
    <result column="door_count" property="doorCount"/>
</resultMap>

这个extends表示这个resultMap继承了vehiclers,那么在vehiclers的字段也可以在car里面映射了。
这样子的话就要在Car这个Javabean中也extends Vehicle,这样子就得到了一个完整的Car类型,为了可以在Car里面输出所有的属性,所以Vehicle的属性最好不是private,这样子Car才能够通过for (Field field : this.getClass().getFields())来得到所有的属性(包括vehiclecar的),然后来重写toString方法。然后就是这样了:

Car{color:yellow,doorCount:4,id:1,speed:120,year:2020,make:china,type:1}

auto-mapping

就是会自动把一些简单的column映射给property,默认是无视大小写,通过设置mapUnderscoreToCamelCase=True也可以映射一些下划线。

auto-mapping有三个等级

  • NONE - 不进行auto-mapping
  • PARTIAL - 对本身进行auto-mapping但是不对嵌套的resultMap进行
  • FULL - 对全部都进行

默认是PARTIAL
最好不要设置为FULL,因为:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id,
    B.title,
    A.username,
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
  <association property="author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <result property="username" column="author_username"/>
</resultMap>

在这种情况下,result set里有id这个column,在authorResult里也有个id,所以authorid也会被映射

cache

默认只会缓存本地会话local session,只要在map file添加这一句:

<cache/>

作用是(默认的):

  • 所有的select缓存
  • 所有的增删改会删除缓存
  • 会使用least recently used(LRU)算法来删掉老的
  • 不会在任何类型的时间相关的日程表上删除,就是说不会每隔一段时间删除
  • 会缓存1024个list 或 object的指针
  • 缓存是读/写类型的缓存,就是说我们得到的对象不是共享的,可以被安全的修改,不会因为其他的进程或方法的调用而产生影响。

cache只对声明了这个标签的map file有效,如果有Java API的话,要对相应的interface添加注解@CacheNamespaceRef

这些属性都可以修改:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这里flushInterval=60000表示每一分钟删除一次,size=512表示缓存最多512个,readOnly=true表示返回的对象是只读的,这样子要修改的话会导致和其他的进程冲突。
eviction

  • LRU(默认) - least recently used: 把好久没用的删掉
  • FIFO - first in first out: 先进先出
  • SOFT - soft reference: 根据垃圾收集器和soft reference的规则来删除
  • WEAK - weak reference: 更加激进的根据垃圾收集器和weak reference的规则来删除

flushInterval

  • 默认是unset,就是不会根据时间来删除缓存,只根据增删改等操作

size

  • 默认1024

readOnly

  • 如果是true,那么会返回同一个实例给调用者,所以修改的话会产生冲突,但是会快一点
  • 如果是false,那么会返回copy给调用者,可以修改,不产生冲突,但是会慢一点

二级缓存是transactional,会话级别的。就是一个sqlSessioncommit的时候就更新。或者一个sqlSession在没有执行增删改 with flushCache=true的时候更新

using a custom cache

将自己写的或者是第三方的cache类放到这里:

<cache type="com.domain.something.MyCustomCache"/>

实现这样子的接口org.apache.ibatis.cache.Cache

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

要设置cache,只需要把Javabean 的 public属性添加到实现类,然后把属性传到cache标签中:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

也可以设置placeholder(默认值)比如${cache.file}来替代在configuration里定义的属性

现在mybatis支持initialization,会在他设置好所有的属性后执行,要实现这个接口org.apache.ibatis.builder.InitializingObject

public interface InitializingObject {
  void initialize() throws Exception;
}

使用自定义的cache的时候,cache标签的属性设置就不能用了:eviction, flushInterval, size, readOnly
cache的作用域是一个mapper文件,如果要让里面的某一部分不使用对应的cache设置的话:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

上面是默认设置,可以自己修改

cache-ref

我们设置cache的时候可以不用cache标签而用cache-ref标签:

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

这样子把另一个mapper的cache也同时设置到了自己的mapper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值