Data Stores

A data store represents a database or any other source of data in your application.

Jmix provides JpaDataStore which stores JPA entities in a relational database. This is the main persistence mechanism in Jmix applications, and when we mention a data store in this manual, we always mean JpaDataStore if not explicitly stated otherwise.

Main Store

When you create a new Jmix project in Studio, it has a single data store called main that connects to a relational database. The connection parameters are specified in the following application properties:

main.datasource.url = jdbc:hsqldb:file:.jmix/hsqldb/sample
main.datasource.username = sa
main.datasource.password =
Use Studio interface to define database connection properties for a data store.

The main application class contains a corresponding JDBC DataSource bean declaration:

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

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

All JPA entities by default are associated with the main data store.

Additional Stores

In order to work with multiple databases, you need additional data stores.

Use Studio interface to define additional data stores.

Each additional store has a unique name, which is specified in the comma-separated list of the jmix.core.additionalStores application property. Parameters of the database connection have the store name as a prefix. In the examples below, the locations additional store is configured:

jmix.core.additionalStores = locations,inmem

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

For each additional store, Studio creates a Spring configuration class and defines the JDBC DataSource and other related beans in it:

@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
    public SpringLiquibase locationsLiquibase(LiquibaseChangeLogProcessor processor, @Qualifier("locationsDataSource") DataSource dataSource) {
        return JmixLiquibaseCreator.create(dataSource, new LiquibaseProperties(), processor, "locations");
    }
}

To associate an entity with an additional data store, use the @Store annotation on the entity class:

@Store(name = "locations")
@JmixEntity
@Table(name = "SAMPLE_COUNTRY")
@Entity(name = "sample_Country")
public class Country {
Studio adds @Store annotation when you select an additional data store for the entity in the entity designer.

In the example above, the Country entity will be stored in the database connected as the locations data store.

Custom Data Store

A custom data store may help you to work with some DTO entities in the same way as with JPA entities - by using DataManager. If your DTO entity is associated with a custom store, DataManager will delegate its CRUD operations to your data store and use it when resoving references to your DTOs from other entities.

Let’s consider the process of creating a custom data store. Imagine a transient Metric entity and an in-memory store for it.

  1. Create a class that implements the DataStore interface. The class must be a Spring prototype bean. The example below contains a primitive implementation that is capable of storing entities of different types in memory.

    @Component("sample_InMemoryStore")
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public class InMemoryStore implements DataStore {
        // ...
    }
  2. Create a class that implements the StoreDescriptor interface. It must be a Spring singleton bean, and its getBeanName() method must return the name of the data store implementation bean created on the previous step:

    @Component("sample_InMemoryStoreDescriptor")
    public class InMemoryStoreDescriptor implements StoreDescriptor {
    
        @Override
        public String getBeanName() {
            return "sample_InMemoryStore";
        }
    
        @Override
        public boolean isJpa() {
            return false;
        }
    }
  3. Add the data store name (inmem in this case) to the jmix.core.additionalStores property:

    jmix.core.additionalStores = locations,inmem
  4. Set the StoreDescriptor bean name in the jmix.core.storeDescriptor_<store_name> property:

    jmix.core.storeDescriptor_inmem = sample_InMemoryStoreDescriptor
  5. Add @Store annotation to the entity:

    @Store(name = "inmem") (1)
    @JmixEntity(name = "sample_Metric", annotatedPropertiesOnly = true) (2)
    public class Metric {
  6. Now you can save and load the entity using DataManager and it will delegate the CRUD operations to your custom data store:

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

    Also, if another entity has a reference to Metric, the Metric instance will be loaded automatically when accessing the reference attribute.