§ 本文目标

通过本章节可以了解框架封装的数据持久化使用和原生的Mybatis使用方式

§ 练习场景

做一个简单的单表查询:

  1. 使用框架封装的数据持久化进行操作
  2. 使用原生的Mybatis进行操作

§ 操作步骤

表模型

CREATE TABLE ADMIN_SM_ORG (
"ORG_ID" VARCHAR2(32) NOT NULL ,
"INSTU_ID" VARCHAR2(32) NOT NULL ,
"ORG_CODE" VARCHAR2(100) NOT NULL ,
"ORG_NAME" VARCHAR2(100) NOT NULL ,
"UP_ORG_ID" VARCHAR2(32) NULL ,
"ORG_LEVEL" VARCHAR2(10) NOT NULL ,
"ORG_ADDR" VARCHAR2(200) NULL ,
"ZIP_CDE" VARCHAR2(6) NULL ,
"CONT_TEL" VARCHAR2(25) NULL ,
"CONT_USR" VARCHAR2(100) NULL ,
"ORG_STS" VARCHAR2(10) NOT NULL ,
"LAST_CHG_USR" VARCHAR2(32) NOT NULL ,
"LAST_CHG_DT" VARCHAR2(20) NOT NULL 
);
ALTER TABLE ADMIN_SM_ORG ADD PRIMARY KEY ("ORG_ID");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

§ 引入依赖

添加以下依赖

<dependency>
     <groupId>cn.com.yusys.yusp</groupId>
     <artifactId>yusp-common-mapper</artifactId>
</dependency>
1
2
3
4

§ 使用框架封装的CommonMapper

§ 框架封装的API说明

实现CommonMapper.java即拥有单表的增删改查功能,默认实现的方法如下:

§ 插入操作

/**
* 插入操作(全部插入)
* 如果domain的字段为null,也将插入DB中
*/
insert(T record)
 
 
/**
* 插入操作(部分字段插入)
* 如果domain的字段为null,则不会插入DB中
*/
insertSelective (T record)
 
 
/**
* 主键自动生成
* 设置注解后,插入操作中如果字段为null,会自动生成UUID进行保存
*/
@Generated(GenerationType.UUID)
private String id;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

§ 更新操作

/**
* 更新操作(全部更新)
* 如果domain的字段为null,也将更新到DB中
*/
updateByPrimaryKey (T record)
 
 
/**
* 更新操作(部分字段更新)
* 如果domain的字段为null,则不会更新到DB中
*/
updateByPrimaryKeySelective (T record)
1
2
3
4
5
6
7
8
9
10
11
12

§ 删除操作

/**
* 删除操作(条件删除)
* 将Domain中的字段等号拼接,进行条件删除
*/
delete (T record)
 
 
/**
* 删除操作(根据主键删除)
* 主键对应Domain中@Id声明的字段
*/
deleteByPrimaryKey (T record)
 
 
/**
* 删除操作(多主键删除)
* 用逗号隔开多主键进行删除
*/
deleteByIds (String ids)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

§ 查询操作

/**
* 查询操作(根据主键查询)
* 主键对应Domain中@Id声明的字段
*/
selectByPrimaryKey (T record)
 
 
/**
* 查询操作(条件查询)
* 支持单表的精确和模糊查询
*/
selectByModel (QueryModel params)
// 参数说明
condition:类型为Map,指定查询的字段默认使用等号拼接查询。如果字段类型为String且字段使用%开头或结尾则使用like拼接查询
sort:排序,指定排序使用的条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

§ 分页操作

/**
* 分页操作
* 通过QueryModel设置分页查询
*/
queryModel.setPage(int page);
queryModel.setSize(int size);
1
2
3
4
5
6

§ 框架封装的API使用方式

§ Mapper接口类继承

当前mapper接口继承CommonMapper接口,其中CommonMapper接口中的泛型对象为当前这个Mapper接口需要操作的java bean对象

public interface AdminSmOrgMapper extends CommonMapper<AdminSmOrg> {
}
1
2

§ Service使用方式

在Service中使用@Autowired注入需要使用的Mapper即可

@Autowired
private AdminSmOrgMapper adminSmOrgMapper;
 
