code-learning/mybatis/13-mybatis-注解模块.md

936 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 MyBatis 源码分析 —— 注解模块
# 1. 概述
本文,我们来分享 MyBatis 的注解模块,对应 `annotations` 包。如下图所示:[![`annotations` 包](13-mybatis-注解模块.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_02_04/01.png)`annotations`
在 [《精尽 MyBatis 源码解析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中,简单介绍了这个模块如下:
> 随着 Java 注解的慢慢流行MyBatis 提供了**注解**的方式,使得我们方便的在 Mapper 接口上编写简单的数据库 SQL 操作代码,而无需像之前一样,必须编写 SQL 在 XML 格式的 Mapper 文件中。虽然说,实际场景下,大家还是喜欢在 XML 格式的 Mapper 文件中编写响应的 SQL 操作。
注解比较多,艿艿尽量对它们的用途,进行规整。
另外,想要看 MyBatis 注解文档的胖友,可以看看 [《MyBatis 文档 —— Java API》](http://www.mybatis.org/mybatis-3/zh/java-api.html) 。
> 艿艿的补充:在写完本文后,发现田守枝对注解的整理更好,引用如下:
>
> FROM [《mybatis 注解配置详解》](http://www.tianshouzhi.com/api/tutorials/mybatis/393)
>
> - **增删改查:** @Insert、@Update、@Delete、@Select、@MapKey、@Options、@SelelctKey、@Param、@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider
> - **结果集映射:** @Results、@Result、@ResultMap、@ResultType、@ConstructorArgs、@Arg、@One、@Many、@TypeDiscriminator、@Case
> - **缓存:** @CacheNamespace、@Property、@CacheNamespaceRef、@Flush
# 2. CRUD 常用操作注解
示例如下:
```
package com.whut.inter;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.whut.model.User;
// 最基本的注解CRUD
public interface IUserDAO {
@Select("select *from User")
public List<User> retrieveAllUsers();
//注意这里只有一个参数,则#{}中的标识符可以任意取
@Select("select *from User where id=#{idss}")
public User retrieveUserById(int id);
@Select("select *from User where id=#{id} and userName like #{name}")
public User retrieveUserByIdAndName(@Param("id")int id,@Param("name")String names);
@Insert("INSERT INTO user(userName,userAge,userAddress) VALUES(#{userName},"
+ "#{userAge},#{userAddress})")
public void addNewUser(User user);
@Delete("delete from user where id=#{id}")
public void deleteUser(int id);
@Update("update user set userName=#{userName},userAddress=#{userAddress}"
+ " where id=#{id}")
public void updateUser(User user);
}
```
## 2.1 @Select
`org.apache.ibatis.annotations.@Select` ,查询语句注解。代码如下:
```
// Select.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface Select {
/**
* @return 查询语句
*/
String[] value();
}
```
## 2.2 @Insert
`org.apache.ibatis.annotations.@Insert` ,插入语句注解。代码如下:
```
// Insert.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface Insert {
/**
* @return 插入语句
*/
String[] value();
}
```
## 2.3 @Update
`org.apache.ibatis.annotations.@Update` ,更新语句注解。代码如下:
```
// Update.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Update {
/**
* @return 更新语句
*/
String[] value();
}
```
## 2.4 @Delete
`org.apache.ibatis.annotations.@Delete` ,删除语句注解。代码如下:
```
// Delete.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface Delete {
/**
* @return 删除语句
*/
String[] value();
}
```
## 2.5 @Param
`org.apache.ibatis.annotations.@Param` ,方法参数名的注解。代码如下:
> 当映射器方法需多个参数,这个注解可以被应用于映射器方法参数来给每个参数一个名字。否则,多参数将会以它们的顺序位置来被命名。比如 `#{1}``#{2}` 等,这是默认的。
>
> 使用 `@Param("person")` SQL 中参数应该被命名为 `#{person}` 。
```
// Param.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) // 参数
public @interface Param {
/**
* @return 参数名
*/
String value();
}
```
# 3. CRUD 高级操作注解
示例如下:
- IBlogDAO 接口:
```
package com.whut.inter;
import java.util.List;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.type.JdbcType;
import com.whut.model.Blog;
import com.whut.sqlTool.BlogSqlProvider;
@CacheNamespace(size=100)
public interface IBlogDAO {
@SelectProvider(type = BlogSqlProvider.class, method = "getSql")
@Results(value ={
@Result(id=true, property="id",column="id",javaType=Integer.class,jdbcType=JdbcType.INTEGER),
@Result(property="title",column="title",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="date",column="date",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="authername",column="authername",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="content",column="content",javaType=String.class,jdbcType=JdbcType.VARCHAR),
})
public Blog getBlog(@Param("id") int id);
@SelectProvider(type = BlogSqlProvider.class, method = "getAllSql")
@Results(value ={
@Result(id=true, property="id",column="id",javaType=Integer.class,jdbcType=JdbcType.INTEGER),
@Result(property="title",column="title",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="date",column="date",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="authername",column="authername",javaType=String.class,jdbcType=JdbcType.VARCHAR),
@Result(property="content",column="content",javaType=String.class,jdbcType=JdbcType.VARCHAR),
})
public List<Blog> getAllBlog();
@SelectProvider(type = BlogSqlProvider.class, method = "getSqlByTitle")
@ResultMap(value = "sqlBlogsMap")
// 这里调用resultMap这个是SQL配置文件中的,必须该SQL配置文件与本接口有相同的全限定名
// 注意文件中的namespace路径必须是使用@resultMap的类路径
public List<Blog> getBlogByTitle(@Param("title")String title);
@InsertProvider(type = BlogSqlProvider.class, method = "insertSql")
public void insertBlog(Blog blog);
@UpdateProvider(type = BlogSqlProvider.class, method = "updateSql")
public void updateBlog(Blog blog);
@DeleteProvider(type = BlogSqlProvider.class, method = "deleteSql")
@Options(useCache = true, flushCache = false, timeout = 10000)
public void deleteBlog(int ids);
}
```
- BlogSqlProvider 类:
```
package com.whut.sqlTool;
import java.util.Map;
import static org.apache.ibatis.jdbc.SqlBuilder.*;
package com.whut.sqlTool;
import java.util.Map;
import static org.apache.ibatis.jdbc.SqlBuilder.*;
public class BlogSqlProvider {
private final static String TABLE_NAME = "blog";
public String getSql(Map<Integer, Object> parameter) {
BEGIN();
//SELECT("id,title,authername,date,content");
SELECT("*");
FROM(TABLE_NAME);
//注意这里这种传递参数方式,#{}与map中的key对应而map中的key又是注解param设置的
WHERE("id = #{id}");
return SQL();
}
public String getAllSql() {
BEGIN();
SELECT("*");
FROM(TABLE_NAME);
return SQL();
}
public String getSqlByTitle(Map<String, Object> parameter) {
String title = (String) parameter.get("title");
BEGIN();
SELECT("*");
FROM(TABLE_NAME);
if (title != null)
WHERE(" title like #{title}");
return SQL();
}
public String insertSql() {
BEGIN();
INSERT_INTO(TABLE_NAME);
VALUES("title", "#{title}");
// VALUES("title", "#{tt.title}");
//这里是传递一个Blog对象的如果是利用上面tt.方式则必须利用Param来设置别名
VALUES("date", "#{date}");
VALUES("authername", "#{authername}");
VALUES("content", "#{content}");
return SQL();
}
public String deleteSql() {
BEGIN();
DELETE_FROM(TABLE_NAME);
WHERE("id = #{id}");
return SQL();
}
public String updateSql() {
BEGIN();
UPDATE(TABLE_NAME);
SET("content = #{content}");
WHERE("id = #{id}");
return SQL();
}
}
```
- 该示例使用 `org.apache.ibatis.jdbc.SqlBuilder` 来实现 SQL 的拼接与生成。实际上,目前该类已经废弃,推荐使用个的是 `org.apache.ibatis.jdbc.SQL` 类。
- 具体的 SQL 使用示例,可参见 `org.apache.ibatis.jdbc.SQLTest` 单元测试类。
- 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="com.whut.inter.IBlogDAO">
<resultMap type="Blog" id="sqlBlogsMap">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="authername" column="authername"/>
<result property="date" column="date"/>
<result property="content" column="content"/>
</resultMap>
</mapper>
```
## 3.1 @SelectProvider
`org.apache.ibatis.annotations.@SelectProvider` ,查询语句提供器。代码如下:
```
// SelectProvider.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface SelectProvider {
/**
* @return 提供的类
*/
Class<?> type();
/**
* @return 提供的方法
*/
String method();
}
```
- 从上面的使用示例可知XXXProvider 的用途是,指定一个类( `type` )的指定方法( `method` ),返回使用的 SQL 。并且,该方法可以使用 `Map<String,Object> params` 来作为方法参数,传递参数。
## 3.2 @InsertProvider
`org.apache.ibatis.annotations.@InsertProvider` ,插入语句提供器。代码如下:
```
// InsertProvider.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface InsertProvider {
/**
* @return 提供的类
*/
Class<?> type();
/**
* @return 提供的方法
*/
String method();
}
```
## 3.3 @UpdateProvider
`org.apache.ibatis.annotations.@UpdateProvider` ,更新语句提供器。代码如下:
```
// UpdateProvider.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface UpdateProvider {
/**
* @return 提供的类
*/
Class<?> type();
/**
* @return 提供的方法
*/
String method();
}
```
## 3.4 @DeleteProvider
`org.apache.ibatis.annotations.@DeleteProvider` ,删除语句提供器。代码如下:
```
// DeleteProvider.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface DeleteProvider {
/**
* @return 提供的类
*/
Class<?> type();
/**
* @return 提供的方法
*/
String method();
}
```
## 3.5 @Results
`org.apache.ibatis.annotations.@Results` ,结果的注解。代码如下:
> 对应 XML 标签为 `<resultMap />`
```
// Results.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Results {
/**
* The name of the result map.
*/
String id() default "";
/**
* @return {@link Result} 数组
*/
Result[] value() default {};
}
```
## 3.6 @Result
`org.apache.ibatis.annotations.@Results` ,结果字段的注解。代码如下:
```
// Result.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Result {
/**
* @return 是否是 ID 字段
*/
boolean id() default false;
/**
* @return Java 类中的属性
*/
String property() default "";
/**
* @return 数据库的字段
*/
String column() default "";
/**
* @return Java Type
*/
Class<?> javaType() default void.class;
/**
* @return JDBC Type
*/
JdbcType jdbcType() default JdbcType.UNDEFINED;
/**
* @return 使用的 TypeHandler 处理器
*/
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
/**
* @return {@link One} 注解
*/
One one() default @One;
/**
* @return {@link Many} 注解
*/
Many many() default @Many;
}
```
### 3.6.1 @One
`org.apache.ibatis.annotations.@One` ,复杂类型的单独属性值的注解。代码如下:
```
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface One {
/**
* @return 已映射语句(也就是映射器方法)的全限定名
*/
String select() default "";
/**
* @return 加载类型
*/
FetchType fetchType() default FetchType.DEFAULT;
}
```
### 3.6.2 @Many
`org.apache.ibatis.annotations.@Many` ,复杂类型的集合属性值的注解。代码如下:
```
// Many.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Many {
/**
* @return 已映射语句(也就是映射器方法)的全限定名
*/
String select() default "";
/**
* @return 加载类型
*/
FetchType fetchType() default FetchType.DEFAULT;
}
```
## 3.7 @ResultMap
`org.apache.ibatis.annotations.@ResultMap` ,使用的结果集的注解。代码如下:
```
// ResultMap.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface ResultMap {
/**
* @return 结果集
*/
String[] value();
}
```
- 例如上述示例的 `#getBlogByTitle(@Param("title")String title)` 方法,使用的注解为 `@ResultMap(value = "sqlBlogsMap")`,而 `"sqlBlogsMap"` 中 Mapper XML 中有相关的定义。
## 3.8 @ResultType
`org.apache.ibatis.annotations.@ResultType` ,结果类型。代码如下:
```
// ResultType.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ResultType {
/**
* @return 类型
*/
Class<?> value();
}
```
## 3.9 @CacheNamespace
`org.apache.ibatis.annotations.@CacheNamespace` ,缓存空间配置的注解。代码如下:
> 对应 XML 标签为 `<cache />`
```
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // Mapper 类上
public @interface CacheNamespace {
/**
* @return 负责存储的 Cache 实现类
*/
Class<? extends org.apache.ibatis.cache.Cache> implementation() default PerpetualCache.class;
/**
* @return 负责过期的 Cache 实现类
*/
Class<? extends org.apache.ibatis.cache.Cache> eviction() default LruCache.class;
/**
* @return 清空缓存的频率。0 代表不清空
*/
long flushInterval() default 0;
/**
* @return 缓存容器大小
*/
int size() default 1024;
/**
* @return 是否序列化。{@link org.apache.ibatis.cache.decorators.SerializedCache}
*/
boolean readWrite() default true;
/**
* @return 是否阻塞。{@link org.apache.ibatis.cache.decorators.BlockingCache}
*/
boolean blocking() default false;
/**
* Property values for a implementation object.
* @since 3.4.2
*
* {@link Property} 数组
*/
Property[] properties() default {};
}
```
### 3.9.1 @Property
`org.apache.ibatis.annotations.@Property` ,属性的注解。代码如下:
```
// Property.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Property {
/**
* 属性名
*
* A target property name
*/
String name();
/**
* 属性值
*
* A property value or placeholder
*/
String value();
}
```
## 3.10 @CacheNamespaceRef
`org.apache.ibatis.annotations.@CacheNamespaceRef` ,指向指定命名空间的注解。代码如下:
> 对应 XML 标签为 `<cache-ref />`
```
// CacheNamespaceRef.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 类型
public @interface CacheNamespaceRef {
/**
* 见 {@link MapperAnnotationBuilder#parseCacheRef()} 方法
*
* A namespace type to reference a cache (the namespace name become a FQCN of specified type)
*/
Class<?> value() default void.class;
/**
* 指向的命名空间
*
* A namespace name to reference a cache
* @since 3.4.2
*/
String name() default "";
}
```
## 3.11 @Options
`org.apache.ibatis.annotations.@Options` ,操作可选项。代码如下:
```
// Options.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Options {
/**
* The options for the {@link Options#flushCache()}.
* The default is {@link FlushCachePolicy#DEFAULT}
*/
enum FlushCachePolicy {
/** <code>false</code> for select statement; <code>true</code> for insert/update/delete statement. */
DEFAULT,
/** Flushes cache regardless of the statement type. */
TRUE,
/** Does not flush cache regardless of the statement type. */
FALSE
}
/**
* @return 是否使用缓存
*/
boolean useCache() default true;
/**
* @return 刷新缓存的策略
*/
FlushCachePolicy flushCache() default FlushCachePolicy.DEFAULT;
/**
* @return 结果类型
*/
ResultSetType resultSetType() default ResultSetType.DEFAULT;
/**
* @return 语句类型
*/
StatementType statementType() default StatementType.PREPARED;
/**
* @return 加载数量
*/
int fetchSize() default -1;
/**
* @return 超时时间
*/
int timeout() default -1;
/**
* @return 是否生成主键
*/
boolean useGeneratedKeys() default false;
/**
* @return 主键在 Java 类中的属性
*/
String keyProperty() default "";
/**
* @return 主键在数据库中的字段
*/
String keyColumn() default "";
/**
* @return 结果集
*/
String resultSets() default "";
}
```
- 通过 `useGeneratedKeys` + `keyProperty` + `keyColumn` 属性,可实现返回自增 ID 。示例见 [《【MyBatis】 MyBatis修炼之八 MyBatis 注解方式的基本用法》的 「返回自增主键」 小节](https://www.jianshu.com/p/b5f823ac5355) 。
## 3.12 @SelectKey
`org.apache.ibatis.annotations.@SelectKey` ,通过 SQL 语句获得主键的注解。代码如下:
```
// SelectKey.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SelectKey {
/**
* @return 语句
*/
String[] statement();
/**
* @return {@link #statement()} 的类型
*/
StatementType statementType() default StatementType.PREPARED;
/**
* @return Java 对象的属性
*/
String keyProperty();
/**
* @return 数据库的字段
*/
String keyColumn() default "";
/**
* @return 在插入语句执行前,还是执行后
*/
boolean before();
/**
* @return 返回类型
*/
Class<?> resultType();
}
```
- 具体使用示例,可见 [《【MyBatis】 MyBatis修炼之八 MyBatis 注解方式的基本用法》的 「返回非自增主键」 小节](https://www.jianshu.com/p/b5f823ac5355) 。
## 3.13 @MapKey
`org.apache.ibatis.annotations.@MapKey` Map 结果的键的注解。代码如下:
```
// MapKey.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法上
public @interface MapKey {
/**
* @return 键名
*/
String value();
}
```
- 这个注解看的艿艿一脸懵逼,具体使用示例,见 [《MyBatis使用@MapKey注解接收多个查询记录到Map中以便方便地用get()方法获取字段的值》](https://blog.csdn.net/ClementAD/article/details/50589459) 。
## 3.14 @Flush
`org.apache.ibatis.annotations.@Flush` Flush 注解。代码如下:
> 如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 `SqlSession#flushStatements()` 方法。Mybatis 3.3及以上)
```
// Flush.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法上
public @interface Flush {
}
```
# 4. 其它注解
## 4.1 @Mapper
`org.apache.ibatis.annotations.Mapper` ,标记这是个 Mapper 的注解。代码如下:
```
// Mapper.java
@Documented
@Inherited
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Mapper {
// Interface Mapper
}
```
- 使用示例,见 [《MyBatis中的@Mapper注解及配套注解使用详解》](https://blog.csdn.net/phenomenonsTell/article/details/79033144) 。
## 4.2 @Lang
`org.apache.ibatis.annotations.@Lang` ,语言驱动的注解。代码如下:
```
// Lang.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 方法
public @interface Lang {
/**
* @return 驱动类
*/
Class<? extends LanguageDriver> value();
}
```
- 具体使用示例,可见 [《增强MyBatis注解》的 「自定义Select In注解」 小节](https://www.jianshu.com/p/03642b807688) 。
## 4.3 暂时省略
如下几个注解,暂时省略,使用较少。
- `@TypeDiscriminator` + `@Case`
- `@ConstructorArgs` + `@Arg`
- `@AutomapConstructor`
# 666. 彩蛋
实际代码编写时,还是推荐使用 XML 的方式,而不是注解的方式。因为,好统一维护。
参考和推荐如下文章:
- 田守枝 [《mybayis注解配置详解》](http://www.tianshouzhi.com/api/tutorials/mybatis/393)
- 开心跳蚤 [《【MyBatis】 MyBatis修炼之八 MyBatis 注解方式的基本用法》](https://www.jianshu.com/p/b5f823ac5355)
- zhao_xiao_long [《MyBatis注解Annotation介绍及Demo》](http://blog.51cto.com/computerdragon/1399742)