How to build a Camel route to JPA

In this tutorial we will learn how to persist some data on a PostgreSQL Database using a Camel route which is based on the Java Persistence API (JPA) defined in a project.

The steps to complete this tutorial are:

  1. Let’s start from a maven archetype
  2. Create the entity bean
  3. Configure the persistence.xml
  4. Configure the camel-context
  5. Define the camel route
  6. enjoy running it !

1. Camel Maven archetype

Start by creating the skeleton of your Camel project using its archetype:

mvn archetype:generate                   \   -DarchetypeGroupId=org.apache.camel.archetypes  \   -DarchetypeArtifactId=camel-archetype-spring   \   -DarchetypeVersion=3.0.0 \   -DgroupId=com.example.camel.jpa \   -DartifactId=camel-jpa \   -Dversion=1.0-SNAPSHOT

Next, we will adjust the dependencies in pom.xml so that we are able to use JPA, Hibernate and JAXB for marshalling:

<dependencyManagement>     <dependencies>       <!-- Camel BOM -->       <dependency>         <groupId>org.apache.camel</groupId>         <artifactId>camel-parent</artifactId>         <version>3.0.0</version>         <scope>import</scope>         <type>pom</type>       </dependency>     </dependencies>   </dependencyManagement>    <dependencies>     <dependency>       <groupId>org.hibernate</groupId>       <artifactId>hibernate-entitymanager</artifactId>     </dependency>     <dependency>       <groupId>org.apache.camel</groupId>       <artifactId>camel-jaxb</artifactId>      </dependency>     <dependency>       <groupId>org.apache.camel</groupId>       <artifactId>camel-jpa</artifactId>     </dependency>     <dependency>       <groupId>org.postgresql</groupId>       <artifactId>postgresql</artifactId>       <version>42.2.12</version>     </dependency>      <dependency>       <groupId>org.apache.camel</groupId>       <artifactId>camel-core</artifactId>     </dependency>     <dependency>       <groupId>org.apache.camel</groupId>       <artifactId>camel-spring</artifactId>     </dependency>      <!-- logging -->     <dependency>       <groupId>org.apache.logging.log4j</groupId>       <artifactId>log4j-api</artifactId>       <scope>runtime</scope>     </dependency>     <dependency>       <groupId>org.apache.logging.log4j</groupId>       <artifactId>log4j-core</artifactId>       <scope>runtime</scope>     </dependency>     <dependency>       <groupId>org.apache.logging.log4j</groupId>       <artifactId>log4j-slf4j-impl</artifactId>       <scope>runtime</scope>     </dependency>      <!-- testing -->     <dependency>       <groupId>org.apache.camel</groupId>       <artifactId>camel-test-spring</artifactId>       <scope>test</scope>     </dependency>    </dependencies>

Now let’s move to the code.

2. Create the Entity bean

The class Person represents the entity bean that will be inserted into the database:

package com.example.camel.jpa.model;

import javax.persistence.GenerationType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@XmlRootElement
@XmlType
@Entity(name = "Person")
public class Person {
  private Long id;
  private String name;
  private String surname;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getSurname() {
    return surname;
  }

