Go To My HomePage

通用mapper使用指南

一、概述

​ 通用 Mapper是一个可以实现任意 MyBatis 通用方法的框架,项目提供了常规的增删改查操作以及查询相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。

二、集成

Spring集成

第一步:添加依赖

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>版本号</version>
</dependency>
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>版本号</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>版本号</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>版本号</version>
</dependency>
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper</artifactId>
  <version>版本号</version>
</dependency>

第二步:开始集成

  • XML配置有以下两种方式,选其一

    • 使用MapperScannerConfigurer

      tk.mybatis.spring.mapper.MapperScannerConfigurer代替org.mybatis.spring.mapper.MapperScannerConfigurer

      <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
          <property name="basePackage" value="扫描包名"/>
          ...
      </bean>
      
    • 使用Configuration

      如果某些第三方也需要特殊的 MapperScannerConfigurer 时,就不能用上面的方式进行配置了,此时使用以下方法

      <!--使用 Configuration 方式进行配置-->
      <bean id="mybatisConfig" class="tk.mybatis.mapper.session.Configuration">
        <!-- 配置通用 Mapper,有三种属性注入方式 -->
        <property name="mapperProperties">
          <value>
            notEmpty=true
          </value>
        </property>
      </bean>
          
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configuration" ref="mybatisConfig"/>
      </bean>
          
      <!-- 不需要考虑下面这个,注意这里是 org 的 -->
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="tk.mybatis.mapper.configuration"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
      </bean>
      

      这里使用了 tk.mybatis.mapper.session.Configuration ,就是不能通过读取 mybatis-config.xml进行配置,直接使用 Spring setter 配置属性

  • 使用java注解和代码集成方式有以下三种

    • 使用@MapperScanproperties属性

      @Configuration
      @MapperScan(value = "tk.mybatis.mapper.annotation",
                  properties = {
                    "mappers=tk.mybatis.mapper.common.Mapper",
                    "notEmpty=true"
                  }
                 )
      public class MyBatisConfigProperties {
          
      }
      
    • 使用@MapperScanmapperHelperRef 属性

      @Configuration
      @MapperScan(value = "tk.mybatis.mapper.annotation", mapperHelperRef = "mapperHelper")
      public static class MyBatisConfigRef {
          
        //其他
        @Bean
        public MapperHelper mapperHelper() {
          Config config = new Config();
          List<Class> mappers = new ArrayList<Class>();
          mappers.add(Mapper.class);
          config.setMappers(mappers);
          
          MapperHelper mapperHelper = new MapperHelper();
          mapperHelper.setConfig(config);
          return mapperHelper;
        }
      }
      
    • 使用构造Configuration的方式

      @Configuration
      @MapperScan(value = "tk.mybatis.mapper.annotation")
      public static class MyBatisConfig {
          
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
          SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
          sessionFactory.setDataSource(dataSource());
          //tk.mybatis.mapper.session.Configuration
          Configuration configuration = new Configuration();
          //可以对 MapperHelper 进行配置后 set
          configuration.setMapperHelper(new MapperHelper());
          //设置为 tk 提供的 Configuration
          sessionFactory.setConfiguration(configuration);
          return sessionFactory.getObject();
        }
      }
      

spring boot集成

基于 starter 的自动配置

<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper-spring-boot-starter</artifactId>
  <version>版本号</version>
</dependency>

在 starter 的逻辑中,如果你没有使用 @MapperScan 注解,你就需要在你的接口上增加 @Mapper注解,否则 MyBatis 无法判断扫描哪些接口。所以需要手动在所有接口上增加 @Mapper 注解

在yml中配置属性

mapper:
  mappers:
    - tk.mybatis.mapper.common.Mapper
  notEmpty: true

基于MapperScan注解

@tk.mybatis.spring.annotation.MapperScan(basePackages = "扫描包")
@SpringBootApplication
public class SampleMapperApplication implements CommandLineRunner {

}

三、注解详解

@NameStyle注解(Mapper)

可以设置实体类字段与表字段的转换方式,可选值

public enum Style {
  normal,                     //原值
  camelhump,                  //驼峰转下划线,默认值
  uppercase,                  //转换为大写
  lowercase,                  //转换为小写
  camelhumpAndUppercase,      //驼峰转下划线大写形式
  camelhumpAndLowercase,      //驼峰转下划线小写形式
}

@Table注解(JPA)

作用:建立实体类和数据库表之间的对应关系。 默认规则: 实体类类名首字母小写作为表名。 Employee 类→employee 表 用法: 在@Table注解的 name属性中指定目标数据库表的表名

