Last week I wrote about Red Hat Fuse and REST DSL in combination with Open JPA. This was about how to query, insert, update, and delete entities using OpenJPA. But, how about updating an existing entity object? You might poll for entities that conform to a certain condition and do a logical delete by updating a status-attribute in the entity object.
In our example, you create a new order using the REST service. Subsequently, our shipment processor may poll on the Shipments table for orders marked with the shipped status on false. Ater processing the shipment, the status should be updated to ‘true’. In addition, the ship_date should be registered.
Seems like a lot of trouble doesn’t it? You might now expect a lengthy article as a result of days of investigation. Well, it turns out: I was done faster than expected.
What do I want to do?
Let’s set up the use-case here. I extended my data model with a Shipments-table. Each Order has an associated Shipment record, that includes a shipped status and a shipment date (ship_date). I created those in a new GitHub project: animalorder-jpa-amq, where I annotated these entities as described in my previous article. Notice that Shipment has a similar foreign key relationship with Order as Order has with Customer.
The service needs to poll the Shipments table, and then enqueue a JSON representation of the Shipment, with the Order including the Customer to a JMS Queue, so a potential auditing service could audit it. Maybe in a later stage, I can make this dependent on the order total price, since I also want to make an example of the usage of predicate methods.
The update of the Shipments table should be within a combined transaction of the enqueue on the JMS queue. However, that I want to leave out of the scope of this article. That would be another story.
Let’s get on to it.
Poll the database
I already described the configuration of the JPA Persistency, including the Entity annotations. In this project, however, I extracted the JPA-related beans into a separate jpa-beans.xml definition, as I did with my CXF Bean definitions, described in my article about SOAP. The advantage of this is that it combines the definitions around a particular Camel component. You might even be able to put several of these in a jar file and override the properties with a property file.
To poll the database the Shipment entity has a NamedQuery:
To use this query to poll the database, it has to be added to a route as a from-component:
Here you see the Shipment as entityType, part of the JPA-component, and a reference to the namedQuery “getNotShippedOrders”. Important here is the property consumeDelete that is set to false in the URI. This will prevent the deletion of the processed Shipments.
Update the Shipment
To update the Shipment, you might think you’ll have to modify the attributes of the entity and then call the JPA-component again. However, its much easier than that!
To update processed entities I created the following method in Shipment:
This method is annotated as @Consumed. This is a Camel annotation of the JPA-component, so it needs the import org.apache.camel.component.jpa.Consumed. Important for this to work is that the entityType in the jpa:entityType… URI in the from of the route states the full-qualified name of the entity, like jpa:nl.vs.fuse.animalorder.entities.Shipment, as explained in this StackOverflow thread.
This is all there is to do, to have your polled rows marked as processed. Camel takes care of calling this method for every queried/polled row. You don’t need to add anything to your route.
But wouldn’t it be fun, to also explicitly update some attributes of the Shipment? To illustrate that, I added a description column to the Shipments table and a corresponding attribute to the Shipment entity class. I also created another method in the class:
The @Body annotation tells Camel to provide the Body variable, which at the moment of invoking the bean method should contain a Shipment. Notice that this method is placed in the Shipment bean, is not static, but does not work on instance attributes. Also, since this object is a JPA-entity, it is inherently added to the Spring beans registry. I did not need to explicitly register it in the Camel context. So the only thing I need to do is to call this bean method:
The OpenJPA implementation (Hibernate in this case) takes care of updating entities that are changed like this. You don’t need to invoke the jpa:nl.vs.fuse.animalorder.entities.Shipment endpoint for this.
Put the Shipment to JMS
The actual reason for this article was to show how to work with polling and updating rows from a database using JPA. But what to do with those rows? As a bonus, I also added a JMS-put operation of the queried and updated row as a JSON object.
To configure the JMS component I added a separate jms-bean.xml for the JMS configuration. It describes the following beans:
- id: jms, class: org.apache.camel.component.jms.JmsComponent; the actual JmsComponent used in the Camel route. Referencing the jmsConfig.
- id: jmsConfig, class: org.apache.camel.component.jms.JmsConfiguration; the configuration of JMS, refering to jmsPooledConnectionFactory, and jmsTransactionManager, and setting properties as transacted, requestTimeout, and cacheLevelName.
- id: jmsTransactionManager, class: org.springframework.jms.connection.JmsTransactionManager; the transaction manager refering to the jmsPooledConnectionFactory.
- id: jmsPooledConnectionFactory, class: org.apache.activemq.pool.PooledConnectionFactory; describing a pool for ConnectionFactories, refering to the jmsConnectionFactory (through a property).
- id: jmsConnectionFactory, class: org.apache.activemq.ActiveMQConnectionFactory; the actual connection factory used, providing the brokerURL (through a property).
- id: jmsSecureConnectionFactory, class: org.apache.activemq.ActiveMQSslConnectionFactory; alternative for the connection factory, to use with TLS.
Using properties (in the application.properties) in the import allows for changing the beans-definitions in different environments. For instance, for unit-testing, you could have other bean definitions.
To produce the message to the queue simply marshal the Shipment in the body to JSON, and invoke the JMS-component:
Spin up containers with docker-compose
For the database, I described a docker-compose solution in my first article about JPA. I included the same docker-compose.yml file in this project. I also added a docker-compose.yml for the JMS Broker. This spins a Docker container for Active MQ. It adds also starts a Jaeger container for later use.
Startup the database and broker containers using: $ mvn docker-compose:up. Shut them down using: $ mvn docker-compose:down.
Create the database and tables using the following scripts:
The project already has DDL scripts to create:
I also provided three scripts with a few rows of data:
For these scripts, I use Oracle SQL Developer with the MySQL Connector for Java JDBC Driver. The great Tim Hall describes nicely how to configure this. The DataModel picture at the beginning of this article is created with SQL Developer.
Start the service using: $ mvn spring-boot:run.
If you already ran the service, then update the shipments with:
This will result in the following output:
Every time you’ll execute the update statement, this output will come along. Notice by the way, that the shipped property here is false! While the shipDate is updated to today (that is March 15th, 2021). And the description is updated.
In the database, however, the shipped column is updated:
So, if you need to have this attribute set to true for later in the process, you would need to do so explicitly in the update method described earlier.
The actual reason for this article is the JPA part, the JMS part is a bonus.
To poll a database using JPA, you only need to add a namedQuery annotation to your entity class. And just reference that in the JPA from endpoint-URI of your route, setting the consumeDelete property to false. Besides that, only annotate a method that disables the from-where-clause attributes with the @Consumed annotation. As simple as that.
Updating other columns isn’t much harder than that. Just create a method with a parameter annotated with @Body to have Camel provide the body in the bean-method call.
In conclusion, writing this article took me factors more time. Saving you the time to figure that out. You’re most welcome!