前言
JPA是Java Persistence API
的简称,中文名为Java持久层API,是JDK5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Sun引入新的JPA ORM规范出于两个原因: 其一, 简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下 归一。
JPA包括以下3方面的内容:
- 一套API标准。在
javax.persistence
的包下面,用来操作 实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开 发者从烦琐的JDBC和SQL代码中解脱出来。 - 面向对象的查询语言:Java Persistence Query Language(JPQL)。这是持久化操作中很重要的一个方面,通过面向 对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密 耦合。
- ORM(object/relational metadata)元数据的映射。JPA 支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间 的映射关系,框架据此将实体对象持久化到数据库表中。
JPA的宗旨是为POJO提供持久化标准规范,由此可见,经过这几 年的实践探索,能够脱离容器独立运行,方便开发和测试的理念已经 深入人心了。Hibernate 3.2+、TopLink 10.1.3以及OpenJPA都提供 了JPA的实现,以及最后的Spring的整合Spring Data JPA。目前互联 网公司和传统公司大量使用了JPA的开发标准规范,如下图所示。
对象实体映射
JPA(Java持久性API)是存储业务实体关联的实体来源。它显示了如何定义一个面向普通Java对象(POJO)作为一个实体,以及如何与管理关系实体提供一套标准。因此,下面的有些注解还是必须要去了解的,以便于更好的提高工作效率。
Thingsboard相关对象实体在org.thingsboard.server.dao.model.sql
目录下:
我们先看下Device(设备)相关对象实体存储的定义和实现。
DeviceEntity实体类,代码如下:
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.DEVICE_COLUMN_FAMILY_NAME)
public final class DeviceEntity extends AbstractDeviceEntity<Device> {
public DeviceEntity() {
super();
}
public DeviceEntity(Device device) {
super(device);
}
@Override
public Device toData() {
return super.toDevice();
}
}Copy to clipboardErrorCopied
- @Entity定义对象将会成为被JPA管理的实体,将映射到指定的数据库表。
- @Table指定数据库的表名。name: 表的名字,可选。如果不填写,系统认为实体的名字为表名。
- @TypeDef 允许您将Java对象或Jackson
JsonNode
为JPA实体属性。
AbstractDeviceEntity实体类,代码如下:
@Data
@EqualsAndHashCode(callSuper = true)
@TypeDefs({
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
@MappedSuperclass
public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> implements SearchTextEntity<T> {
@Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid")
private UUID tenantId;
@Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY, columnDefinition = "uuid")
private UUID customerId;
@Column(name = ModelConstants.DEVICE_TYPE_PROPERTY)
private String type;
@Column(name = ModelConstants.DEVICE_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.DEVICE_LABEL_PROPERTY)
private String label;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid")
private UUID deviceProfileId;
@Type(type = "jsonb")
@Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb")
private JsonNode deviceData;
......
}Copy to clipboardErrorCopied
- @MappedSuperclass基于代码复用和模型分离的思想,在项目开发中使用JPA的@MappedSuperclass注解将实体类的多个属性分别封装到不同的非实体类中。例如,数据库表中都需要id来表示编号,id是这些映射实体类的通用的属性,交给jpa统一生成主键id编号,那么使用一个父类来封装这些通用属性,并用@MappedSuperclas标识。注意:
- 标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。
- 标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。
- @Column 定义该属性对应数据库中的列名,
- name: 数据库中表的列名。可选,如果不填写认为字段名和实体属性名一样。
- unique : 是否唯一,默认false,可选
- nullable: 数据字段是否允许空。可选,默认true
- insertable: 执行insert操作的时候是否包含此字段,默认true,可选
- updateabe:执行update的时候是否包含此字段,默认true,可选
- …
BaseSqlEntity类,代码如下:
@Data
@MappedSuperclass
public abstract class BaseSqlEntity<D> implements BaseEntity<D> {
@Id
@Column(name = ModelConstants.ID_PROPERTY, columnDefinition = "uuid")
protected UUID id;
@Column(name = ModelConstants.CREATED_TIME_PROPERTY)
protected long createdTime;
@Override
public UUID getUuid() {
return id;
}
@Override
public void setUuid(UUID id) {
this.id = id;
}
@Override
public long getCreatedTime() {
return createdTime;
}
public void setCreatedTime(long createdTime) {
if (createdTime > 0) {
this.createdTime = createdTime;
}
}
}Copy to clipboardErrorCopied
@Id: 定义属性为数据库的主键,一个实体里面必须有一个。
在这里,我在这里再提供几个Thingsboard中使用到的注解。
@Enumerated:这个很好用,直接映射enum枚举类型的字段。
@Transient表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。
其他的表结构和对象实体映射都是差不多的,大家自行阅读就可以了啊!
基础查询方法
Repository的源码如下:
上面的类定义了所有Repository操作的实体和ID两个泛型参数。我们不需要继承任何接口,只要继承这个接口,就可以使用Spring Data JPA里面提供的很多约定的方法查询和注解查询。
Thingsboard数据库查询方法在org.thingsboard.server.dao.sql
目录中,如下图:
DeviceCredentials(设备认证)数据库查询方法,如下:
public interface DeviceCredentialsRepository extends CrudRepository<DeviceCredentialsEntity, UUID> {
DeviceCredentialsEntity findByDeviceId(UUID deviceId);
DeviceCredentialsEntity findByCredentialsId(String credentialsId);
}Copy to clipboardErrorCopied
CrudRepository提供了公共的通用的CRUD方法。
Spring JPA Repository的实现原理是采用动态代理的机制,所以我们介绍两种定义查询方法:
- 从方法名称中可以指定特定用于存储的查询和更新;
- 通过使用@Query手动定义的查询,这个取决于实际存储操作。
内部基础架构中有个根据方法名的查询生成器机制,对于在存储 库的实体上构建约束查询很有用。该机制方法的前缀有find…By、 read…By、query…By、count…By和get…By,从这些方法可以分析 它的其余部分(实体里面的字段)。
引入子句可以包含其他表达式, 例如在Distinct要创建的查询上设置不同的标志。然而,第一个By作 为分隔符来指示实际标准的开始。在一个非常基本的水平上,你可以定义实体性条件,并与它们串联(And和Or)。
用一句话概括,待查询功能的方法名由查询策略(关键字)、查 询字段和一些限制性条件组成。
方法的查询策略的属性表达式
属性表达式(Property Expressions)只能引用托管(泛化)实 体的直接属性,在上面那个例子中:DeviceCredentialsEntity findByDeviceId(UUID deviceId);
将使用DeviceId作为查询参数,并执行相应的SQL语句。
Device(设备)数据库查询方法,如下:
public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntity, UUID> {
@Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " +
"FROM DeviceEntity d " +
"LEFT JOIN CustomerEntity c on c.id = d.customerId " +
"WHERE d.id = :deviceId")
DeviceInfoEntity findDeviceInfoById(@Param("deviceId") UUID deviceId);
......
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
......
@Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId")
List<String> findTenantDeviceTypes(@Param("tenantId") UUID tenantId);
DeviceEntity findByTenantIdAndName(UUID tenantId, String name);
List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(UUID tenantId, UUID customerId, List<UUID> deviceIds);
List<DeviceEntity> findDevicesByTenantIdAndIdIn(UUID tenantId, List<UUID> deviceIds);
DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id);
}Copy to clipboardErrorCopied
PagingAndSortingRepository继承CrudRepository所有的基本方法,它增加了分页和排序等对查询结果进行限制的基本的、常用的、通用的一些分页方法。
我们在方法中将org.springframework.data.domain.Pageable
实例传递给查询方法,以便动态地将分页添加到静态定义的查询中。 Page知道可用的元素和页面的总数。
注解式查询方法
使用命名查询为实体声明查询是一种有效的方法,对于少量查询 很有效。一般只需要关心@Query里面的value和nativeQuery的值。使 用声明式JPQL查询有一个好处,就是启动的时候就知道语法正确与 否。
@Query分页
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
Pageable pageable);Copy to clipboardErrorCopied
直接用Page对象接收接口,参数直接用Pageable的 实现类即可。
整体持久化逻辑业务类流向
以上就是关于Thingsboard的数据持久化业务逻辑流向,我在上面的文档中,用的是设备表,其他Thingsboard的表结构和逻辑操作都是类似的。请大家仔细阅读和理解。