public interface DataStorage
Modifier and Type | Method and Description |
---|---|
<R> R |
executeWithConnection(java.util.function.Function<java.sql.Connection,R> function)
Executes the given function and returns its result.
|
<R> R |
executeWithConnection(IContext context,
java.util.function.Function<java.sql.Connection,R> function) |
<R> R executeWithConnection(java.util.function.Function<java.sql.Connection,R> function)
The function being passed must not throw checked exceptions. If you want to throw an exception, for example to rollback a microflow
action, you must throw an unchecked exception, like a java.lang.RuntimeException
.
Note that this method is only provided for convenience, for example to enable execution of database-specific SQL SELECT statements, or calling stored procedures created in the same database (schema), with a direct connection to the Mendix database.
DISCLAIMER
ANY USAGE OF THIS METHOD ON THE MENDIX DATABASE WITH DATA MUTATING SQL STATEMENTS LIKE (BUT NOT LIMITED TO) DELETE AND UPDATE, OR SCHEMA MUTATING SQL STATEMENTS LIKE (BUT NOT LIMITED TO) DROP AND ALTER, IS ENTIRELY AT YOUR OWN RISK. MENDIX CANNOT GUARANTEE THE CONSISTENCY OF THE MENDIX DATABASE DATA OR SCHEMA UNDER SUCH USAGE.
One should be especially careful with the Mendix administration tables in the MENDIXSYSTEM$ and SYSTEM$ module namespaces. Please be aware that the existence of Mendix administration tables, and the naming conventions in the database, are a Mendix-internal implementation detail, and are potentially subject to change between different Mendix versions.
To avoid tight coupling between your queries and database-specific names, please use Mendix Core API methods with Mendix Core Meta classes to retrieve database-specific table and column names:
Core.getDatabaseTableName(IMetaObject)
Core.getDatabaseTableName(IMetaAssociation)
Core.getDatabaseColumnName(IMetaPrimitive)
Core.getDatabaseChildColumnName(IMetaAssociation)
Core.getDatabaseParentColumnName(IMetaAssociation)
References to table and column names from within stored procedures in the database might become invalid after a change in the Mendix domain model has been synchronized with the database.
Furthermore, executing the SQL INSERT statement on tables in the Mendix database should be avoided. Mendix creates a special unique ID for each data row in the table, where its Long data type actually has a specialized Mendix-internal representation. Inserting one's own data row in a table with a custom ID might lead to unspecified or undesired external behavior, like not being able to retrieve such data rows in a data grid.
Please be advised that the automated backup of the Mendix cloud database (PostgreSQL) is only done for the PUBLIC schema. By using this method, any (meta)data that is stored in another schema might potentially be lost on hardware failure.
Code examples
Basic examples
Example without parameters:
private Report generate(Connection connection) {
Report report = new Report();
try (Statement statement = connection.createStatement()) {
...
report.addRow(..);
} catch (SQLException e) {
// handle exception
}
return report;
}
public Report generateReport() {
return Core.dataStorage().executeWithConnection(this::generate);
}
The above example first defines a generate method at the top, which returns a report. It takes a connection as its first
parameter, which is automatically set from the data storage API. This method is then passed as
a method reference to the
executeWithConnection API method at the bottom.
The first function uses the
try-with-resources functionality, which automatically closes the given resource.
Example with parameters:
private Function<Connection, Report> createReportOnConnection(String name, LocalDate start, LocalDate end) {
return connection -> {
Report report = new Report();
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
// do something with the result set and fill the report
} catch (SQLException e) {
// handle exception
}
return report;
};
}
public Report createReport(String name, LocalDate start, LocalDate end) {
return Core.dataStorage().executeWithConnection(createReportOnConnection(name, start, end));
}
The above example first defines a create method at the top, which has three method-specific parameters. This method returns a function
which receives that a connection and returns a report.
This method is then called as the argument of the executeWithConnection API method at the bottom.
Examples with checked exceptions
In this example, the exception is caught and wrapped in a java.lang.RuntimeException
:
private Report generate(Connection connection) {
Report report = new Report();
try (Statement statement = connection.createStatement()) {
...
report.addRow(..);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return report;
}
public Report generateReport() {
return Core.dataStorage().executeWithConnection(this::generate);
}
In this example, we make use of the Durian library, to wrap checked exceptions more easily:
private Report generate(Connection connection) throws Exception {
Report report = new Report();
try (Statement statement = connection.createStatement()) {
...
report.addRow(..);
}
// do not handle the exception here
return report;
}
public Report generateReport() {
return Core.dataStorage().executeWithConnection(Errors.rethrow().wrap(this::generate));
}
In that library, there are a lot more handy methods to handle exceptions in a more functional way, for example by using Either
.function
- the function to execute with a database connection as argument<R> R executeWithConnection(IContext context, java.util.function.Function<java.sql.Connection,R> function)
context
- the context to execute this function onfunction
- the function to execute with a database connection as argument
The only difference is that this method executes the function on the current connection of the given context, allowing function
execution to be part of the current transaction.
This connection will not be automatically closed afterwards.