SpringBoot集成MySQL - MyBatis 注解方式

arcstack约 3071 字大约 10 分钟

SpringBoot集成MySQL - MyBatis 注解方式

上文主要介绍了Spring集成MyBatis访问MySQL,采用的是XML配置方式;我们知道除了XML配置方式,MyBatis还支持注解方式。本文主要介绍SpringBoot+MyBatis注解方式。@pdai

准备知识

MyBatis的相关知识体系。

具体可以参考 SpringBoot集成MySQL - MyBatis XML方式

在构建知识体系时:我们最重要的目标并不是如何使用注解方式,而是要理解:

  • 对于有原有xml方式改为注解方式(一定要有对比),如何写? * 基本的CRUD怎么用注解写?

  • 对于复杂的动态SQL如何写?

  • 对于表关联的如何写?

  • 为什么xml方式依然是比注解方式使用广泛? * xml方式和注解方式混合使用?

  • 注解方式是如何工作的呢?

基本查改删操作

我们从最基本的增删改操作开始,对比xml方式进行理解。

查询操作

@Results和@Result注解

对于xml配置查询时定义的ResultMap, 在注解中如何定义呢?

    <resultMap type="tech.pdai.springboot.mysql57.mybatis.xml.entity.User" id="UserResult1">
      <id property="id" column="id" />
      <result property="userName" column="user_name" />
      <result property="password" column="password" />
      <result property="email" column="email" />
      <result property="phoneNumber" column="phone_number" />
      <result property="description" column="description" />
      <result property="createTime" column="create_time" />
      <result property="updateTime" column="update_time" />
    </resultMap>

使用注解方式,用@Results注解对应

    @Results(
            id = "UserResult1",
            value = {
                    @Result(id = true, property = "id", column = "id"),
                    @Result(property = "userName", column = "user_name"),
                    @Result(property = "password", column = "password"),
                    @Result(property = "email", column = "email"),
                    @Result(property = "phoneNumber", column = "phone_number"),
                    @Result(property = "description", column = "description"),
                    @Result(property = "createTime", column = "create_time"),
                    @Result(property = "updateTime", column = "update_time")
            }
    )

@Select和@Param注解

对于查询,用@Select注解;对于参数, 使用@Param注解

所以根据用户ID查询用户,使用注解方式写法如下:

    @Results(
            id = "UserResult1",
            value = {
                    @Result(id = true, property = "id", column = "id"),
                    @Result(property = "userName", column = "user_name"),
                    @Result(property = "password", column = "password"),
                    @Result(property = "email", column = "email"),
                    @Result(property = "phoneNumber", column = "phone_number"),
                    @Result(property = "description", column = "description"),
                    @Result(property = "createTime", column = "create_time"),
                    @Result(property = "updateTime", column = "update_time")
            }
    )
    @Select("select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time from tb_user u where id = #{id}")
    User findById1(@Param("id") Long id);

@ResultMap注解

xml配置查询时定义的ResultMap是可以复用的,那么我们上面通过@Results定义在某个方法上的,如何复用呢?

比如查询所有用户返回用户实体@Results是和查询单个用户一致的,那么我们可以通过@ResultMap指定返回值对应关系

    @ResultMap("UserResult1")
    @Select("select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time from tb_user u")
    User findAll1();

由此你可以猜到,@ResultMap定义在哪个方法上并没有什么关系,因为它会被优先通过注解解析为数据库字段与Java字段的映射关系。

表关联查询

用户和角色存在着一对多的关系,上面的查询只是查询了用户的基本信息,如何关联查询(查询用户同时返回角色信息)呢?

