How can I provide TransactionManager while using additional DataStore?

Hi guys,

I introduced a 2nd data source for my jmix for a different screen using dynamoDB on AWS.

I think I managed to have a secondary data manager,

so;

I’m stuck providing this TransactionManager;

return applicationContext.getBean(storeName + "TransactionManager", PlatformTransactionManager.class);

It looks up for my TransactionManager but I don’t have any and I don’t know how to provide it.

Could you please help me or share with me any opinion?

App starts up successfully, only throws exception when I tried to open the new screen which is using the additional data store that is dynamoDB.

I’d appreciate any help.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘admin_dynamoDBDataStoreTransactionManager’ available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:872) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1160) ~[spring-context-5.3.18.jar:5.3.18]
at io.jmix.core.impl.TransactionManagerLocator.getTransactionManager(TransactionManagerLocator.java:35) ~[jmix-core-1.2.2.jar:na]
at io.jmix.data.StoreAwareLocator.getTransactionManager(StoreAwareLocator.java:55) ~[jmix-data-1.2.2.jar:na]
at io.jmix.eclipselink.impl.JpaDataStore.beginLoadTransaction(JpaDataStore.java:295) ~[jmix-eclipselink-1.2.2.jar:na]
at io.jmix.core.datastore.AbstractDataStore.loadList(AbstractDataStore.java:124) ~[jmix-core-1.2.2.jar:na]
at io.jmix.core.impl.UnconstrainedDataManagerImpl.loadList(UnconstrainedDataManagerImpl.java:112) ~[jmix-core-1.2.2.jar:na]
at io.jmix.ui.model.impl.CollectionLoaderImpl._load(CollectionLoaderImpl.java:92) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.model.impl.CollectionLoaderImpl.load(CollectionLoaderImpl.java:75) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.component.dataloadcoordinator.OnFrameOwnerEventLoadTrigger.load(OnFrameOwnerEventLoadTrigger.java:48) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.component.dataloadcoordinator.OnFrameOwnerEventLoadTrigger.lambda$new$0(OnFrameOwnerEventLoadTrigger.java:39) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.core.common.event.EventHub.publish(EventHub.java:170) ~[jmix-core-1.2.2.jar:na]
at io.jmix.ui.screen.Screen.fireEvent(Screen.java:124) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.screen.UiControllerUtils.fireEvent(UiControllerUtils.java:58) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.sys.ScreensImpl.fireScreenBeforeShowEvent(ScreensImpl.java:1364) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.sys.ScreensImpl.show(ScreensImpl.java:357) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.screen.Screen.show(Screen.java:306) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.navigationhandler.ScreenNavigationHandler.openScreen(ScreenNavigationHandler.java:245) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.navigationhandler.ScreenNavigationHandler.navigate(ScreenNavigationHandler.java:146) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.navigationhandler.ScreenNavigationHandler.doHandle(ScreenNavigationHandler.java:129) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.ScreenNavigator.handleScreenNavigation(ScreenNavigator.java:46) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.UrlChangeHandler.__handleUrlChange(UrlChangeHandler.java:132) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.UrlChangeHandler.handleUrlChange(UrlChangeHandler.java:113) ~[jmix-ui-1.2.2.jar:na]
at io.jmix.ui.navigation.UrlChangeHandler$$FastClassBySpringCGLIB$$4834d39f.invoke() ~[jmix-ui-1.2.2.jar:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.18.jar:5.3.18]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.3.18.jar:5.3.18]
at io.jmix.ui.navigation.UrlChangeHandler$$EnhancerBySpringCGLIB$$fcc3ebee.handleUrlChange() ~[jmix-ui-1.2.2.jar:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:709) ~[vaadin-server-8.14.1-1-jmix.jar:8.14.1-1-jmix]
… 93 common frames omitted

Thank you guys,
Emre

Hi Emre,

DynamoDB integration is possible in Jmix. It is possible to use the custom data store as you mentioned, but I found it a little problematic since the data access model is quite different. In the custom data store, you would have to find out the intended filtering based on the JPQL query and then apply the filtering in Dynamo terms.

Instead, you can also just use a custom service together with a DTO entity. This solution seems to be quite a bit simpler.

I have prepared an example on how to do that: https://github.com/mariodavid/jmix-petclinic-dynamodb


Jmix - DynamoDB Integration

This example application shows how to integrate a DynamoDB table into a Jmix application.

The example scenario is the following:

For Visits, it is possible to keep a log book of VisitLog entries. Those Log entries should be stored in DynamoDB.

1. Spring Data DynamoDB dependency

There is a Spring Data DynamoDB integration out there, which we can leverage to ease the actual interaction with DynamoDB.

Add the following dependency to your Jmix application:

dependencies {
    // ...
    implementation 'com.github.derjust:spring-data-dynamodb:5.1.0'
}

2. DynamoDB Config