public AdminSmOrg queryByPk(String orgId) {
    return adminSmOrgMapper.selectByPrimaryKey(orgId);
}
1
2
3
4
5
6

§ Resource使用方式

在Resource中使用@Autowired注入需要使用的service即可

@Autowired
private AdminSmOrgService adminSmOrgService;
 
@GetMapping("/queryByCommonMapper/{orgId}")
public AdminSmOrg queryByPk(@PathVariable("orgId") String orgId) {
    return adminSmOrgService.queryByPk(orgId);
}
1
2
3
4
5
6
7

§ 启动类注解添加

在启动类上添加@tk.mybatis.spring.annotation.MapperScan注解,该注解中的路径为mapper接口所在包路径

@MapperScan({"cn.com.yusys.yusp.repository.mapper"})
1

§ 配置文件配置

请在配置文件applicaiton.yml或配置中心中添加如下配置:

#mybatis 配置
mybatis: 
    mapperLocations: classpath*:mapper/**/*.xml #mybatis对应的xml文件路径
    configuration: 
        map-underscore-to-camel-case: true #是否开启驼峰命名
#
#通用mapper列表,使用apollo配置中心时,请将该属性配置在bootstrap中
mapper: 
    mappers: 
        - cn.com.yusys.yusp.commons.mapper.CommonMapper
#
#分页配置
pagehelper: 
    helperDialect: oracle #分页使用的数据库方言
    reasonable: true #最后一页时是否总返回最后一页数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

§ 测试

启动应用后通过postman进行测试

Mybatis_01

§ 使用CommonMapper自定义生成主键

§ 使用UUID方式生成主键

在数据库对应的java bean实体里,主键对应的字段上添加@cn.com.yusys.yusp.commons.mapper.annotation.GeneratedGenerated(GenerationType.UUID)注解,如下

/** 记录编号 **/
@Id 
@Column(name = "ORG_ID")
@Generated(GenerationType.UUID)
private String orgId;
1
2
3
4
5

§ 使用自定义Sql方式生成主键

在数据库对应的java bean实体里,主键对应的字段上添加@tk.mybatis.mapper.annotation.KeySql注解,如下

/** ID **/
@Id
@Column(name = "ID")
@KeySql(sql="SELECT PUB_FLOW_SEQ_NO.NEXTVAL FROM DUAL", order=ORDER.BEFORE)
private java.math.BigDecimal id;
1
2
3
4
5

§ 快速的OracleSequence序列主键

当前主键使用Oracle sequence序列进行生成,主键对应字段加上**@tk.mybatis.mapper.annotation.KeySql@cn.com.yusys.yusp.commons.mapper.annotation.OracleSequence**注解,如下

/** ID **/
@Id
@Column(name = "ID")
@KeySql(genSql=OracleSequenceGenSql.class, order=ORDER.BEFORE)
@OracleSequence("PUB_FLOW_SEQ_NO")
private java.math.BigDecimal id;
1
2
3
4
5
6

§ 使用原生的Mybatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

该节具体的代码见CRUD单表增删查改 章节 5 操作步骤(代码编写)

§ Mybatis对应的Mapper接口创建

创建数据库操作层的Mapper接口,具体的实现类由Mybatis根据对应的Mapper.xml实行动态代理

public interface AdminSmOrgMapper {
 
          public AdminSmOrg queryByOrgId(String orgId); 
 
}
1
2
3
4
5

