CUBA Database Access
DataSources
This chapter explains basic access to SQL databases by means of DataSources which can be looked up
from
a component or client context and allow to obtain
a database connection. The connection can be used to execute plain JDBC
calls or to feed additional persistence managers. Adapters for the open
source
O/R mappers Hibernate, PriDE, and OJB are available from the CUBA
website and are part of the examples of the CUBA delivery. Another
convenient and standardized way to work with SQL databases is the use
of the Java Persistence API which is alos supported by CUBA (siehe
chapter JPA).
However, the concept of DataSources is the common basis for all
persistence management techniques. Transaction management is separated
from the connection
management and direct transaction management calls on a connection
object are forbidden. The details of connection and transaction
management concepts of the EJB standard will not be discussed here but
can be found in the EJB specification and in related literature. We
will focus on the database-related functions of the CUBA API and the
configuration aspects required for the embedded container.
The following code shows how to obtain a database connection from a
global lookup:
public class
MyComponentImpl
extends
AbstractComponent implements MyComponent {
public void
operationWithDB() throws SQLException {
ClientContextI cc =
getContext().getClientContext();
DataSource
ds = cc.getDataSource("myDB");
Connection
con = ds.getConnection();
//do
something useful
con.close();
}
}
|
In an EJB system, the lookup above only requires the managed
database "myDB" to be configured as a DataSource in the
application server, just as usual. Using the component in wired mode
requires a corresponding declaration in the
application descriptor wired-application.xml:
<wired-application>
<display-name>DB
application</dispply-name>
<modules>
<wired>mycomponent.jar</wired>
</modules>
<resources>
<data-source resource-name="myDB">
<url>...</url>
<driver>...</driver>
<user>...</user>
<password>...</password>
</data-source>
</resources>
</wired-application> |
The referenced JDBC driver does not have to be XA-capable because
CUBA's wired container does not perform a 2-phase-commit protocol even
when accessing multiple databases. If a feature like this is required,
it is strongly recommended to run the components as EJBs and use the
powerful transaction management capabilities
of an application server. For simple applications and for component
testing, a simple commit is sufficient. The elements user and
password are optional.
Connection Pooling
The J2EE-style of connection management usually makes it essential to
establish connection pools which the container can allocate connections
from very fastly whenever requested by a component. As this is also
true
for CUBA's embedded container, connection pooling can be declared in
the wired-application.xml
too. Even if you run a single-threaded stand-alone application, it is
often reasonable to establish a pool of size 1 to allocate a connection
only once
in the application's lifetime and keep it allocated for fast repeated
usage.
Connection pooling is declared as an optional element in a DataSource
declaration
as follows:
<data-source
resource-name="myDB">
<url>...</url>
<driver>...</driver>
<user>...</user>
<password>...</password>
<connection-pool
min-size="1" max-size="10"/>
</data-source> |
Alternative tag sources
By default, the tag values for a DataSource declaration are taken as
specified in the wired-application.xml
which is sometimes too
static. If you require more flexibility without wanting to modify the
descriptor file, you can use the source attribute for an indirect
specification, e.g.
<data-source
resource-name="myDB">
<url source="system">db.url</url>
<driver source="system">db.driver</driver>
</data-source> |
The value "system" causes the specified value to be interpreted as the
name of a system property which to extract the actual value from. I.e.
starting a programm with a descriptor as above would allow to specify
the actual database URL by command line option "-Ddb.url=my-funny-url".
Another alternative
is to use the value "init" for the source-attribute, to refer to the
property
list passed to the constructor
of the WiredClientContext. This feature is available for all
descriptor
tags, allowing not only to flexibelize DataSource declarations but also
the
component linkage - e.g. for the integration of mock components during
testing.
Transactions
Transactions can be managed by the container or by components in CUBA.
Similar to the EJB standard, container-managed transactions are
declared on
method level in the component's deployment descriptor or by Java code
annotations. The following
code is an example of such a declaration in a descriptor, setting
transaction attribute
Required for all methods of a
component. Methods can be
addressed
by name and by wildcard but not by method signature as in an EJB
deployment
descriptor (which is a pretty questionable technique anyway).
<component-jar>
<component>
...
<component-name>hello</component-name>
<component-class>hello.HelloWorldImpl</component-class>
<transaction type="Container">
<container-transaction
attribute="Required" method-name="*"/>
</transaction>
</component>
</component-jar> |
As an alternative, the components can mark transaction boundaries
themselves, using an interface similar to UserTransaction in
J2EE. When working in an EJB environment, this is only a compatibility
interface over javax.transaction.UserTransaction.
public class
MyComponentImpl
extends
AbstractComponent implements
MyComponentContractI {
public void
operationWithTX()
throws SQLException, TransactionException
{
ClientContextI cc =
getContext().getClientContext();
DataSourceI ds = cc.getDataSource("myDB");
Connection con = ds.getConnection();
TransactionI trans =
getContext().getTransaction();
trans.begin();
//do
something useful
trans.commit();
}
}
|
Global vs. Local Lookup
The lookup of DataSources can be performed either globally or locally
which is a major difference for J2EE environments (see the EJB
specification for details). The examples above perform only global
lookups, by making
a client context out of a component context and looking up the
DataSource from
the client context. This approach ist less flexible, but it is very
common when integrating additional persistence management frameworks. Client
context lookups are always global lookups while component
context lookups are always local. I.e. they do not refer to a global
resource name space directly, but a component's local lookup tables.
These local name spaces are much faster to access in an EJB container.
Using a local lookup, the first
example of this chapter becomes a little simpler:
public class
MyComponentImpl
extends
AbstractComponent implements MyComponent {
public void
operationWithDB() throws SQLException {
DataSourceI
ds =
getContext().getDataSource("myDBref");
Connection
con = ds.getConnection();
//do
something useful
con.close();
}
}
|
However, the local reference name "myDBref" must be declared in the
component's deployment descriptor and mapped to a globally defined
DataSource. A reference declaration looks like this in a component-jar.xml:
<component-jar>
<component>
<component-name>myComp</component-name>
...
<resource-refs>
<resource-ref
ref-name="myDBref" interface="javax.sql.DataSource"/>
</resource-refs>
</component>
</component-jar> |
For a wired application, the mapping is done in the reference
section in wired-application.xml,
e.g.
<references>
<component-references component-name="hello">
<resource-refs>
<resource-ref
ref-name="myDBref" refers-global-name="myDB"/>
</resource-refs>
</component-references>
</references> |
In the EJB standard, the exact mechanism of these mappings is not
defined and therefore is solved differently by every application server
vendor. This
is why they are part of the vendor-specific deployment descriptors
which are
not generated by CUBA but must be added separately, either by
hand-coding or
by interactive deployment tools. This is a nasty weakness of the
standard.
To put it in a nutshell, this technique seems to be more complicated at
first sight. But using one apprach or the other is usually not a matter
of convenience but of requirements.
An example for database access with CUBA is available under examples/database.