This is the material accompanying the presentation of part I - Database persistence with Camel from simple to more elaborated. It covers the different demos made during the talk and is organised like that:
database
: directory containing the scripts to create the database for H2, HSQLDB and PostgreSQL RDBMSesjdbc-spring
: Maven project using camel-jdbc component and Spring XML DSLjdbc-blueprint
: Maven project using camel-jdbc component and Blueprint XML DSLsql-spring
: Maven project using camel-sql component and Spring XML DSLsql-blueprint
: Maven project using camel-sql component and Blueprint XML DSLjpa-javase
: Maven project with JUnit tests showing JPA API usage with local and JTA transactionsjpa-ds
: a bundle defining JDBC data sources to be installed in JBoss Fuse containerjpa-model
: a bundle defining JPA Persistence Units to be installed in JBoss Fuse containerjpa-ds-and-model
: data source + persistence units (however see comments below)jpa-spring
: a bundle with Spring XML based Camel routes usingjpa:
endpoints (both consumer and producer)jpa-blueprint
: a bundle with Blueprint XML based Camel routes usingjpa:
endpoints (both consumer and producer) and JTA-boundEntityManager
injected directly to Camel bean
We will refer to the root directory of camel-persistence-part1
project as $PROJECT_HOME
.
To perform tests in more realistic environments, we can leverage the power of docker to run more advanced database servers. Of course you can use existing database instances. The below examples are just here for completeness.
We can use official PostgreSQL docker image available at docker hub. You can use any of available methods to access PostgreSQL server (e.g., by mapping ports or connecting to containers IP address directly).
-
Start PostgreSQL server docker container:
$ docker run -d --name fuse-postgresql-server -e POSTGRES_USER=fuse -e POSTGRES_PASSWORD=fuse -p 5432:5432 postgres:9.6.4
-
Create
reportdb
database from thefuse-postgresql-server
container:$ docker exec -ti fuse-postgresql-server /bin/bash root@58b0d9de9c5b:/# psql -U fuse -d fuse psql (9.6.4) Type "help" for help. fuse=# create database reportdb owner fuse encoding 'utf8'; CREATE DATABASE fuse=# \c reportdb You are now connected to database "reportdb" as user "fuse". reportdb=# create schema report; CREATE SCHEMA reportdb=# \q
-
Create
reportdb2
database from thefuse-postgresql-server
container (for XA purposes):$ docker exec -ti fuse-postgresql-server /bin/bash root@58b0d9de9c5b:/# psql -U fuse -d fuse psql (9.6.4) Type "help" for help. fuse=# create database reportdb2 owner fuse encoding 'utf8'; CREATE DATABASE fuse=# \c reportdb2 You are now connected to database "reportdb2" as user "fuse". reportdb2=# create schema report; CREATE SCHEMA reportdb2=# \q
-
Initialize database
reportdb
by creating schema, table and populating the table with data.$ cd $PROJECT_HOME/database $ docker cp src/config/postgresql/reportdb-postgresql-script.sql fuse-postgresql-server:/tmp $ docker exec -ti fuse-postgresql-server /bin/bash root@58b0d9de9c5b:/# psql -U fuse -d reportdb -f /tmp/reportdb-postgresql-script.sql ... DROP SCHEMA CREATE SCHEMA CREATE TABLE INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1
-
Configure PostgreSQL database to allow XA transactions by setting
max_prepared_transactions
to the value equal or greater thanmax_connections
setting (100
in the case ofpostgres:9.6.4
image).root@58b0d9de9c5b:/# sed -i 's/^#max_prepared_transactions = 0/max_prepared_transactions = 200/' /var/lib/postgresql/data/postgresql.conf
-
Restart
fuse-postgresql-server
container. Your PostgreSQL database is ready to use.
These examples can be run using camel-test-spring and camel-test-blueprint respectively. Examples will run outside of JBoss Fuse server and require only running database server.
For jdbc-spring
example, run:
$ cd $PROJECT_HOME/jdbc-spring
$ mvn clean compile camel:run
We will see Camel context being started and after initial delay of 4 secods, every 20 seconds we'll see list of incidents being printed in console.
Additionally we can trigger key-from-file
route simply by dropping comma-separated list of incident references
to a file inside $PROJECT_HOME/jdbc-spring/target/data
directory. Here's example:
$ cd $PROJECT_HOME/jdbc-spring
$ echo -n 002,004 > target/data/keys
We can also run 2nd Camel context using:
$ cd $PROJECT_HOME/jdbc-spring
$ mvn clean compile camel:run -Pcontext2
For jdbc-blueprint
we use Blueprint XML DSL and because of more discovery nature of camel-test-blueprint
, we
use only camelContext1.xml
example. We can run this example using:
$ cd $PROJECT_HOME/jdbc-blueprint
$ mvn clean package camel:run
The invocation is slightly different - package
goal has to be invoked, so we have proper OSGi bundle - or rather
proper OSGi bundle MANIFEST.MF
file generated in target/classes/META-INF
directory. This allows camel-test-blueprint
to pick up target/classes
directory as correct OSGi bundle.
These examples also can be run outside JBoss Fuse server using camel-test-spring and camel-test-blueprint respectively.
This time we can also create new records in database by sending content of files through Camel route to the database
using camel-spring
component.
For sql-spring
example, run:
$ cd $PROJECT_HOME/sql-spring
$ mvn clean compile camel:run
This will start the route and print the content of t_incident
table every 20 seconds. We can create new incidents
in two ways:
-
by invoking
insert-from-file-using-bean
route that prepares new record in bean method:$ cd $PROJECT_HOME/sql-spring $ cp data/key.txt target/datainsert
-
by invoking
insert-from-file-using-split
route that prepares new record simply by splitting comma-separated values from given file$ cd $PROJECT_HOME/sql-spring $ cp data/keyParams.txt target/datainsertparams
For sql-blueprint
the examples are run almost like the ones in sql-spring
, except that package
phase has
to be invoked.
$ cd $PROJECT_HOME/sql-blueprint
$ mvn clean package camel:run
This project should be examined before running jpa-spring
and jpa-blueprint
(and supporting jpa-ds
and jpa-model
).
jpa-javase
shows few canonical ways of using JPA API with Hibernate JPA provider.
According to JPA 2.0 specification (JSR 317), JPA can successfully be used outside application server - in pure JavaSE
environment. Using JPA this way is described as application-managed Entity Manager.
In this mode, it's the role of application developer to create EntityManagerFactory
and obtain EntityManager
. (In
application server, i.e., when using container-managed Entity Manager, EntityManager
instance is injected to application
code using @javax.persistence.PersistenceContext
annotation).
jpa-javase
provides 3 unit tests that illustrate few uses cases related to JPA:
com.fusesource.examples.persistence.part1.DiscoveryTest.discovery()
: a tiny example of using JPA discover API, where we can check what is the configured (discovered)javax.persistence.spi.PersistenceProvider
instance.com.fusesource.examples.persistence.part1.JavaSETest.bootstrapAndUseApplicationManagedResourceLocalEntityManager()
: Full example usingtransaction-type="RESOURCE_LOCAL"
that shows how to:- create dbcp2
javax.sql.DataSource
- use
javax.persistence.Persistence.createEntityManagerFactory()
to create application-managed EMF using provided properties - use
javax.persistence.EntityManagerFactory.createEntityManager()
to obtain EntityManager - use
javax.persistence.EntityTransaction
API to manually demarcate JPA transactions according to declaredtransaction-type="RESOURCE_LOCAL"
- use
javax.persistence.EntityManager.persist()
to simply use JPA API - use plain JDBC code to verify that record was indeed persisted to database
- create dbcp2
com.fusesource.examples.persistence.part1.JavaSETest.bootstrapAndUseApplicationManagedJTAEntityManager()
: Full example usingtransaction-type="JTA"
that shows how to:- create fully functional
javax.transaction.TransactionManager
/javax.transaction.UserTransaction
instances using aries.transaction.manager - create 2
org.postgresql.xa.PGXADataSource
instances for XA-aware access to PostgreSQL database - create 2
javax.sql.DataSource
instances that are using JCA API to interact with aries.jdbc and provide XA-aware, JTA-enlisting, pooling data sources - use
javax.persistence.Persistence.createEntityManagerFactory()
to create application-managed EMF using provided properties - use
javax.persistence.EntityManagerFactory.createEntityManager()
to obtain EntityManager - use
javax.transaction.UserTransaction
API to manually demarcate JPA transactions according to declaredtransaction-type="JTA"
- use
javax.persistence.EntityManager.joinTransaction()
to tie JPA to JTA - use
javax.persistence.EntityManager.persist()
to simply use JPA API - use plain JDBC to insert row to 2nd database within the same global JTA transaction
- use plain JDBC code to verify that record was indeed persisted to both databases
- create fully functional
These examples run inside JBoss Fuse container, because mvn camel:run
, which underneath uses trimmed-down OSGi registry
(Felix Connect, formerly known as PojoSR) doesn't work well with aries.jpa.
Before installing any logic bundles, we'll create/use 2 supporting bundles:
jpa-ds
which exposesjavax.sql.(XA)DataSource
OSGi services for use by other bundlesjpa-model
which exposesjavax.persistence.EntityManagerFactory
OSGi service to be used by camel routes, components and any bundle that requires access to EMF and EntityManager
The separation of data source and JPA model bundles is important in OSGi environment, so we won't get into situation when none of the services will be published and will be blocked waiting for each other. More precisely - common mistake is to create a Blueprint bundle that both:
- declare a
<bean>
forjavax.sql.DataSource
and - declare a
<bean>
which needsjavax.persistence.EntityManagerFactory
service, even ifMETA-INF/persistence.xml
is in another bundle
The reason is that EntityManagerFactory
will never get published when DataSource
is not available, and Blueprint container
will never publish javax.sql.DataSource
while waiting for javax.persistence.EntityManagerFactory
.
It is (almost) fine to have single bundle publishing both DataSource OSGi services (either from Spring XML or Blueprint XML)
and at the same time containing Meta-Persistence: META-INF/persistence.xml
Manifest header - this bundle will be
extended
by two extenders
:
- aries.blueprint.core that'll run BlueprintContainer or org.springframework.osgi.extender that'll run Spring context
- org.apache.aries.jpa.container that'll instantiate
EntityManagerFactory
fromMETA-INF/persistence.xml
descriptor
An example of such bundle is shown in jpa-ds-and-model
project.
The problem may arise when hibernate.hbm2ddl.auto
property is used in META-INF/persistence.xml
- because data
source is immediately needed during construction of EntityManagerFactory
. That's why it's preferred to separate
data source and entity manager factory bundles.
Here's a list of steps required to install jpa-ds
bundle in fresh Fuse installation:
install -s mvn:org.postgresql/postgresql/42.1.4
install -s mvn:org.apache.commons/commons-pool2/2.4.2
install -s mvn:org.apache.commons/commons-dbcp2/2.1.1
features:install jndi transaction connector
install -s mvn:com.fusesource.examples.camel-persistence-part1/jpa-ds/1.0
After installing and starting jpa-ds
, we can check the the services exposed by it:
JBossFuse:karaf@root> ls com.fusesource.examples.camel-persistence-part1.jpa-ds
FuseSource :: Examples :: Camel Persistence :: JPA Data Source (320) provides:
------------------------------------------------------------------------------
objectClass = [javax.sql.DataSource]
osgi.jndi.service.name = jdbc/reportdb
service.id = 705
...
----
aries.managed = true
objectClass = [javax.sql.DataSource]
osgi.jndi.service.name = jdbc/reportdb
service.id = 706
service.ranking = 1000
...
----
objectClass = [javax.sql.XADataSource]
osgi.jndi.service.name = jdbc/reportdbxa
service.id = 707
...
----
aries.managed = true
objectClass = [javax.sql.DataSource]
osgi.jndi.service.name = jdbc/reportdbxa
service.id = 708
service.ranking = 1000
...
----
objectClass = [..., org.springframework.context.ApplicationContext, ...]
service.id = 709
...
What can be seen in the above output is that we have two pairs of data sources - for both jdbc/reportdb
and
jdbc/reportdbxa
we have original service and a service with aries.managed=true
property and higher ranking.
This is why, when looking up a service with given name, we'll get managed version that can perform JTA enlistment
(if needed).
Here's how jpa-model
should be installed:
features:install jpa hibernate
install -s mvn:com.fusesource.examples.camel-persistence-part1/jpa-model/1.0
After installing and starting jpa-model
, we can check the the services exposed by it:
JBossFuse:karaf@root> ls com.fusesource.examples.camel-persistence-part1.jpa-model
FuseSource :: Examples :: Camel Persistence :: JPA Model (321) provides:
------------------------------------------------------------------------
objectClass = [javax.persistence.EntityManagerFactory]
org.apache.aries.jpa.container.managed = true
org.apache.aries.jpa.default.unit.name = false
osgi.unit.name = reportincident-jta
osgi.unit.provider = org.hibernate.ejb.HibernatePersistence
osgi.unit.version = 1.0.0
service.id = 677
----
objectClass = [javax.persistence.EntityManagerFactory]
org.apache.aries.jpa.container.managed = true
org.apache.aries.jpa.default.unit.name = false
osgi.unit.name = reportincident-local
osgi.unit.provider = org.hibernate.ejb.HibernatePersistence
osgi.unit.version = 1.0.0
service.id = 678
We can see two javax.persistence.EntityManagerFactory
services for two persistence units from META-INF/persistence.xml
.
Let's install jpa-spring
bundle that provides Spring XML based camel routes using JPA endpoints and RESOURCE_LOCAL transactions:
features:install spring-orm
features:install camel-jpa
install -s mvn:com.fusesource.examples.camel-persistence-part1/jpa-spring/1.0
We can see contexts running:
JBossFuse:karaf@root> context-list
Context Status Total # Failed # Inflight # Uptime
------- ------ ------- -------- ---------- ------
camel Started 76 0 0 12 minutes
JBossFuse:karaf@root> route-list
Context Route Status Total # Failed # Inflight # Uptime
------- ----- ------ ------- -------- ---------- ------
camel create-incident Started 0 0 0 12 minutes
camel rollback-incident Started 0 0 0 12 minutes
camel trigger-database Started 50 0 0 12 minutes
camel trigger-database-named-query Started 26 0 0 12 minutes
trigger-database
and trigger-database-named-query
routes poll from database and print the content of report.t_incident
tables.
In order to test create-incident
and rollback-incident
routes, we can:
$ cd $PROJECT_HOME/jpa-spring/data
$ cp csv.txt $FUSE_HOME/data/camel/datainsert
$ cp csv-one-record.txt $FUSE_HOME/data/camel/datainsert
$ cp csv-notinserted.txt $FUSE_HOME/data/camel/datainsertrollback
Finally jpa-blueprint
is the ultimate showcase of integrating JPA, Hibernate, JTA and Camel. It can be installed like this:
features:install spring-orm
features:install camel-jpa
install -s mvn:com.fusesource.examples.camel-persistence-part1/jpa-blueprint/1.0
jpa-blueprint
differs from jpa-spring
in these:
- it references persistence unit with
transaction-type="JTA"
- it doesn't create own
PlatformTransactionManager
and instead references global (JTA) transaction manager - it injects
EntityManagerFactory
tojpa
component using<jpa:unit>
custom element - it injects JTA-scoped
EntityManager
instance tocom.fusesource.examples.persistence.part1.camel.ProcessIncidents
Camel bean using<jpa:context>
custom element
In addition to 4 routes identical to the ones provided by jpa-spring
, there's additional route create-incident-using-jpa
which
invokes Camel bean which has JTA-scoped EntityManager
instance injected. We can send incident messages to it using:
$ echo -n 'panic!' > $FUSE_HOME/data/camel/datainsertjpa/incident
org.apache.aries.jpa.container bundle:
org.apache.aries.jpa.container.parsing.PersistenceDescriptorParser
service implemented byorg.apache.aries.jpa.container.parsing.impl.PersistenceDescriptorParserImpl
- parsesMETA-INF/persistence.xml
descriptors intoorg.apache.aries.jpa.container.parsing.impl.PersistenceUnitImpl
objects usingorg.apache.aries.jpa.container.parsing.impl.JPAHandler
org.apache.aries.jpa.container.impl.PersistenceBundleManager
- locates, parses and manages persistence units defined in OSGi bundles. ContainsMap<Bundle, EntityManagerFactoryManager>
Parsed org.apache.aries.jpa.container.parsing.impl.PersistenceUnitImpl
is changed into
org.apache.aries.jpa.container.unit.impl.ManagedPersistenceUnitInfoImpl
and getPersistenceUnitInfo()
is called
to get javax.persistence.spi.PersistenceUnitInfo
instance which is then finally used as argument to
javax.persistence.spi.PersistenceProvider.createContainerEntityManagerFactory()
.
org.apache.aries.jpa.container.context bundle:
- provides contextual (proxied, thread-local) access to context-related
javax.persistence.EntityManager
instance for Blueprint XMLs which use<jpa:context>
custom element.