§ Mybatis 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="cn.com.yusys.yusp.repository.mapper.AdminSmOrgMapper">
    <resultMap id="BaseResultMap" type="cn.com.yusys.yusp.domain.AdminSmOrg">
        <id column="ORG_ID" jdbcType="VARCHAR" property="orgId" />
        <result column="INSTU_ID" jdbcType="VARCHAR" property="instuId" />
        <result column="ORG_CODE" jdbcType="VARCHAR" property="orgCode" />
        <result column="ORG_NAME" jdbcType="VARCHAR" property="orgName" />
        <result column="UP_ORG_ID" jdbcType="VARCHAR" property="upOrgId" />
        <result column="ORG_LEVEL" jdbcType="VARCHAR" property="orgLevel" />
        <result column="ORG_ADDR" jdbcType="VARCHAR" property="orgAddr" />
        <result column="ZIP_CDE" jdbcType="VARCHAR" property="zipCde" />
        <result column="CONT_TEL" jdbcType="VARCHAR" property="contTel" />
        <result column="CONT_USR" jdbcType="VARCHAR" property="contUsr" />
        <result column="ORG_STS" jdbcType="VARCHAR" property="orgSts" />
        <result column="LAST_CHG_USR" jdbcType="VARCHAR" property="lastChgUsr" />
        <result column="LAST_CHG_DT" jdbcType="VARCHAR" property="lastChgDt" />
    </resultMap>
    <sql id="Base_Column_List">
        ORG_ID,INSTU_ID,ORG_CODE,ORG_NAME,UP_ORG_ID,ORG_LEVEL,ORG_ADDR,ZIP_CDE,CONT_TEL,CONT_USR,ORG_STS,LAST_CHG_USR,LAST_CHG_DT
    </sql>
    
    <select id="queryByOrgId" resultMap="BaseResultMap" parameterType="java.lang.String">
        select <include refid="Base_Column_List" />
        from admin_sm_org where org_id = #{orgId}
    </select>
    
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

§ Service使用方式

Service中使用**@Autowired**注入需要使用的Mapper即可

@Autowired
private AdminSmOrgMapper adminSmOrgMapper;
 
public AdminSmOrg queryByOrgId(String orgId) {
    return adminSmOrgMapper.queryByOrgId(orgId);
}
1
2
3
4
5
6

§ Resource使用方式

Resource中使用**@Autowired**注入需要使用的service即可

@Autowired
private AdminSmOrgService adminSmOrgService;
    
@GetMapping("/queryByMybatis/{orgId}")
public AdminSmOrg queryByOrgId(@PathVariable("orgId") String orgId) {
          return adminSmOrgService.queryByOrgId(orgId);
}
1
2
3
4
5
6
7

§ 启动类注解添加

在启动类上添加@tk.mybatis.spring.annotation.MapperScan注解,该注解中的路径为mapper接口所在包路径,该处使用tk路径的包是为了在使用过程中同事也可以使用框架封装的CommonMapper接口进行操作

§ 测试

启动应用后通过postman进行测试

Mybatis_02

§ 框架封装CommonMapper与原生Mybatis共同使用

系统可以支持同时使用框架封装的CommonMapper和原生Mybatis,具体在Mapper接口中使用方式如下:

public interface AdminSmOrgMapper extends CommonMapper<AdminSmOrg> {
 
          public AdminSmOrg queryByOrgId(String orgId); 
 
}
1
2
3
4
5

此时,当前的Mapper接口既支持框架封装的CommonMapper,也支持原生自定义的Mybatis,但需要注意的是,两者同时使用的时候,在Mapper中添加自己的接口方法时不可与框架提供的接口方法同名,即不可与Mybatis对应的Mapper接口创建Resource使用方式

§ Mybatis介绍

参见文档:http://www.mybatis.org/mybatis-3/sqlmap-xml.html

§ 映射器XML文件

Mapper XML文件只有少数一等元素(按照它们的定义顺序):

  • cache - 为给定命名空间配置缓存。
  • cache-ref - 从另一个名称空间引用缓存配置。
  • resultMap - 描述如何从数据库结果集加载对象的最复杂和最强大的元素。
  • parameterMap - 已弃用!老派的方式来映射参数。内联参数是首选,将来可能会删除此元素。这里没有记录。
  • sql - 可重用的SQL块,可以被其他语句引用。
  • insert - 映射的INSERT语句。
  • update - 映射的UPDATE语句。
  • delete - 映射的DELETE语句。
  • select - 映射的SELECT语句。

§ select语句属性介绍

