使用 JPA 注解时添加依赖:
<dependency> <groupId>io.mybatis</groupId> <artifactId>mybatis-jpa</artifactId> <version>版本号</version> </dependency>
Copied!
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
,除此之外,值只能为ASC
或DESC
@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; //忽略其他 }
Copied!
v2.2.2版本后增加了 @Entity
和 @Convert
:
@Entity @Table //可以省略 public class UserAuto { @Id private Integer id; private String userName; @Convert(converter = AddressTypeHandler.class) private Address address; //忽略其他 }
Copied!
点击查看 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); } }
Copied!
# 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; } }
Copied!
# 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; } }
Copied!
在实现中先执行了 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; } }
Copied!
在上面具体代码的实现中,会先判断 @Transient
注解,只要标记了该注解就会忽略。
然后是读取 JPA 中的 @Column
注解,支持下面的属性:
- name: 列名
- insertable: 是否可插入
- updatable: 是否可更新
- scale: 小数位数
还支持 JPA 的 @Id
设置主键字段。
支持 @OrderBy
设置字段的排序,这里有一定的限制,不能指定多个字段,只能为空(默认 ASC
)或者为 ASC
或 DESC
。
v2.2.2增加 @Convert
类型转换注解,可以将参数和查询结果转换为指定类型,需要指定 MyBatis TypeHandler 子类,默认可以用于参数中,想让查询结果列生效时需要配合 @Entity
注解开启 autoResultMap=true
。
除了上面提到的有限支持外,其他的 JPA 注解都不支持,如果有好的建议或者应用场景,可以提交 issue 或者 PR 增加额外的支持。