@Column注解(JPA)

作用:建立实体类字段和数据库表字段之间的对应关系。

默认规则:实体类字段:驼峰式命名。数据库表字段:使用“_”区分各个单词

用法:在@Column 注解的name 属性中指定目标字段的字段名

@ColumnType注解(Mapper)

主要用于枚举属性,其中column属性和 @Column 中的 name 作用相同,但是 @Column的优先级更高。除了 name 属性外,这个注解主要提供了 jdbcType 属性和 typeHandler 属性。

  • jdbcType 用于设置特殊数据库类型时指定数据库中的 jdbcType

  • typeHandler 用于设置特殊类型处理器,常见的是枚举。

@Transient注解(JPA)

用于标记不与数据库表字段对应的实体类字段。对于类中的复杂对象,以及 Map,List 等属性不需要配置这个注解。

主键相关注解

主键策略

  • 自增类型的

    • 使用@Id配合@KeySql,偏向Mybatis写法

      @Id
      @KeySql(useGeneratedKeys = true)//或者@GeneratedValue(generator = "JDBC")
      private Integer id;
      
    • 使用@Id配合@KeySql,偏向JPA写法

      @Id
      @KeySql(dialect = IdentityDialect.DEFAULT)//或者直接指定数据库方言dialect = IdentityDialect.MYSQL
      private Integer id;
      
    • 使用@GeneratedValue

      @Id 
      @GeneratedValue(strategy = GenerationType.IDENTITY) 
      private Integer id;
      
  • 通过序列和任意 SQL 获取主键值

    • 使用@Id配合@KeySql

      @Id 
      @KeySql(sql = "select SEQ_ID.nextval from dual", order = ORDER.BEFORE)
      private Integer id;
      
    • 使用@GeneratedValue

      @Id 
      @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "select SEQ_ID.nextval from dual") 
      private Integer id;
      

@Verson注解(Mapper)

@Verson注解已经不常用,以下只是简单说明:

在使用乐观锁时,由于通用 Mapper 是内置的实现,不是通过拦截器方式实现的,如果版本不一致可能执行影响数为0,但不会抛出异常,所以在 Java6,7中使用时,你需要自己在调用方法后进行判断是否执行成功。

//Java8+可以使用默认方法判断自动抛出异常
public interface MyMapper<T> extends Mapper<T> {

  default int deleteWithVersion(T t){
    int result = delete(t);
    if(result == 0){
      throw new RuntimeException("删除失败!");
    }
    return result;
  }

  default int updateByPrimaryKeyWithVersion(Object t){
    int result = updateByPrimaryKey(t);
    if(result == 0){
      throw new RuntimeException("更新失败!");
    }
    return result;
  }
}

@RegisterMapper 注解

作用: 通用 Mapper 检测到该接口被继承时,会自动注册。否则需要配置扫描参数。

四、全局主键

第一步:实现GenId<T>接口,实现主键生成策略

//用UUID实现主键生成策略
public class UUIDGenId implements GenId<String> {
  @Override
  public String genId(String table, String column) {
    return UUID.randomUUID().toString().replace("-","");
  }
}

第二步:配置

public class User {
  @Id
  @KeySql(genId = UUIDGenId.class)
  private String id;
  private String name;
  private String code;

  public User() {
  }

  public User(String name, String code) {
    this.name = name;
    this.code = code;
  }

  //省略 setter 和 getter
}

如果你使用了@KeySql提供的其他方式,genId就不会生效,genId 是所有方式中优先级最低的

五、常用配置

指配置通用mapper的位置,以xml方式为例

<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="com.kun.mapper.mapper"/>
  <property name="properties"><!--设置的是这个properties值-->
    <props>
      <prop key="keyXXX">valueXXX</prop>
    </props>
  </property>
</bean>

mappers

​ 4.0 之后,增加了一个 @RegisterMapper 注解,通用 Mapper 中提供的所有接口都有这个注解,有了该注解后,通用 Mapper 会自动解析所有的接口,如果父接口(递归向上找到的最顶层)存在标记该注解的接口,就会自动注册上。因此 4.0 后使用通用 Mapper 提供的方法时,不需要在配置这个参数。当自己扩展通用接口时,建议加上该注解,否则就要配置 mappers 参数。

IDENTITY

​ 取回主键的方式,可以配置的值为所列的数据库类型,例如MYSQL: SELECT LAST_INSERT_ID() ,配置为IDENTITY=MYSQL

ORDER

