数据存储

数据存储表示应用程序中的数据库或任何其他数据源。

Jmix 核心子系统提供了 JpaDataStore,将 JPA 实体 存储在关系型数据库中。这是 Jmix 应用程序中主要的持久化机制,如果没有明确说明,在本手册中提到数据存储都是指 JpaDataStore

REST 数据存储 扩展组件提供了 RestDataStore,用于处理远端 Jmix 应用程序通过 REST API 开放的 DTO 实体

主数据存储

当使用 Studio 创建一个新的 Jmix 项目时,会带有有一个名为 main 的数据存储用来连接到一个关系型数据库。连接参数通过以下应用程序属性指定:

main.datasource.url = jdbc:hsqldb:file:.jmix/hsqldb/demo
main.datasource.username = sa
main.datasource.password =

main.liquibase.change-log = com/company/demo/liquibase/changelog.xml
可以使用 Studio 图形界面为数据存储定义数据库连接参数。

主应用程序类包含一个对应的 JDBC DataSource bean 声明:

@Bean
@Primary
@ConfigurationProperties("main.datasource")
DataSourceProperties dataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("main.datasource.hikari")
DataSource dataSource(final DataSourceProperties dataSourceProperties) {
    return dataSourceProperties.initializeDataSourceBuilder().build();
}

所有的 JPA 实体均默认关联到主数据存储。

附加数据存储

如果需要处理多个数据库,需要附加数据存储。

可以使用 Studio 图形界面定义附加数据存储。

每个附加数据存储都有唯一的名称,该名称在 jmix.core.additional-stores 应用程序属性指定,该属性是一个以逗号作为分隔符的列表。数据库连接的参数以数据存储的名称作为前缀。在下面的示例中,配置了一个名称为 locations 的附加数据存储:

jmix.core.additional-stores = locations,inmem

locations.datasource.url = jdbc:hsqldb:file:.jmix/hsqldb/locations
locations.datasource.username = sa
locations.datasource.password =

locations.liquibase.change-log = com/company/demo/liquibase/locations-changelog.xml

对于每个额外数据存储,Studio 会创建一个 Spring 配置类并在其中定义 JDBC DataSource 和其他相关的 bean:

@Configuration
public class LocationsStoreConfiguration {

    @Bean
    @ConfigurationProperties("locations.datasource")
    DataSourceProperties locationsDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "locations.datasource.hikari")
    DataSource locationsDataSource(@Qualifier("locationsDataSourceProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    @Bean
    LocalContainerEntityManagerFactoryBean locationsEntityManagerFactory(
            @Qualifier("locationsDataSource") DataSource dataSource,
            JpaVendorAdapter jpaVendorAdapter,
            DbmsSpecifics dbmsSpecifics,
            JmixModules jmixModules,
            Resources resources
    ) {
        return new JmixEntityManagerFactoryBean("locations", dataSource, jpaVendorAdapter, dbmsSpecifics, jmixModules, resources);
    }

    @Bean
    JpaTransactionManager locationsTransactionManager(@Qualifier("locationsEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JmixTransactionManager("locations", entityManagerFactory);
    }

    @Bean("locationsLiquibaseProperties")
    @ConfigurationProperties(prefix = "locations.liquibase")
    public LiquibaseProperties locationsLiquibaseProperties() {
        return new LiquibaseProperties();
    }

    @Bean("locationsLiquibase")
    public SpringLiquibase locationsLiquibase(@Qualifier("locationsDataSource") DataSource dataSource,
                                              @Qualifier("locationsLiquibaseProperties") LiquibaseProperties liquibaseProperties) {
        return JmixLiquibaseCreator.create(dataSource, liquibaseProperties);
    }
}

要将一个实体与附加数据存储关联,用 @Store 注解:

@Store(name = "locations")
@JmixEntity
@Table(name = "COUNTRY")
@Entity
public class Country {
当你在实体设计器中为实体选择附加数据存储时,Studio 会帮你添加 @Store 注解。

在上面的示例中,Country 实体将被存储在 locations 数据存储连接的数据库中。

自定义数据存储

自定义数据存储支持以处理 JPA 实体相同的方式处理某些 DTO 实体 - 通过使用 DataManager。如果你的 DTO 实体与自定义数据存储关联,则 DataManager 会代理定制数据存储的 CRUD 操作,并能处理其他实体对 DTO 的引用。

我们来看一下创建自定义数据存储的过程。假设一个 transient 的 Metric 实体,存储在内存中。

  1. 创建一个实现 DataStore 接口的类。该类必须是 Spring prototype bean。下面是一个能够在内存中存储不同类型实体的基本实现。

    @Component
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public class InMemoryStore implements DataStore {
        // ...
    }
  2. 创建一个实现 StoreDescriptor 接口的类。必须是一个 Spring singleton bean,其 getBeanName() 方法必须返回上一步创建的实现 DataStore bean 的名称:

    @Component
    public class InMemoryStoreDescriptor implements StoreDescriptor {
    
        @Override
        public String getBeanName() {
            return "inMemoryStore";
        }
    
        @Override
        public boolean isJpa() {
            return false;
        }
    }
  3. 将数据存储名称(在本例中为 inmem)添加到 jmix.core.additionalStores 属性:

    jmix.core.additional-stores = locations,inmem
  4. jmix.core.store-descriptor-<store_name> 属性中设置 StoreDescriptor bean 名称:

    jmix.core.store-descriptor-inmem = inMemoryStoreDescriptor
  5. 为实体添加 @Store 注解:

    @Store(name = "inmem") (1)
    @JmixEntity(annotatedPropertiesOnly = true) (2)
    public class Metric {
    1 @Store 注解指定了一个自定义数据存储。
    2 参阅 @JmixEntity 的解释。
  6. 现在你可以使用 DataManager 保存和加载实体了,它会帮你代理自定义数据库存储的 CRUD 操作:

    Metric metric = dataManager.create(Metric.class);
    metric.setName("test");
    metric.setValue(10.0);
    dataManager.save(metric);
    
    Metric metric1 = dataManager.load(Id.of(metric)).one();

    此外,如果另一个实体引用了 Metric,则在访问该引用属性时会自动加载 Metric 实例。