我们看下xml配置方式是如何做到的?

    <resultMap type="tech.pdai.springboot.mysql57.mybatis.xml.entity.User" id="UserResult">
      <id property="id" column="id" />
      <result property="userName" column="user_name" />
      <result property="password" column="password" />
      <result property="email" column="email" />
      <result property="phoneNumber" column="phone_number" />
      <result property="description" column="description" />
      <result property="createTime" column="create_time" />
      <result property="updateTime" column="update_time" />
      <collection property="roles" ofType="tech.pdai.springboot.mysql57.mybatis.xml.entity.Role">
        <result property="id" column="id" />
        <result property="name" column="name" />
        <result property="roleKey" column="role_key" />
        <result property="description" column="description" />
        <result property="createTime" column="create_time" />
        <result property="updateTime" column="update_time" />
      </collection>
    </resultMap>

使用注解方式, 可以通过@Results+@Many注解

    @Results(
            id = "UserResult",
            value = {
                    @Result(id = true, property = "id", column = "id"),
                    @Result(property = "userName", column = "user_name"),
                    @Result(property = "password", column = "password"),
                    @Result(property = "email", column = "email"),
                    @Result(property = "phoneNumber", column = "phone_number"),
                    @Result(property = "description", column = "description"),
                    @Result(property = "createTime", column = "create_time"),
                    @Result(property = "updateTime", column = "update_time"),
                    @Result(property = "roles", column = "id", many = @Many(select = "tech.pdai.springboot.mysql57.mybatis.anno.dao.IRoleDao.findRoleByUserId", fetchType = FetchType.EAGER))
            }
    )

其中findRoleByUserId是通过user表中的id查找Role, 具体方法如下

    @Results(
                id = "RoleResult",
                value = {
                        @Result(id = true, property = "id", column = "id"),
                        @Result(property = "name", column = "name"),
                        @Result(property = "roleKey", column = "role_key"),
                        @Result(property = "description", column = "description"),
                        @Result(property = "createTime", column = "create_time"),
                        @Result(property = "updateTime", column = "update_time")
                }
        )
        @Select("select r.id, r.name, r.role_key, r.description, r.create_time, r.update_time from tb_role r, tb_user_role ur where r.id = ur.user_id and ur.user_id = #{userId}")
        List<Role> findRoleByUserId(Long userId);

对于一对一的可以使用@One注解。

插入操作

涉及插入操作的主要注解有:@Insert, @SelectKey等。

@Insert注解

对于插入操作,在xml配置可以定义为:

    <insert id="save" parameterType="tech.pdai.springboot.mysql57.mybatis.xml.entity.User" useGeneratedKeys="true" keyProperty="id">
     		insert into tb_user(
     			<if test="userName != null and userName != ''">user_name,</if>
    			<if test="password != null and password != ''">password,</if>
     			<if test="email != null and email != ''">email,</if>
    			<if test="phoneNumber != null and phoneNumber != ''">phone_number,</if>
     			<if test="description != null and description != ''">description,</if>
     			create_time,
    			update_time
     		)values(
     			<if test="userName != null and userName != ''">#{userName},</if>
    			<if test="password != null and password != ''">#{password},</if>
     			<if test="email != null and email != ''">#{email},</if>
     			<if test="phoneNumber != null and phoneNumber != ''">#{phoneNumber},</if>
     			<if test="description != null and description != ''">#{description},</if>
     			sysdate(),
    			sysdate()
     		)
    	</insert>

特别是,这里通过<if>判断条件更新的情况应该如何在注解中写呢? 可以通过@Insert

    @Insert({"<script> ", "insert into tb_user(\n" +
            " <if test=\"userName != null and userName != ''\">user_name,</if>\n" +
            " <if test=\"password != null and password != ''\">password,</if>\n" +
            " <if test=\"email != null and email != ''\">email,</if>\n" +
            " <if test=\"phoneNumber != null and phoneNumber != ''\">phone_number,</if>\n" +
            " <if test=\"description != null and description != ''\">description,</if>\n" +
            " create_time,\n" +
            " update_time\n" +
            " )values(\n" +
            " <if test=\"userName != null and userName != ''\">#{userName},</if>\n" +
            " <if test=\"password != null and password != ''\">#{password},</if>\n" +
            " <if test=\"email != null and email != ''\">#{email},</if>\n" +
            " <if test=\"phoneNumber != null and phoneNumber != ''\">#{phoneNumber},</if>\n" +
            " <if test=\"description != null and description != ''\">#{description},</if>\n" +
            " sysdate(),\n" +
            " sysdate()\n" +
            " )", " </script>"})
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int save(User user);

