使用 JPA 注解时添加依赖:

<dependency>
  <groupId>io.mybatis</groupId>
  <artifactId>mybatis-jpa</artifactId>
  <version>版本号</version>
</dependency>

最新版本号:

2.0 版本之后,使用 JPA 注解时,最简单的情况下不需要给实体添加 @Table 注解,给主键添加 @Id注解即可,不需要给所有字段添加其他注解。

# 3.2.1 注解介绍

对 JPA 注解的支持非常有限,仅支持以下注解的部分属性:

  • @Table: 用于实体类,必须指定该注解,否则不会被识别为实体类

    • name: 设置表名
    • catalog: 设置表的目录(v2.2.2增加)
    • schema: 设置表的模式(v2.2.2增加)
  • @Entity(v2.2.2增加): 配置该注解相当于开启默认注解中的 autoResultMap=true,配合 @Convert 注解使用时可以对查询结果进行转换

  • @Column: 设置字段的列信息

    • name: 列名
    • insertable: 是否可插入
    • updatable: 是否可更新
    • scale: 小数位数
  • @Id: 主键标记

  • @Transient: 排除字段,没有标记该字段的所有字段都会作为表字段处理

  • @OrderBy: 设置字段排序,空值时为 ASC,除此之外,值只能为 ASCDESC

  • @Convert(v2.2.2增加): 类型转换注解,可以将查询结果转换为指定类型,需要指定 MyBatis TypeHandler 子类,默认可以用于参数中,想让查询结果列生效时需要配合 @Entity 注解开启 autoResultMap=true

提示

除了上面提到的有限支持外,其他的 JPA 注解都不支持,如果有好的建议或者应用场景,可以提交 issue 或者 PR 增加额外的支持。

除了 JPA 这几个注解外,还支持 @Entity.Table@Entity.Column@Entity.Transient(v2.2.2支持) 注解,这些注解可以混用,相同含义的配置中,JPA优先级更高。

# 3.2.2 JPA 示例

简单示例:

@Table(name = "user") //可以省略
public class User {
  @Id
  private Long   id;
  @Column(name = "name")
  private String username;
  private String sex;
  //忽略其他
}

v2.2.2版本后增加了 @Entity@Convert

@Entity
@Table //可以省略
public class UserAuto {
  @Id
  private Integer id;
  private String  userName;
  @Convert(converter = AddressTypeHandler.class)
  private Address address;
  //忽略其他
}
点击查看 Address 和 AddressTypeHandler 代码
public static class Address {
  private String sheng;
  private String shi;

  public String getSheng() {
    return sheng;
  }

  public void setSheng(String sheng) {
    this.sheng = sheng;
  }

  public String getShi() {
    return shi;
  }

  public void setShi(String shi) {
    this.shi = shi;
  }

  @Override
  public String toString() {
    return sheng + "---" + shi;
  }
}

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

  private Address valueToAddress(String value) {
    String[] split = value.split("/");
    Address address = new Address();
    address.setSheng(split[0]);
    address.setShi(split[1]);
    return address;
  }

  @Override
  public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
    String string = rs.getString(columnName);
    return string == null ? null : valueToAddress(string);
  }

  @Override
  public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    String string = rs.getString(columnIndex);
    return string == null ? null : valueToAddress(string);
  }

  @Override
  public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    String string = cs.getString(columnIndex);
    return string == null ? null : valueToAddress(string);
  }
}

# 3.2.3 扩展实现

# 3.2.3.1 查找实体类类型

首先通过 JpaEntityClassFinder 支持添加了 javax.persistence.Table 注解的实体类。

/**
 * 支持识别带有 @javax.persistence.Table 的实体类或者不带任何注解的POJO
 *
 * @author liuzh
 */
public class JpaEntityClassFinder extends GenericEntityClassFinder {