  public void setSurname(String surname) {
    this.surname = surname;
  }

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long getId() {
    return id;
  }

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

To help jaxb to create the JAXBContext we add the file jaxb.index under the resources folder of the project, using the same package structure of your Model bean:

│   └── resources │       ├── com │       │   └── example │       │       └── camel │       │           └── jpa │       │               └── model │       │                   └── jaxb.index 

The jaxb.index file will contain just the name of the Class:

Person

3. Configure the persistence.xml

In the META-INF folder we add the following persistence.xml file with the jdbc properties to connect to the PostgreSQL database:

<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"              xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"              version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">     <persistence-unit name="persistenceUnit"                       transaction-type="RESOURCE_LOCAL">          <properties>             <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" /> <!-- DB Driver -->             <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost/cameldb" /> <!-- BD Mane -->             <property name="javax.persistence.jdbc.user" value="camel" /> <!-- DB User -->             <property name="javax.persistence.jdbc.password" value="camel" /> <!-- DB Password -->              <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL95Dialect"/> <!-- DB Dialect -->             <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <!-- create / create-drop / update -->              <property name="hibernate.show_sql" value="true" /> <!-- Show SQL in console -->             <property name="hibernate.format_sql" value="true" /> <!-- Show SQL formatted -->         </properties>      </persistence-unit>   </persistence>

4. Configure the camel-context

The following file, using spring, configures a camel context. A simple java route is added to the camel context.

In the camel context we add the jpa component. It needs also an EntityManagerFactory and a JpaTransactionManager.

<?xml version="1.0" encoding="UTF-8"?> <!-- Configures the Camel Context-->  <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">     <camelContext trace="true" xmlns="http://camel.apache.org/schema/spring">     <routeBuilder ref="myroute"/>   </camelContext>   <bean id="myroute" class="com.example.camel.jpa.MyRouteBuilder"/>   <bean id="jpa" class="org.apache.camel.component.jpa.JpaComponent">     <property name="entityManagerFactory" ref="entityManagerFactory"/>     <property name="transactionManager" ref="jpaTxManager"/>   </bean>   <bean id="jpaTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">     <property name="entityManagerFactory" ref="entityManagerFactory"/>   </bean>   <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">     <property name="persistenceUnitName" value="persistenceUnit"/>   </bean> </beans> 

5. Define the camel route

The camel route is defined by MyRouteBuilder class in the configure method.

package com.example.camel.jpa;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.dataformat.JaxbDataFormat;

public class MyRouteBuilder extends RouteBuilder {
  public void configure() {
    JaxbDataFormat jxb = new JaxbDataFormat();
    jxb.setContextPath("com.example.camel.jpa.model");
    from("file:src/data?noop=true").unmarshal(jxb).to("jpa:com.example.camel.jpa.model.Person");
  }
}

A JaxbDataFormat transforms data from xml to a Person entity and the jpa component inserts it in the database. The XML is picked up from the data folder:

<?xml version="1.0" encoding="UTF-8"?><person>
        
   <name>Francesco</name>
        