To define the DynamoDB beans that should be used, create a DynamoDBConfig class in your application like this:

@Configuration
@EnableDynamoDBRepositories(basePackageClasses = VisitLogRepository.class)
public class DynamoDBConfig {


	@Primary
	@Bean
	public DynamoDBMapperConfig dynamoDBMapperConfig() {
		return DynamoDBMapperConfig.DEFAULT;
	}

	@Primary
	@Bean
	public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig config) {
		return new DynamoDBMapper(amazonDynamoDB, config);
	}

	@Primary
	@Bean
	public AmazonDynamoDB amazonDynamoDB() {
		return AmazonDynamoDBClientBuilder.standard()
				.withRegion(Regions.US_EAST_1).build();
	}
}

3. VisitLog DTO entity

Now let’s create a DTO entity for the Visit log. Use the annotation from dynamodb to indicate which table should be used
and which properties are considered to be the hash key:

@DynamoDBTable(tableName = "visit-log")
@JmixEntity(name = "petclinic_VisitLog", annotatedPropertiesOnly = true)
public class VisitLog {

    @JmixProperty
    @JmixGeneratedValue
    @JmixId
    private UUID id;

    @JmixProperty(mandatory = true)
    @NotNull
    private UUID visitId;

    @JmixProperty
    private String title;

    @InstanceName
    @JmixProperty
    private String description;

    @DynamoDBAttribute
    public UUID getVisitId() {
        return visitId;
    }

    public void setVisitId(UUID visitId) {
        this.visitId = visitId;
    }

    @DynamoDBAttribute
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @DynamoDBAttribute
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @DynamoDBHashKey
    @DynamoDBAutoGeneratedKey
    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }
}

4. Create Spring Data Repository

Next up we can create our Spring Data repository in order to interact with the table. K

import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;

@EnableScan
public interface VisitLogRepository extends CrudRepository<VisitLog, UUID> {

    List<VisitLog> findByVisitId(UUID visitId);
}

It is necessary to not let the Jmix Spring Data integration interfere with the DynamoDB Spring Data repositories.
We can achieve that by limiting the JmixDataRepositories to the subpackage: repository. The VisitLogRepository is not located
in this package. With that it will not be recognised by the Jmix Spring Data implementation.

@EnableJmixDataRepositories("io.jmix.petclinic.repository")
public class JmixPetclinicApplication {
    
}

To activate the VisitLogRepository, we need to annotate our DynamoDBConfig to enable scanning and mention VisitLogRepository class:

@EnableDynamoDBRepositories(basePackageClasses = VisitLogRepository.class)
public class DynamoDBConfig {
    // ...
}

5. Use Repository in application Code

For easier integration additional we can create a VisitLogService, that acts as the logic layer for interacting with the Table:


@Component("petclinic_VisitLogService")
public class VisitLogService {

    @Autowired
    VisitLogRepository visitLogRepository;

    public List<VisitLog> findByVisit(Visit visit) {
        return visitLogRepository.findByVisitId(visit.getId());
    }

    public void saveLogEntry(VisitLog visitLog) {
        visitLogRepository.save(visitLog);
    }
}

6. Integrate with Jmix UI

Show Visit Log Entries

1-visit-log-entries-button

2-visit-log-browse

Now it is possible to use the new DTO entity in our application. It is possible to generate Screens for the DTO entity.

In this example you will find the VisitLogBrowse and VisitLogEditor accordingly.

The browse screen loads the entites via the Service during initialization of the screen:

public class VisitLogBrowse extends StandardLookup<VisitLog> {

    private Visit visit;
    @Autowired
    private CollectionContainer<VisitLog> visitLogsDc;
    @Autowired
    private VisitLogService visitLogService;
    @Autowired
    private Metadata metadata;

    @Subscribe
    public void onBeforeShow(BeforeShowEvent event) {
        visitLogsDc.setItems(
            visitLogService.findByVisit(visit)
        );
    }
}

The visit parameter is passed in by the VisitBrowse screen, where this screen is called from. The setItems method sets
the values into the data container and utilises the VisitLogService for loading the data.

Create Visit Log Entries

3-create-visit-log
4-saved-visit-logs

When creating the Visit Log Entries, we need change the way it is stored. For that we can use the commit delegate handler:

@UiController("petclinic_VisitLog.edit")
@UiDescriptor("visit-log-edit.xml")
@EditedEntityContainer("visitLogDc")
public class VisitLogEdit extends StandardEditor<VisitLog> {

    @Autowired
    VisitLogService visitLogService;

    @Install(target = Target.DATA_CONTEXT)
    private Set<Object> commitDelegate(SaveContext saveContext) {
        visitLogService.saveLogEntry(getEditedEntity());
        return Set.of(getEditedEntity());
    }
}

The actual storage is done by the VisitLogService once again.


I hope this helps to see how the integration with DynamoDB is possible.

Cheers
Mario

2 Likes