属性 描述
id 此命名空间中的唯一标识符,可用于引用此语句。
parameterType 将传递给此语句的参数的完全限定类名或别名。此属性是可选的,因为MyBatis可以计算TypeHandler以使用传递给语句的实际参数。默认是未设置的。
parameterMap 这是一种不推荐使用的引用外部parameterMap的方法。使用内联参数映射和parameterType属性。
resultType 将从此语句返回的预期类型的完全限定类名或别名。请注意,对于集合,这应该是集合包含的类型,而不是集合本身的类型。使用resultType或 resultMap,而不是两者。
resultMap 对外部resultMap的命名引用。结果图是MyBatis最强大的功能,并且对它们有很好的理解,可以解决许多困难的映射案例。使用resultMap或resultType,而不是两者。
flushCache 将此设置为true将导致在调用此语句时刷新本地和第二级高速缓存。对于select语句,默认值:false。
useCache 将此设置为true将导致此语句的结果缓存在二级缓存中。默认值: 对于select语句为true。
timeout 这将设置驱动程序在抛出异常之前等待数据库从请求返回的秒数。默认为未设置(取决于驱动程序)。
fetchSize 这是一个驱动程序提示,它将尝试使驱动程序以大小等于此设置的批量行返回结果。默认为未设置(取决于驱动程序)。
statementType STATEMENT,PREPARED或CALLABLE中的任何一个。这导致MyBatis分别使用Statement, PreparedStatement或CallableStatement。默认值:PREPARED。
resultSetType 任何一个FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(与未设置相同)。默认为未设置(取决于驱动程序)。
databaseId 如果有配置的databaseIdProvider,MyBatis将加载所有没有databaseId 属性或与当前匹配的databaseId的语句。如果在使用和不使用databaseId的情况下找到相同的语句,后者将被丢弃。
resultOrdered 这仅适用于嵌套的结果选择语句:如果这是真的,则假定嵌套结果被包含或组合在一起,这样当返回新的主结果行时,将不再发生对先前结果行的引用。这允许嵌套结果填充更多内存友好。默认值: false。
resultSets 这仅适用于多个结果集。它列出了语句将返回的结果集,并为每个结果集命名。名称以逗号分隔。

§ insert、update、delete语句属性介绍

属性 描述
id 此命名空间中的唯一标识符,可用于引用此语句。
parameterType 将传递给此语句的参数的完全限定类名或别名。此属性是可选的,因为MyBatis可以计算TypeHandler以使用传递给语句的实际参数。默认是未设置的。
parameterMap 这是一种不推荐使用的引用外部parameterMap的方法。使用内联参数映射和parameterType属性。
flushCache 将此设置为true将导致在调用此语句时刷新第2级和本地缓存。默认值:对于insert,update和delete语句为true。
timeout 这将设置驱动程序在抛出异常之前等待数据库从请求返回的最大秒数。默认为未设置(取决于驱动程序)。
statementType STATEMENT,PREPARED或CALLABLE中的任何一个。这导致MyBatis分别使用Statement, PreparedStatement或CallableStatement。默认值:PREPARED。
useGeneratedKeys (仅插入和更新)这告诉MyBatis使用JDBC getGeneratedKeys方法来检索数据库内部生成的密钥(例如,像MySQL或SQL Server这样的RDBMS中的自动增量字段)。默认值:false。
keyProperty (仅插入和更新)标识MyBatis将设置getGeneratedKeys返回的键值或insert语句的selectKey子元素的属性。默认值:未设置。如果需要多个生成的列,则可以是以逗号分隔的属性名称列表。
keyColumn (仅插入和更新)使用生成的密钥设置表中列的名称。仅当键列不是表中的第一列时,才需要在某些数据库(如PostgreSQL)中使用此选项。如果需要多个生成的列,则可以是以逗号分隔的列名列表。
databaseId 如果有配置的databaseIdProvider,MyBatis将加载所有没有databaseId 属性或与当前匹配的databaseId的语句。如果在使用和不使用databaseId的情况下找到相同的语句,后者将被丢弃。
resultSetType STATEMENT,PREPARED或CALLABLE中的任何一个。这导致MyBatis分别使用Statement, PreparedStatement或CallableStatement。默认值:PREPARED。
databaseId 任何一个FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(与未设置相同)。默认为未设置(取决于驱动程序)。
resultOrdered 如果有配置的databaseIdProvider,MyBatis将加载所有没有databaseId 属性或与当前匹配的databaseId的语句。如果在使用和不使用databaseId的情况下找到相同的语句,后者将被丢弃。
resultSets 这仅适用于嵌套的结果选择语句:如果这是真的,则假定嵌套结果被包含或组合在一起,这样当返回新的主结果行时,将不再发生对先前结果行的引用。这允许嵌套结果填充更多内存友好。默认值: false。

§ selectKey介绍