​ 用于配置何时获取主键<selectKey>中的order属性,可选值为BEFOREAFTER

catalog、schema

​ 数据库的catalog,如果设置该值,查询的时候表名会带catalog设置的前缀

schemacatalogcatalog优先级高于schema

enumAsSimpleType

​ 用于配置是否将枚举类型当成基本类型对待。默认 simpleType 会忽略枚举类型,使用 enumAsSimpleType 配置后会把枚举按简单类型处理,需要自己配置好 typeHandler

​ 配置方式如下:enumAsSimpleType=true

checkExampleEntityClass

​ 用于校验通用 Example 构造参数 entityClass 是否和当前调用的 Mapper<EntityClass>类型一致,默认 false 。配置该字段为 true 后就会对不匹配的情况进行校验。

safeDelete、safeUpdate

​ 配置为 true 后,delete 和 deleteByExample 都必须设置查询条件才能删除,否则会抛出异常

​ 配置为 true 后,updateByExample 和 updateByExampleSelective 都必须设置查询条件才能更新,否则会抛出异常

六、代码生成器

第一步:引入MBG依赖

<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>版本号</version>
    <scope>test</scope>
</dependency>

第二步:配置generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
  <!-- MyBatis3会生成xxxxExample -->
  <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">

    <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
      <!-- 继承的Mapper基类接口 -->
      <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
      <!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
      <property name="caseSensitive" value="true" />
      <!-- 强制打上注解 -->
      <property name="forceAnnotation" value="true"/>
      <property name="beginningDelimiter" value="`"/>
      <property name="endingDelimiter" value="`"/>
      <!-- 是否使用lombok -->
      <property name="lombok" value="Data"/>
    </plugin>

    <!-- 数据库驱动配置 -->
    <jdbcConnection driverClass="com.mysql.jdbc.Driver" 
                    connectionURL="jdbc:mysql://192.168.10.191:3306/test"
                    userId="root" 
                    password="123456">
            <!--解决读取别的数据库表问题-->
            <property name="nullCatalogMeansCurrent" value="true" />
            <!--解决不生成delete、update等-->
            <property name="useInformationSchema" value="true"/>
    </jdbcConnection>

    <!-- java类型处理器
         用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
         注意默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型;
         如需配置类型对应关系应该继承它
    -->
    <!--
        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">

                true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
                false:默认,
                    scale>0;length>18:使用BigDecimal;
                    scale=0;length[10,18]:使用Long;
                    scale=0;length[5,9]:使用Integer;
                    scale=0;length<5:使用Short;

            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
    -->

    <!-- 生成POJO对象所在包 -->
    <javaModelGenerator targetPackage="com.kun.model" targetProject="src\main\java">
      <!-- POJO类继承的基类,没有可以不写 -->
      <property name="rootClass" value="com.kun.model.BaseModel"/>
    </javaModelGenerator>

    <!-- 生成的mapper.xml所在文件夹 -->
    <sqlMapGenerator targetPackage="mapper" targetProject="src\main\resources" />
    <!-- 生成的mapper接口所在包 -->
    <javaClientGenerator targetPackage="com.kun.mapper" targetProject="src\main\java" type="XMLMAPPER" />

    <!-- 表名,主键策略 -->
    <table tableName="PRESS_REMI_EXPRESS"
           enableCountByExample="false"
           enableUpdateByExample="true"
           enableDeleteByExample="true"
           enableSelectByExample="true"
           selectByExampleQueryId="true">
      <generatedKey column="ID" sqlStatement="Mysql" identity="true" />
    </table>
  </context>
</generatorConfiguration>

第三步:以java代码的方式运行MBG

public class MybatisGenerator{

  public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
    InputStream configFile = MybatisGenerator.class.getResourceAsStream("/generatorConfig.xml");
    System.out.println(configFile);
    List<String> warnings = new ArrayList<String>();
    boolean overwrite = true;
    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
    ProgressCallback progress = new VerboseProgressCallback();
    myBatisGenerator.generate(progress);
  }
}

七、扩展通用接口

以自定义的SelectAll为例,演示创建通用接口步骤。

第一步:创建接口,接口使用注解@RegisterMapper使通用mapper扫描,使用注解@SelectProvider指定驱动类,method = "dynamicSQL"为固定写法。

@RegisterMapper
public interface SelectAllMapper<T> {

  // 查询全部结果
  @SelectProvider(type = MySelectProvider.class, method = "dynamicSQL")
  List<T> selectAll();
}