返回Insert后实体的主键值

上述@Options(useGeneratedKeys = true, keyProperty = "id") 表示什么意思呢?

表示,如果数据库提供了自增列生成Key的方式(比如这里的id), 并且需要返回自增主键时,可以通过这种方式返回实体。 那么,如果id的自增不使用数据库自增主键时, 在xml中可以使用SelectKey:

    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>

对应着注解:

    @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyColumn = "id", keyProperty = "id", resultType = Long.class, before = false)

  • before = false, 相当于XML中的order="AFTRE",这是MySql数据库的配置。
  • before = true, 相当于XML中的order="BEFORE",这是Oracle数据库的配置。

注意事项:不同的数据库statement的值会不同,上面中的值适用于MySql数据库,使用其他类型的数据库时要注意修改。

更新操作

涉及更新操作的主要注解有:@Update等。

@Update 注解

对于xml的更新操作如下:

    <update id="update" parameterType="tech.pdai.springboot.mysql57.mybatis.xml.entity.User">
      update tb_user
      <set>
        <if test="userName != null and userName != ''">user_name = #{userName},</if>
        <if test="email != null and email != ''">email = #{email},</if>
        <if test="phoneNumber != null and phoneNumber != ''">phone_number = #{phoneNumber},</if>
        <if test="description != null and description != ''">description = #{description},</if>
        update_time = sysdate()
      </set>
      where id = #{id}
    </update>

    <update id="updatePassword" parameterType="tech.pdai.springboot.mysql57.mybatis.xml.entity.User">
      update tb_user
      <set>
        password = #{password}, update_time = sysdate()
      </set>
      where id = #{id}
    </update>

对应的注解写法如下:

    @Update({"update tb_user set password = #{password}, update_time = sysdate()", " where id = #{id}"})
    int updatePassword(User user);

    @Update({"<script> ", "update tb_user\n" +
            " <set>\n" +
            " <if test=\"userName != null and userName != ''\">user_name = #{userName},</if>\n" +
            " <if test=\"email != null and email != ''\">email = #{email},</if>\n" +
            " <if test=\"phoneNumber != null and phoneNumber != ''\">phone_number = #{phoneNumber},</if>\n" +
            " <if test=\"description != null and description != ''\">description = #{description},</if>\n" +
            " update_time = sysdate()\n" +
            " </set>\n" +
            " where id = #{id}", " </script>"})
    int update(User user);

删除操作

涉及删除操作的主要注解有:@Delete等。

@Delete 注解

对于xml的删除操作如下:

    <delete id="deleteById" parameterType="Long">
      delete from tb_user where id = #{id}
    </delete>

    <delete id="deleteByIds" parameterType="Long">
      delete from tb_user where id in
      <foreach collection="array" item="id" open="(" separator="," close=")">
        #{id}
          </foreach> 
    </delete>

对应的注解写法如下:

    @Delete("delete from tb_user where id = #{id}")
    int deleteById(Long id);

    @Delete({"<script> ", "delete from tb_user where id in\n" +
            "<foreach collection=\"array\" item=\"id\" open=\"(\" separator=\",\" close=\")\">\n" +
            "#{id}\n" +
            "</foreach>", " </script>"})
    int deleteByIds(Long[] ids);

Provider注解

其实你可以发现通过注解方式,对于有一些需要通过动态构建查询条件的操作是非常不方便的。MyBatis的作者们自然就想到了动态构建SQL,动态构建SQL的方式是配合@Provider注解来完成的。