  @Override
  public boolean isEntityClass(Class<?> clazz) {
    //带注解或不是简单类型和枚举的都算实体
    return clazz.isAnnotationPresent(Table.class)
            || (!clazz.isPrimitive()
            && !clazz.isInterface()
            && !clazz.isArray()
            && !clazz.isAnnotation()
            && !clazz.isEnum() && !SimpleTypeUtil.isSimpleType(clazz));
  }

  @Override
  public int getOrder() {
    return super.getOrder() + 100;
  }
}

# 3.2.3.2 构建 EntityTable 信息

然后通过 JpaEntityTableFactory 读取 javax.persistence.Table 注解的配置信息。

/**
 * 通过 SPI 工厂扩展 EntityColumn 和 EntityTable
 *
 * @author liuzh
 */
public class JpaEntityTableFactory implements EntityTableFactory {

  @Override
  public EntityTable createEntityTable(Class<?> entityClass, Chain chain) {
    EntityTable entityTable = chain.createEntityTable(entityClass);
    if (entityTable == null) {
      entityTable = EntityTable.of(entityClass);
    }
    if (entityClass.isAnnotationPresent(Table.class)) {
      Table table = entityClass.getAnnotation(Table.class);
      if (!table.name().isEmpty()) {
        entityTable.table(table.name());
      }
      if(!table.catalog().isEmpty()) {
        entityTable.catalog(table.catalog());
      }
      if(!table.schema().isEmpty()) {
        entityTable.schema(table.schema());
      }
    } else if (Utils.isEmpty(entityTable.table())) {
      //没有设置表名时,默认类名转下划线
      entityTable.table(Style.getDefaultStyle().tableName(entityClass));
    }
    //使用 JPA 的 @Entity 注解作为开启 autoResultMap 的标志,可以配合字段的 @Convert 注解使用
    if(entityClass.isAnnotationPresent(Entity.class)) {
      entityTable.autoResultMap(true);
    }
    return entityTable;
  }

  @Override
  public int getOrder() {
    return EntityTableFactory.super.getOrder() + 100;
  }

}

在实现中先执行了 chain.createEntityTable 调用工厂链的后续方法创建 EntityTable,因此 JPA 注解实现支持和 @Entity.Table 注解混用。

如果 chain 方法没有返回 EntityTable,就根据 javax.persistence.Table 注解创建。

如果返回了 EntityTable,就用 javax.persistence.Table 注解的配置覆盖 EntityTable 的配置,也就是 JPA 注解优先级高于默认的 @Entity.Table 注解。

# 3.2.3.3 构建 EntityColumn 信息

最后通过 JpaEntityColumnFactory 构建 EntityColumn 信息,这部分逻辑和上面类似,仍然支持 @Entity.Column 注解,而且 JPA 的优先级更高。

public class JpaEntityColumnFactory implements EntityColumnFactory {

  @Override
  public Optional<List<EntityColumn>> createEntityColumn(EntityTable entityTable, EntityField field, Chain chain) {
    //代码太长,省略
  }

  @Override
  public int getOrder() {
    return EntityColumnFactory.super.getOrder() + 100;
  }

}

在上面具体代码的实现中,会先判断 @Transient 注解,只要标记了该注解就会忽略。

然后是读取 JPA 中的 @Column 注解,支持下面的属性:

  • name: 列名
  • insertable: 是否可插入
  • updatable: 是否可更新
  • scale: 小数位数

还支持 JPA 的 @Id 设置主键字段。

支持 @OrderBy 设置字段的排序,这里有一定的限制,不能指定多个字段,只能为空(默认 ASC)或者为 ASCDESC

v2.2.2增加 @Convert 类型转换注解,可以将参数和查询结果转换为指定类型,需要指定 MyBatis TypeHandler 子类,默认可以用于参数中,想让查询结果列生效时需要配合 @Entity 注解开启 autoResultMap=true

除了上面提到的有限支持外,其他的 JPA 注解都不支持,如果有好的建议或者应用场景,可以提交 issue 或者 PR 增加额外的支持。