第二步:实现驱动类,注意驱动类继承MapperTemplate,创建的方法名需要与接口的方法名同名。

public class MySelectProvider extends MapperTemplate {

  public BaseSelectProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
    super(mapperClass, mapperHelper);
  }

  // 查询全部结果
  public String selectAll(MappedStatement ms) {
    final Class<?> entityClass = getEntityClass(ms);
    //修改返回值类型为实体类型
    setResultType(ms, entityClass);
    StringBuilder sql = new StringBuilder();
    sql.append(SqlHelper.selectAllColumns(entityClass));
    sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
    sql.append(SqlHelper.orderByDefault(entityClass));
    return sql.toString();
  }
}

注:可映射驱动注解类型

  • @SelectProvider —— 查询驱动类型,返回值为其查询结果集合
  • @UpdateProvider —— 更新驱动类型,返回值为影响记录行数
  • @InsertProvider —— 插入驱动类型,返回值为影响记录行数,主键填充在传入的参数中
  • @DeleteProvider —— 删除驱动类型,返回值为影响记录行数

注:对开发自定义有帮助的类

  • MapperTemplate —— 通用Mapper模板类,扩展通用Mapper时需要继承该类
  • EntityHelper —— 实体类工具类,处理实体和数据库表以及字段关键的一个类
  • FieldHelper —— 类字段工具类
  • SqlHelper —— 拼常用SQL的工具类
  • MapperHelper —— 获取通用mapper的所有配置

自定义Insert Ignore插入

@RegisterMapper
public interface InsertIgnoreSelectiveMapper<T> {

  @InsertProvider(type = InsertIgnoreSelectiveProvider.class, method = "dynamicSQL")
  int insertIgnoreSelective(T record);
}

public class InsertIgnoreSelectiveProvider extends MapperTemplate {

  public InsertIgnoreSelectiveProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
    super(mapperClass, mapperHelper);
  }

  public String insertIgnoreSelective(MappedStatement ms) {
    Class<?> entityClass = getEntityClass(ms);
    StringBuilder sql = new StringBuilder();
    //获取全部列
    Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass);
    EntityColumn logicDeleteColumn = SqlHelper.getLogicDeleteColumn(entityClass);
    processKey(sql, entityClass, ms, columnList);
    sql.append("INSERT IGNORE INTO ");
    sql.append(SqlHelper.getDynamicTableName(entityClass, tableName(entityClass)));
    sql.append(" ");
    sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
    for (EntityColumn column : columnList) {
      if (!column.isInsertable()) {
        continue;
      }
      if (column.isIdentity()) {
        sql.append(column.getColumn()).append(",");
      } else {
        if (logicDeleteColumn != null && logicDeleteColumn == column) {
          sql.append(column.getColumn()).append(",");
          continue;
        }
        sql.append(SqlHelper.getIfNotNull(column, column.getColumn() + ",", isNotEmpty()));
      }
    }
    sql.append("</trim>");

    sql.append("<trim prefix=\"VALUES(\" suffix=\")\" suffixOverrides=\",\">");
    for (EntityColumn column : columnList) {
      if (!column.isInsertable()) {
        continue;
      }
      if (logicDeleteColumn != null && logicDeleteColumn == column) {
        sql.append(SqlHelper.getLogicDeletedValue(column, false)).append(",");
        continue;
      }
      //优先使用传入的属性值,当原属性property!=null时,用原属性
      //自增的情况下,如果默认有值,就会备份到property_cache中,所以这里需要先判断备份的值是否存在
      if (column.isIdentity()) {
        sql.append(SqlHelper.getIfCacheNotNull(column, column.getColumnHolder(null, "_cache", ",")));
      } else {
        //其他情况值仍然存在原property中
        sql.append(SqlHelper.getIfNotNull(column, column.getColumnHolder(null, null, ","), isNotEmpty()));
      }
      //当属性为null时,如果存在主键策略,会自动获取值,如果不存在,则使用null
      //序列的情况
      if (column.isIdentity()) {
        sql.append(SqlHelper.getIfCacheIsNull(column, column.getColumnHolder() + ","));
      }
    }
    sql.append("</trim>");
    return sql.toString();
  }

  private void processKey(StringBuilder sql, Class<?> entityClass, MappedStatement ms, Set<EntityColumn> columnList){
    //Identity列只能有一个
    Boolean hasIdentityKey = false;
    //先处理cache或bind节点
    for (EntityColumn column : columnList) {
      if (column.isIdentity()) {
        //这种情况下,如果原先的字段有值,需要先缓存起来,否则就一定会使用自动增长
        //这是一个bind节点
        sql.append(SqlHelper.getBindCache(column));
        //如果是Identity列,就需要插入selectKey
        //如果已经存在Identity列,抛出异常
        if (hasIdentityKey) {
          //jdbc类型只需要添加一次
          if (column.getGenerator() != null && "JDBC".equals(column.getGenerator())) {
            continue;
          }
          throw new MapperException(ms.getId() + "对应的实体类" + entityClass.getCanonicalName() + "中包含多个MySql的自动增长列,最多只能有一个!");
        }
        //插入selectKey
        SelectKeyHelper.newSelectKeyMappedStatement(ms, column, entityClass, isBEFORE(), getIDENTITY(column));
        hasIdentityKey = true;
      } else if(column.getGenIdClass() != null){
        sql.append("<bind name=\"").append(column.getColumn()).append("GenIdBind\" value=\"@tk.mybatis.mapper.genid.GenIdUtil@genId(");
        sql.append("_parameter").append(", '").append(column.getProperty()).append("'");
        sql.append(", @").append(column.getGenIdClass().getCanonicalName()).append("@class");
        sql.append(", '").append(tableName(entityClass)).append("'");
        sql.append(", '").append(column.getColumn()).append("')");
        sql.append("\"/>");
      }

    }
  }
}