MyBatis提供了4种Provider注解,分别是@SelectProvider、@InsertProvider、@UpdateProvider和@DeleteProvider。 这里以@SelectProvider为例来根据Id查询User:

  • 定义包含自定义生成的动态SQL的类,比如UserDaoProvider
    /** * @author pdai */
    public class UserDaoProvider {

        public String findById(final Long id) {
            SQL sql = new SQL();
            sql.SELECT("u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time");
            sql.FROM("tb_user u");
            sql.WHERE("id = " + id);
            return sql.toString();
        }
    }

  • 通过@SelectProvider注解关联到定义的类和方法
    @ResultMap("UserResult")
    @SelectProvider(type = UserDaoProvider.class, method = "findById")
    User findById2(Long id);

进一步理解

让我们通过几个问题,进一步理解MyBatis注解方式。

其它注解

  • @CacheNamespace :为给定的命名空间 (比如类) 配置缓存。对应xml中的<cache>

  • @CacheNamespaceRef :参照另外一个命名空间的缓存来使用。属性:value,应该是一个名空间的字 符串值(也就是类的完全限定名) 。对应xml中的<cacheRef>标签。

  • @ConstructorArgs :收集一组结果传递给一个劫夺对象的 构造方法。属性:value,是形式参数 的数组。

  • @Arg :单 独 的 构 造 方 法 参 数 , 是 ConstructorArgs 集合的一部分。属性: id,column,javaType,typeHandler。id 属性是布尔值, 来标识用于比较的属 性,和XML 元素相似。对应xml中的<arg>标签

  • @Case :单独实例的值和它对应的映射。属性: value,type,results。Results 属性是结 果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。对应xml中标签<case>

  • @TypeDiscriminator : 一组实例值被用来决定结果映射的表 现。 属性: column, javaType, jdbcType, typeHandler,cases。cases 属性就是实 例的数组。对应xml中标签<discriminator>

  • @Flush: 在MyBatis 3.3以上版本,可以通过此注解在Mapper接口中调用SqlSession#flushStatements()。

xml方式和注解方式融合

xml方式和注解方式是可以融合写的, 我们可以将复杂的SQL写在xml中 比如将resultMap定义在xml中

    <resultMap type="tech.pdai.springboot.mysql57.mybatis.xml.entity.User" id="UserResult3">
      <id property="id" column="id" />
      <result property="userName" column="user_name" />
      <result property="password" column="password" />
      <result property="email" column="email" />
      <result property="phoneNumber" column="phone_number" />
      <result property="description" column="description" />
      <result property="createTime" column="create_time" />
      <result property="updateTime" column="update_time" />
      <collection property="roles" ofType="tech.pdai.springboot.mysql57.mybatis.xml.entity.Role">
        <result property="id" column="id" />
        <result property="name" column="name" />
        <result property="roleKey" column="role_key" />
        <result property="description" column="description" />
        <result property="createTime" column="create_time" />
        <result property="updateTime" column="update_time" />
      </collection>
    </resultMap>

在方法中用@ResultMap

    @ResultMap("UserResult3")
    @Select("select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time from tb_user u")
    User findAll1();

为什么纯注解方式不是最佳选择?

纯注解方式为何很少大规模呢? 说说我的一些看法

  • 对于复杂的SQL,特别是按照条件动态生成方式极为不便,即便有<script>, 代码的阅读体验和维护极为不佳;
  • 对于复杂的SQL,即便有@Provider方式,这种充其量是一个半成品 * 不是所见即所得的写法,需要再定义额外的类和方法
  • 动态构建时不便利
  • 函数式编程成为主流,lambda方式才是未来
  • ...

这也是mybatis-plus等工具改进的地方。

示例源码

(上述代码中一些实体类和配置的完整代码,请参考如下代码仓库) https://github.com/realpdai/tech-pdai-spring-demos

上次编辑于:
贡献者: javatodo