SpringBoot集成MySQL - MyBatis 注解方式
上文主要介绍了Spring集成MyBatis访问MySQL,采用的是XML配置方式;我们知道除了XML配置方式,MyBatis还支持注解方式。本文主要介绍SpringBoot+MyBatis注解方式。@javaxue
准备知识
MyBatis的相关知识体系。
具体可以参考 SpringBoot集成MySQL - MyBatis XML方式
在构建知识体系时:我们最重要的目标并不是如何使用注解方式,而是要理解:
- 对于有原有xml方式改为注解方式(一定要有对比),如何写?
- 基本的CRUD怎么用注解写?
- 对于复杂的动态SQL如何写?
- 对于表关联的如何写?
- 为什么xml方式依然是比注解方式使用广泛?
- xml方式和注解方式混合使用?
- 注解方式是如何工作的呢?
基本查改删操作
我们从最基本的增删改操作开始,对比xml方式进行理解。
查询操作
@Results和@Result注解
对于xml配置查询时定义的ResultMap, 在注解中如何定义呢?
<resultMap type="tech.javaxue.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.javaxue.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.javaxue.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.javaxue.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.javaxue.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 + <script>
@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.javaxue.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.javaxue.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 javaxue
*/
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.javaxue.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.javaxue.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-javaxue-spring-demos