MyBatis有另一种方法来处理不支持自动生成的主键,可使用selectKey来进行自定义

<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>
1
2
3
4
5
6
7
8
9

在上面的示例中,将首先运行selectKey语句,将设置Author id属性,然后将调用insert语句。这儿提供了与数据库中自动生成的键类似的行为,而不会使Java代码复杂化。具体的属性解释如下

属性 描述
keyProperty 应该设置selectKey语句结果的target属性。如果需要多个生成的列,则可以是以逗号分隔的属性名称列表。
keyColumn 返回的结果集中与列属性匹配的列名称。如果需要多个生成的列,则可以是以逗号分隔的列名列表。
resultType 结果的类型。MyBatis通常可以解决这个问题,但确定添加它并没有什么坏处。MyBatis允许使用任何简单类型作为键,包括字符串。如果您期望生成多个列,则可以使用包含预期属性的Object或Map。
order 这可以设置为BEFORE或AFTER。如果设置为BEFORE,则它将首先选择键,设置keyProperty然后执行insert语句。如果设置为AFTER,则运行insert语句,然后运行selectKey语句 - 这对于Oracle等可能在insert语句中嵌入了序列调用的数据库很常见。
statementType 与上面相同,MyBatis支持分别映射到Statement,PreparedStatement和CallableStatement的STATEMENT,PREPARED和CALLABLE语句类型。

§ jdbcType类型

mybatis支持的jdbcType如下:

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

如存在具有相同名称和类型的属性,则可以省略javaType。其余属性和规则与常规id和result元素相同

属性 描述
column 数据库中的列名称或别名列标签。这与通常传递给resultSet.getString(columnName)的字符串相同。
javaType 完全限定的Java类名称或类型别名(有关内置类型别名的列表,请参阅上表)。如果您要映射到JavaBean,MyBatis通常可以找出类型。但是,如果要映射到HashMap,则应明确指定javaType以确保所需的行为。
jdbcType 从此表后面的受支持类型列表中的JDBC Type。只有在插入,更新或删除时可为空的列才需要JDBC类型。这是JDBC要求,而不是MyBatis要求。因此,即使您直接编写JDBC,也需要指定此类型 - 但仅限于可空值。
typeHandler 我们之前在本文档中讨论了默认类型处理程序。使用此属性,您可以在逐个映射的基础上覆盖默认类型处理程序。该值是TypeHandler实现的完全限定类名或类型别名。
select 另一个映射语句的ID,它将加载此属性映射所需的复杂类型。从column属性中指定的列检索的值将作为参数传递给目标select语句。有关更多信息,请参阅Association元素。
resultMap 这是ResultMap的ID,可以将此参数的嵌套结果映射到适当的对象图中。这是使用对另一个select语句的调用的替代方法。它允许您将多个表连接在一起成为一个ResultSet。这样的ResultSet将包含重复的重复数据组,这些数据需要被分解并正确映射到嵌套对象图。
name 构造函数参数的名称。指定名称允许您以任何顺序编写arg元素

§ 基于Mybatis的注解使用方式

MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。而到了 MyBatis 3,就有新选择了。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。这个配置 API 是基于 XMLMyBatis 配置的基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销

注意:Java 注解的的表达力和灵活性十分有限。尽管很多时间都花在调查、设计和试验上,最强大的 MyBatis 映射并不能用注解来构建——并不是在开玩笑,的确是这样。比方说,**C#**属性就没有这些限制,因此 MyBatis.NET 将会比 XML 有更丰富的选择。也就是说,基于 Java 注解的配置离不开它的特性