   <surname>Marchioni</surname>
    
</person>

Here is the final view of the project:

camel jpa tutorial camel jpa tutorial

 

6. Enjoy running it

In order to test the project we will need a PostgreSQL database instance. For the sake of brevity we will just boot it with Docker as follows:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name camel_jpa -e POSTGRES_USER=camel -e POSTGRES_PASSWORD=camel -e POSTGRES_DB=cameldb -p 5432:5432 postgres:10.5 

Now you can build and run the project with:

mvn clean install camel:run

Check from the output that the Table Person has been created and one rows has been inserted (the trace=”true” option on the camel context make camel to trace the exchange transported in the route):

[pache.camel.spring.Main.main()] LogHelper                      INFO  HHH000204: Processing PersistenceUnitInfo [ 	name: persistenceUnit 	...] [pache.camel.spring.Main.main()] Version                        INFO  HHH000412: Hibernate Core {5.2.16.Final} [pache.camel.spring.Main.main()] Environment                    INFO  HHH000206: hibernate.properties not found [pache.camel.spring.Main.main()] Version                        INFO  HCANN000001: Hibernate Commons Annotations {5.0.1.Final} [pache.camel.spring.Main.main()] pooling                        WARN  HHH10001002: Using Hibernate built-in connection pool (not for production use!) [pache.camel.spring.Main.main()] pooling                        INFO  HHH10001005: using driver [org.postgresql.Driver] at URL [jdbc:postgresql://localhost/cameldb] [pache.camel.spring.Main.main()] pooling                        INFO  HHH10001001: Connection properties: {user=camel, password=****} [pache.camel.spring.Main.main()] pooling                        INFO  HHH10001003: Autocommit mode: false [pache.camel.spring.Main.main()] rManagerConnectionProviderImpl INFO  HHH000115: Hibernate connection pool size: 20 (min=1) [pache.camel.spring.Main.main()] Dialect                        INFO  HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL95Dialect [pache.camel.spring.Main.main()] LobCreatorBuilderImpl          INFO  HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException [pache.camel.spring.Main.main()] BasicTypeRegistry              INFO  HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@590e21d9 Hibernate:           drop table if exists Person cascade [pache.camel.spring.Main.main()] access                         INFO  HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@538522bf] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. [pache.camel.spring.Main.main()] SqlExceptionHelper             WARN  SQL Warning Code: 0, SQLState: 00000 [pache.camel.spring.Main.main()] SqlExceptionHelper             WARN  table "person" does not exist, skipping Hibernate:           drop sequence if exists hibernate_sequence [pache.camel.spring.Main.main()] SqlExceptionHelper             WARN  SQL Warning Code: 0, SQLState: 00000 [pache.camel.spring.Main.main()] SqlExceptionHelper             WARN  sequence "hibernate_sequence" does not exist, skipping Hibernate: create sequence hibernate_sequence start 1 increment 1 [pache.camel.spring.Main.main()] access                         INFO  HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@8d4116] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. Hibernate:           create table Person (        id int8 not null,         name varchar(255),         surname varchar(255),         primary key (id)     ) [pache.camel.spring.Main.main()] SchemaCreatorImpl              INFO  HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@758c039c' [pache.camel.spring.Main.main()] LocalEntityManagerFactoryBean  INFO  Initialized JPA EntityManagerFactory for persistence unit 'persistenceUnit' [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Apache Camel 3.0.0 (CamelContext: camel-1) is starting [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Tracing is enabled on CamelContext: camel-1 [pache.camel.spring.Main.main()] DefaultManagementStrategy      INFO  JMX is disabled [pache.camel.spring.Main.main()] JpaComponent                   INFO  Using EntityManagerFactory configured: org.springframework.orm.jpa.LocalEntityManagerFactoryBean@60a13630 [pache.camel.spring.Main.main()] JpaComponent                   INFO  Using TransactionManager configured on this component: org.springframework.orm.jpa.JpaTransactionManager@152da0e0 [pache.camel.spring.Main.main()] SpringCamelContext             INFO  StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html [pache.camel.spring.Main.main()] FileEndpoint                   INFO  Endpoint is configured with noop=true so forcing endpoint to be idempotent as well [pache.camel.spring.Main.main()] FileEndpoint                   INFO  Using default memory based idempotent repository with cache max size: 1000 [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 started and consuming from: file://src/data?noop=true [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Total 1 routes, of which 1 are started [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Apache Camel 3.0.0 (CamelContext: camel-1) started in 0.501 seconds [pache.camel.spring.Main.main()] BaseMainSupport                INFO  Using properties from classpath:application.properties [pache.camel.spring.Main.main()] DefaultShutdownStrategy        INFO  Starting to graceful shutdown 1 routes (timeout 300 seconds) [el-1) thread #3 - ShutdownTask] DefaultShutdownStrategy        INFO  Route: route1 shutdown complete, was consuming from: file://src/data?noop=true [pache.camel.spring.Main.main()] DefaultShutdownStrategy        INFO  Graceful shutdown of 1 routes completed in 0 seconds [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 is stopped, was consuming from: file://src/data?noop=true [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 is shutdown and removed, was consuming from: file://src/data?noop=true [pache.camel.spring.Main.main()] FileEndpoint                   INFO  Endpoint is configured with noop=true so forcing endpoint to be idempotent as well [pache.camel.spring.Main.main()] FileEndpoint                   INFO  Using default memory based idempotent repository with cache max size: 1000 [pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 started and consuming from: file://src/data?noop=true [pache.camel.spring.Main.main()] DefaultRoutesCollector         INFO  Loading additional Camel XML routes from: classpath:camel/*.xml [pache.camel.spring.Main.main()] DefaultRoutesCollector         INFO  Loading additional Camel XML rests from: classpath:camel-rest/*.xml [1) thread #4 - file://src/data] StaxConverter                  INFO  Created XMLInputFactory: com.sun.xml.internal.stream.XMLInputFactoryImpl@23759ec0. DOMSource/DOMResult may have issues with com.sun.xml.internal.stream.XMLInputFactoryImpl@23759ec0. We suggest using Woodstox. Hibernate:      select         nextval ('hibernate_sequence') Hibernate:      insert      into         Person         (name, surname, id)      values         (?, ?, ?) 

Perfect. You can also log into the Docker container image and check that the data is actually there:

$ docker ps CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES f19c77664c8d        postgres:10.5       "docker-entrypoint..."   About an hour ago   Up About an hour    0.0.0.0:5432->5432/tcp   camel_jpa  $ docker exec -it f19c77664c8d /bin/bash  # psql cameldb camel psql (10.5 (Debian 10.5-2.pgdg90+1)) Type "help" for help.  cameldb=# \dt;         List of relations  Schema |  Name  | Type  | Owner  --------+--------+-------+-------  public | person | table | camel (1 row)   cameldb=# select * from person;  id |   name    |  surname   ----+-----------+-----------   1 | Francesco | Marchioni (1 row) 

What about reading data from database?

Exercise: Try it:

public void configure() {
  JaxbDataFormat jxb = new JaxbDataFormat();
  jxb.setContextPath("com.example.camel.jpa.model");
  from("jpa:com.example.camel.jpa.model.Person?consumer.query=select o from com.example.camel.jpa.model.Person").marshal(jaxb).log("${body}");
}

Summary

We have just covered how to manage JPA marshalling using a Camel Route and the Camel JPA component.

The complete code is here:  https://github.com/fmarchioni/masterspringboot/tree/master/camel/camel-jpa