八、Example 用法

一、使用MBG生成的Example子类

CountryExample example = new CountryExample();
example.createCriteria().andCountrynameLike("A%");
example.or().andIdGreaterThan(100);
example.setDistinct(true);
int count = mapper.deleteByExample(example);

二、使用原始的Example类

Example example = new Example(Country.class);
example.setForUpdate(true);
example.createCriteria().andGreaterThan("id", 100).andLessThan("id",151);
example.or().andLessThan("id", 41);
List<Country> countries = mapper.selectByExample(example);

三、Example.builder 方式

Example example = Example.builder(Country.class)
  .select("countryname")
  .where(Sqls.custom().andGreaterThan("id", 100))
  .orderByAsc("countrycode")
  .forUpdate()
  .build();
List<Country> countries = mapper.selectByExample(example);

四、Weekend 方式

List<Country> selectByWeekendSql = mapper.selectByExample(
  Example.Builder(Country.class)
  .where(WeekendSqls.<Country>custom()
         .andLike(Country::getCountryname, "%a%")
         .andGreaterThan(Country::getCountrycode, "123"))
  .build());

九、TypeHandler用法

例如:数据库存放的用户地址信息为Hebei/ShijiazhuangHebei/Handan

第一步:创建实体类

public class Address implements Serializable {
  private static final long serialVersionUID = 1L;
  private String province;
  private String city;

  // 省略Getter、Setter方法。。。

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    if(province != null && province.length() > 0){
      builder.append(province);
    }
    if(city != null && city.length() > 0){
      builder.append("/").append(city);
    }
    return builder.toString();
  }
}
public class User implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  private Integer   id;
  private String    name;
  @ColumnType(typeHandler = AddressTypeHandler.class)	//指定TypeHandler
  private Address   address;	//实体类对应的地址
}

第二步:创建对应的TypeHandler

public class AddressTypeHandler extends BaseTypeHandler<Address> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Address parameter,
                                  JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter.toString());
  }

  private Address convertToAddress(String addressStr){
    if(addressStr == null || addressStr.length() == 0){
      return null;
    }
    String[] strings = addressStr.split("/");
    Address address = new Address();
    if(strings.length > 0 && strings[0].length() > 0){
      address.setProvince(strings[0]);
    }
    if(strings.length > 1 && strings[1].length() > 0){
      address.setCity(strings[1]);
    }
    return address;
  }

  @Override
  public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return convertToAddress(rs.getString(columnName));
  }

  @Override
  public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return convertToAddress(rs.getString(columnIndex));
  }

  @Override
  public Address getNullableResult(CallableStatement cs, int columnIndex)
    throws SQLException {
    return convertToAddress(cs.getString(columnIndex));
  }
}

注:上述为局部使用方法、也可以在mybatis-config.xml中配置全局的类型处理器

<typeHandlers>    
  <typeHandler handler="tk.mybatis.mapper.typehandler.AddressTypeHandler"/>
</typeHandlers>

并在实体类中配置@Column使mybtais将复杂类型作为普通类型向数据库对应字段进行映射

@Table(name = "user")
public class User implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  private Integer   id;
  private String    name;
  @Column
  private Address   address;
  //省略 setter 和 getter
}