注解 使用对象 相对应的 XML 描述
@CacheNamespace 为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blocking 和properties。
@Property N/A <property> 指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效)
@CacheNamespaceRef 参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。
@ConstructorArgs 方法 收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。
@Arg N/A 单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, select 和 resultMap。id 属性是布尔值,来标识用于比较的属性,和 XML 元素相似。
@TypeDiscriminator 方法 <discriminator> 一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandler 和 cases。cases 属性是实例数组。
@Case N/A 单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
@Results 方法 结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。
@Result N/A 在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的相似)的属性。one 属性是单独的联系,和 相似,而 many 属性是对集合而言的,和相似。它们这样命名是为了避免名称冲突。
@One N/A 复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。
@Many N/A 映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用
@MapKey 方法 这是一个用在返回值为 Map 的方法上的注解。它能够将存放对象的 List 转化为 key 值为对象的某一属性的 Map。属性有: value,填入的是对象的属性名,作为 Map 的 key 值。
@Options 方法 映射语句的属性 这个注解提供访问大范围的交换和配置选项的入口,它们通常在映射语句上作为属性出现。Options 注解提供了通俗易懂的方式来访问它们,而不是让每条语句注解变复杂。属性有:useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=DEFAULT, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty="", keyColumn="", resultSets=""。值得一提的是, Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的预期以外的行为。 注意: keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL等)。请在插入语句一节查看更多关于 keyColumn 和 keyProperty 两者的有效值详情。
@Insert
@Update
@Delete
@Select
方法 这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。
@InsertProvider
@UpdateProvider
@DeleteProvider
@SelectProvider
方法 允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,"Mapper interface type" 和 "Mapper method" 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入)属性有: type, method。type 属性需填入类。method 需填入该类定义了的方法名。注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。
@Param 参数 N/A 如果你的映射方法的形参有多个,这个注解使用在映射方法的参数上就能为它们取自定义名字。若不给出自定义名字,多参数(不包括 RowBounds 参数)则先以 "param" 作前缀,再加上它们的参数位置作为参数别名。例如 #{param1}, #{param2},这个是默认值。如果注解是 @Param("person"),那么参数就会被命名为 #{person}。
@SelectKey 方法 这个注解的功能与 标签完全一致,用在已经被 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 注解了的方法上。若在未被上述四个注解的方法上作 @SelectKey 注解则视为无效。如果你指定了 @SelectKey 注解,那么 MyBatis 就会忽略掉由 @Options 注解所设置的生成主键或设置(configuration)属性。属性有:statement 填入将会被执行的 SQL 字符串数组,keyProperty 填入将会被更新的参数对象的属性的值,before 填入 true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行。resultType 填入 keyProperty 的 Java 类型和用 Statement、 PreparedStatement 和 CallableStatement 中的 STATEMENT、 PREPARED 或 CALLABLE 中任一值填入 statementType。默认值是 PREPARED。
@ResultMap 方法 N/A 这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 的id。这使得注解的 select 可以复用那些定义在 XML 中的 ResultMap。如果同一 select 注解中还存在 @Results 或者 @ConstructorArgs,那么这两个注解将被此注解覆盖。
@ResultType 方法 N/A 此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的
@Flush 方法 N/A 如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上)

§ 映射示例

@SelectKey** ** 如下为如何使用 @SelectKey 注解来在插入前读取数据库序列的值:

@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);
1
2
3

如下为如何使用 @SelectKey 注解来在插入后读取数据库识别列的值:

@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);
1
2
3

§ @Flush

如下为使用 @Flush 注解去调用 SqlSession#flushStatements():

@Flush
List<BatchResult> flush();
1
2

§ @Result

如下为如何通过指定 @Result 的 id 属性来命名结果集:

@Results(id = "userResult", value = {
  @Result(property = "id", column = "uid", id = true),
  @Result(property = "firstName", column = "first_name"),
  @Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);
 
@Results(id = "companyResults")
@ConstructorArgs({
  @Arg(property = "id", column = "cid", id = true),
  @Arg(property = "name", column = "name")
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

§ @SqlProvider

如下为单一参数使用 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);
 
class UserSqlBuilder {
  public static String buildGetUsersByName(final String name) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      if (name != null) {
        WHERE("name like #{value} || '%'");
      }
      ORDER_BY("id");
    }}.toString();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如下为多参数使用 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
    @Param("name") String name, @Param("orderByColumn") String orderByColumn);
 
class UserSqlBuilder {
 
  // If not use @Param, you should be define same arguments with mapper method
  public static String buildGetUsersByName(
      final String name, final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }
 
