Mybatis学习笔记(一):Mybatis基础学习

Scroll Down

Mybatis框架的基础介绍

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

image-20200426112703807

Mybatis常用配置文件解析

configuration(配置)

properties属性

mybatis的配置文件可以引入外部的properties文件来进行赋值

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

类型别名(typeAliases)

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

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

而Mybatis也默认内置了一些别名,用于用户便利操作

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

类型处理器(typeHandlers)

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

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERICBYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERICSMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERICINTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERICBIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERICFLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERICDECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHARLONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

插件(plugins)

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

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

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

环境配置(environments)

image-20200426142603077

其中事务管理器分两种:

  • JDBC:使用JDBC的提交和回滚设置,依赖于从数据源得到的连接来管理事务作用域
  • MANGAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源类型:

  • UNPOOLED: 这个数据源的实现会每次请求时打开和关闭连接
  • POOLED:这个数据源的实现会每次请求时打开和关闭连接
  • JNDI:这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用

映射器(mappers)

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉 MyBatis 去哪里找映射文件

动态Sql

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

常用的动态sql有如下几种:

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

if 类型的动态sql

<select id="findByCondition" parameterType="user" resultType="user">
    select * from User
        <where>
            <if test="id!=0">
            	and id=#{id}
            </if>
            <if test="username!=null">
            	and username=#{username}
       		</if>
    	</where>
</select>

choose、when、otherwise

<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、set

使用where标签可以动态的舍去第一个and

<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 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

foreach

对集合进行遍历

其中item就是循环的每个参数,index就是索引值,collection就是集合的参数,open就是传入循环开始的参数,close就是循环结束的参数,separator就是使用对应标识符来切割

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

抽取通用sql

抽取通用的sql出来
<sql id="Base_Column_List">
   id, corp_id, org_id, bill_code, borrow_begin_date, borrow_end_date,
   reason, department, borrower, contact, apply_date, applyer_id,
   status, return_date, comments, create_time,busi_type,borrow_type,k2_id,k2_form_url,k2_status, ts
</sql>
使用include进行引用通用的sql
<select id="queryRecBorrowList" resultMap="BaseResultMap" parameterType="hashmap">
		select
		<include refid="Base_Column_List" />
		from rec_borrow
</select>

Mybatis的复杂映射开发

用户与订单是一对多的关系,订单与用户是一对一的关系.通过订单查询出关联的用户

声明一个用户订单实体

public class Order {

    private Integer id;
    private String orderTime;
    private Double total;
    // 表明该订单属于哪个用户
    private User user;
}

一个用户实体

public class User implements Serializable {
    private Integer id;
    private String username;
        //表示用户关联的订单
    private List<Order> orderList = new ArrayList<>();
    //表示用户关联的角色
    private List<Role> roleList = new ArrayList<>();
}

XML方式

一对一查询

这里如果使用XML的方式,需要在XML中使用association的配置,javaType填写具体的实体类,包含则是User实体中的参数

<resultMap id="orderResultMap" type="com.lagou.pojo.Order">
    <result column="id" property="id"/>
    <result column="orderTime" property="orderTime"/>
    <result column="total" property="total"/>
    <association property="user" javaType="com.lagou.pojo.User">
        <result property="id" column="uid"/>
        <result property="username" column="username"/>
    </association>
</resultMap>

<select id="findOrderAndUser" resultMap="orderResultMap">
    SELECT
        orders.id,
        orders.ordertime,
        orders.total,
        orders.uid,
        USER.username
    FROM
        orders
            LEFT JOIN USER ON orders.uid = USER.id
</select>

最终结果为:
image-20200426163609891

一对多查询

需要使用collection来进行一对多的查询,其中与一对多的association不同,需要使用的是ofType参数

<resultMap id="userMap" type="com.lagou.pojo.User">
        <result property="id" column="uid"></result>
        <result property="username" column="username"></result>
        <collection property="orderList" ofType="com.lagou.pojo.Order">
            <result property="id" column="id"></result>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="userMap">
        select * from user u left join orders o on u.id = o.uid
    </select>

多对多查询

多对多的查询从单方向的数据角度来看,其实还是一对多,查询方式与一对多相同,都是使用collection标签

注解方式

Mybatis也支持注解查询,可以直接使用注解进行查询,减少mapper文件的编写,常用的mybatis注解有

  • @Insert 新增语句
  • @Update 更新语句
  • @Delete 删除语句
  • @Select 查询语句
  • @Result:查询结果集的封装
  • @Results:与Result一起使用,封装多个结果集
  • @One:实现一对一结果集封装
  • @Many:实现一对多的结果集封装
一对一方式

使用注解一对一的方式进行查询,直接引用另一个查询SQL

    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "orderTime",column = "orderTime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user",column = "uid",javaType = User.class,
                    one=@One(select = "com.lagou.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")

结果为:

image-20200426171535786

使用Left Join方式联查两个表

    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "orderTime",column = "orderTime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user.id",column = "uid"),
            @Result(property = "user.username",column = "username")
    })
    @Select("SELECT\n" +
            "            orders.id,\n" +
            "            orders.ordertime,\n" +
            "            orders.total,\n" +
            "            orders.uid,\n" +
            "            USER.username\n" +
            "        FROM\n" +
            "            orders\n" +
            "                LEFT JOIN USER ON orders.uid = USER.id")
    public List<Order> findOrderAndUser2();

结果为:
image-20200426171856501

所以两种方式是等同的

一对多查询

使用many = @Many方式进行查询

    //查询所有用户、同时查询每个用户关联的订单信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "orderList",column = "id",javaType = List.class,
                many=@Many(select = "com.lagou.mapper.IOrderMapper.findOrderByUid"))
    })
    public List<User> findAll();

结果为:

image-20200426172126432

多对多查询

多对多与一对多查询类似,只是查询的时候需要联查中间表才能查到最终目标信息,使用方式与一对多是相同的,都是many=@Many方式