  // If use @Param, you can define only arguments to be used
  public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

§ Mybatis的TypeHandler使用简介

§ TypeHandler简介

TypeHandler,类型转换器,在mybatis中用于实现java类型和JDBC类型的相互转换.mybatis使用prepareStatement来进行参数设置的时候,需要通过typeHandler将传入的java参数设置成合适的jdbc类型参数,这个过程实际上是通过调用PrepareStatement不同的set方法实现的;在获取结果返回之后,也需要将返回的结果转换成我们需要的java类型,这时候是通过调用ResultSet对象不同类型的get方法时间的;所以不同类型的typeHandler其实就是调用PrepareStatement和ResultSet的不同方法来进行类型的转换,有些时候会在调用PrepareStatement和ResultSet的相关方法之前,可以对传入的参数进行一定的处理

当我们没有指定typeHandler的时候mybatis会根据传入参数的类型和返回值的类型调用默认的typeHandler进行处理.对于一个typeHandler需要配置java类型(javaType)和JDBC类型(jdbcType),typeHandler的作用就是实现这两种类型的转换,在传入的参数为指定的Java类型时,将其转换为指定的JDBC类型,当返回值为指定JDBC类型时将其转换为配置的Java类型

§ Mybatis默认定义的TypeHandler

private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());
    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());
    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());
    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());
    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());
    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());
    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());
    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());
    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());
    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());
    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());
    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    // mybatis-typehandlers-jsr310
    if (Jdk.dateAndTimeApiExists) {
      this.register(Instant.class, InstantTypeHandler.class);
      this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
      this.register(LocalDate.class, LocalDateTypeHandler.class);
      this.register(LocalTime.class, LocalTimeTypeHandler.class);
      this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
      this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
      this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
      this.register(Month.class, MonthTypeHandler.class);
      this.register(Year.class, YearTypeHandler.class);
      this.register(YearMonth.class, YearMonthTypeHandler.class);
      this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
    }
    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

可以看到对于一个Java类型是可以有多个JDBC类型相对应的,所以会存在多个TypeHandler,在这种情况下需要根据传入参数的javaType以及其在数据库中对应JdbcType一起来选定一个TypeHandler进行处理

§ 使用自定义TypeHandler

虽然大部分时候mybatis提供的typeHandler已经够用了,但总有些情况下需要我们自己定义TypeHandler.下面通过一个实例来研究如何自己定义和使用TypeHandler. 现在的场景是:首先是建立了一个person表存储个人信息,这个表里有一栏hobbys是记录个人爱好的,爱好包括足球,排球,游泳之类的,在Java的Person对象中对应的是一个String类型的list,而在数据库中是以逗号分隔的字符串表示的,心事如”足球,排球,游泳”,所以现在需要定义一个TypeHandler实现在插入数据和查询数据时string和list类型的相互转换.自定义一个TypeHandler需要继承TypeHandler T接口,T是传入的java类型,并需要使用@MappedTypes定义需要被拦截的java类型@MappedJdbcTypes配置jdbc类型,这里的JdbcType必须org.apache.ibatis.type.JdbcType中的枚举类型,然后还需要实现TypeHandler接口中一系列的get set方法,具体实现的代码如下

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ListTypeHandler implements TypeHandler<List<String>> {
 
    @Override
    public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        String orgIds = StringUtils.join(parameter, ",");
        try {
            ps.setString(i, orgIds);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String orgIds = cs.getString(columnIndex);
        return Arrays.asList(orgIds.split(","));
    }
 
    @Override
    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
        return Arrays.asList(rs.getString(columnIndex).split(","));
    }
 
    @Override
    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
        return Arrays.asList(rs.getString(columnName).split(","));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

在插入数据和对返回结果进行处理的时候,可以对参数配置javaType和jdbcType或直接配置typeHandler属性来进行标识

下面是插入数据时标识用指定TypeHandler进行处理

<insert id="insertPerson" parameterType="person">
        INSERT INTO person (id,name,sex,org_ids,data_time) values(#{id},#{name},#{sex},#{orgIds,typeHandler=cn.com.yusys.yusp.mapper.typeHandler.ListTypeHandler},#{date})
</insert>
1
2
3

下面是标识对返回的结果用指定TypeHandler进行处理

<resultMap id="personMap" type="person">
          <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="sex" column="sex"/>
    <result property="orgIds" column="org_ids" typeHandler="cn.com.yusys.yusp.mapper.typeHandler.ListTypeHandler"/>
    <result property="date" column="data_time"/>
</resultMap>
1
2
3
4
5
6
7
最后更新于: 5/5/2022, 6:41:08 PM