April 4, 2022
In the majority of cases you can and should access Maximo data through the Maximo Business Objects (MBOs). Using the MBOs ensures that Maximo's business rules are respected and that data is not corrupted. However, there are rare cases where accessing Maximo data through a database connection is appropriate or even necessary. In this post we examine how to obtain a database connection, use it responsibly and finally make sure that the connection does not leak resources.
This post assumes a working understanding of JDBC. Oracle provides an excellent JDBC tutorial here: https://docs.oracle.com/javase/tutorial/jdbc/basics/index.html
The Maximo Database Manager class psdi.server.DBManager
manages the Maximo connection to the database and allocates connections from an internally managed connection pool. Using the psdi.server.DBManager
allows you to get a Maximo managed connection to the database in the same way that the MBOs do, without having to directly establish a java.sql.Connection
to the database.
A JDBC java.sql.Connection
object is obtained from the psdi.server.DBManager
class using a psdi.security.ConnectionKey
object that is associated with a user session. The psdi.security.ConnectionKey
object is accessible from the psdi.security.UserInfo
object. The psdi.security.UserInfo
object is turn available as the implicit automation script variable userInfo
or can be obtained from the implicit mbo
variable with the getUserInfo()
method.
As important as obtaining a connection is making sure you put it back. When manually using a JDBC connection it is critical to free resources to prevent resource leaks. Since Maximo manages a pool of connections, you should not close the connection. Instead you tell the DBManager
that the connection is no longer being used and it is free to be assigned to another. You do this by calling the freeConnection(ConnectionKey)
method on the DBManager
. Note that in our example we put this in a try
finally
block to ensure that the connection is always freed at the end of execution.
MXServer = Java.type("psdi.server.MXServer");main();function main() {var dbManager = MXServer.getMXServer().getDBManager();var connectionKey = userInfo.getConnectionKey()try {// Get a reference to the database managervar dbManager = MXServer.getMXServer().getDBManager();// Get a reference to user's ConnectionKeyvar connectionKey = userInfo.getConnectionKey();// Use the ConnectionKey to get a java.sql.Connectionvar connection = dbManager.getConnection(connectionKey);// Perform actions with the connection...} finally {dbManager.freeConnection(connectionKey);}}
from psdi.server import MXServerdef main():dbManager = MXServer.getMXServer().getDBManager()connectionKey = userInfo.getConnectionKey()try:# Get a reference to the database managerdbManager = MXServer.getMXServer().getDBManager()# Get a reference to user's ConnectionKeyconnectionKey = userInfo.getConnectionKey()# Use the ConnectionKey to get a java.sql.Connectionconnection = dbManager.getConnection(connectionKey)# Perform actions with the connection...finally:dbManager.freeConnection(connectionKey)main()
Once you have a java.sql.Connection
reference, you can use it as you would any other JDBC connection. This post is not intended to cover JDBC, but in the interest of providing something useful we are going to look at using a java.sql.Statement
and a java.sql.PreparedStatement
.
A java.sql.Statement
is used to execute static SQL and return the results it produces. A java.sql.Statement
is obtained from the java.sql.Connection
with the createStatement()
method. Once you have a java.sql.Statement
you can use the execute(String sql)
method to execute a static SQL statement, this will return a boolean
value of true
if there is a result or false
if not. The executeQuery(String sql)
method takes a static query SQL statement and will return a java.sql.ResultSet
that can be used to access to the query results, note that this never returns null even if there are no results. Finally, the executeUpdate(String sql)
method takes a static update SQL statement and returns an int
that represents the number of rows updated.
After using the java.sql.Statement
the close()
method should be called to release JDBC resources immediately. This is important since the java.sql.Statement
comes from a pool of java.sql.Connection
objects and may not be closed automatically in a timely manner since the connection may remain open for an extended time. Likewise, if a java.sql.ResultSet
is obtained, it should be closed after use. In both these cases, a try / finally
block is appropriate to ensure the resources are closed.
java.sql.ResultSet
objects have anext()
method that returns atrue
value if there is a row in the set and moves the cursor to that row. When thejava.sql.ResultSet
is first returned it is in a zero position and callingnext()
moves the cursor to the first row.Also note that column data in a
java.sql.ResultSet
is one (1
) indexed instead of zero (0
), so the first column is at index one (1
) not zero (0
).
MXServer = Java.type("psdi.server.MXServer");main();function main() {// Get a reference to the database managervar dbManager = MXServer.getMXServer().getDBManager();// Get a reference to user's ConnectionKeyvar connectionKey = userInfo.getConnectionKey();// Use the ConnectionKey to get a java.sql.Connectionvar connection = dbManager.getConnection(connectionKey);var statement;var resultSet;try {statement = connection.createStatement();// delete everything in the dummy_tablestatement.execute("delete from dummy_table");// get labor records where the labor code starts with a capital A.resultSet = statement.executeQuery("select laborcode from labor where laborcode like 'A%'");while (resultSet.next()) {// do something with the data.var laborCode = resultSet.getString(1);}// update the user status to active for blocked users and capture how many users were unblocked.var blockedUserCount = statement.executeUpdate("update maxuser set status = 'ACTIVE' where status = 'BLOCKED'");} finally {if (statement && !statement.isClosed()) {statement.close();}if (resultSet && !resultSet.isClosed()) {resultSet.close();}dbManager.freeConnection(connectionKey);}}
from psdi.server import MXServerdef main():# Get a reference to the database managerdbManager = MXServer.getMXServer().getDBManager()# Get a reference to user's ConnectionKeyconnectionKey = userInfo.getConnectionKey()# Use the ConnectionKey to get a java.sql.Connectionconnection = dbManager.getConnection(connectionKey)statementresultSettry:statement = connection.createStatement()# delete everything in the dummy_tablestatement.execute("delete from dummy_table")# get labor records where the labor code starts with a capital A.resultSet = statement.executeQuery("select laborcode from labor where laborcode like 'A%'")while resultSet.next():# do something with the data.laborCode = resultSet.getString(1)# update the user status to active for blocked users and capture how many users were unblocked.blockedUserCount = statement.executeUpdate("update maxuser set status = 'ACTIVE' where status = 'BLOCKED'")finally:if statement is not None and not statement.isClosed():statement.close()if resultSet is not None and not resultSet.isClosed():resultSet.close()dbManager.freeConnection(connectionKey)main()
A java.sql.PreparedStatement
represents a precompiled SQL statement and allows for efficient execution of a statement multiple times. Parameters can be provided as part of the statement using question mark (?
), with parameters one (1
) indexed in the same way as accessing results from a java.sql.ResultSet
object. A java.sql.PreparedStatement
is obtained from the java.sql.Connection
with the prepareStatement(String sql)
method.
The java.sql.PreparedStatement
supports the same execute()
, executeQuery()
and executeUpdate()
as the java.sql.Statement
, but without the java.lang.String
parameter, since a 'java.sql.PreparedStatement` is created with the SQL statement that will be executed.
The same principles of closing both java.sql.ResultSet
and java.sql.PreparedStatement
objects applies when using the java.sql.PreparedStatement
.
MXServer = Java.type("psdi.server.MXServer");main();function main() {// Get a reference to the database managervar dbManager = MXServer.getMXServer().getDBManager();// Get a reference to user's ConnectionKeyvar connectionKey = userInfo.getConnectionKey();// Use the ConnectionKey to get a java.sql.Connectionvar connection = dbManager.getConnection(connectionKey);var preparedStatement;var resultSet;try {preparedStatement = connection.prepareStatement("delete from dummy_table");// delete everything in the dummy_tablepreparedStatement.execute();preparedStatement.close();// get labor records where the status equals ACTIVE.preparedStatement = connection.prepareStatement("select laborcode from labor where status = ?");preparedStatement.setString(1, "ACTIVE");resultSet = preparedStatement.executeQuery();while (resultSet.next()) {// do something with the data.var laborCode = resultSet.getString(1);}resultSet.close();// use the same prepared statement, but search for INACTIVE this time.preparedStatement.setString(1, "INACTIVE");resultSet = preparedStatement.executeQuery();while(resultSet.next()){// do something with the data.var laborCode = resultSet.getString(1);}preparedStatement.close();// prepare a statement to update the users statusespreparedStatement = connection.prepareStatement("update maxuser set status = ? where status = ?");preparedStatement.setString(1, "ACTIVE");preparedStatement.setString(2, "BLOCKED");// execute the statement.var blockedUserCount = preparedStatement.executeUpdate();} finally {if (preparedStatement && !preparedStatement.isClosed()) {preparedStatement.close();}if (resultSet && !resultSet.isClosed()) {resultSet.close();}dbManager.freeConnection(connectionKey);}}
from psdi.server import MXServerdef main():# Get a reference to the database managerdbManager = MXServer.getMXServer().getDBManager()# Get a reference to user's ConnectionKeyconnectionKey = userInfo.getConnectionKey()# Use the ConnectionKey to get a java.sql.Connectionconnection = dbManager.getConnection(connectionKey)preparedStatementresultSettry:preparedStatement = connection.prepareStatement("delete from dummy_table")# delete everything in the dummy_tablepreparedStatement.execute()preparedStatement.close()# get labor records where the status equals ACTIVE.preparedStatement = connection.prepareStatement("select laborcode from labor where status = ?")preparedStatement.setString(1, "ACTIVE")resultSet = preparedStatement.executeQuery()while resultSet.next() :# do something with the data.laborCode = resultSet.getString(1)resultSet.close()# use the same prepared statement, but search for INACTIVE this time.preparedStatement.setString(1, "INACTIVE")resultSet = preparedStatement.executeQuery()while resultSet.next():# do something with the data.var laborCode = resultSet.getString(1)preparedStatement.close()# prepare a statement to update the users statusespreparedStatement = connection.prepareStatement("update maxuser set status = ? where status = ?")preparedStatement.setString(1, "ACTIVE")preparedStatement.setString(2, "BLOCKED")# execute the statement.blockedUserCount = preparedStatement.executeUpdate()finally:if preparedStatement is not None and not preparedStatement.isClosed():preparedStatement.close()if resultSet is not None and not resultSet.isClosed():resultSet.close()dbManager.freeConnection(connectionKey)main()
If all that was a bit much, there is a shortcut, the psdi.mbo.DBShortcut
. This provides access to connect, execute statements and queries and then free the connection without having to worry about the underlying psdi.server.DBManager
, java.sql.Statement
and java.sql.PreparedStatement
objects. It is somewhat limited in its capabilities, but if you only need to execute a statements one at a time or query the database it may provide a more simple solution than the full JDBC framework provides.
The connect(ConnectionKey)
method is similar the psdi.server.DBManager
, but allows the psdi.mbo.DBShortcut
to manage the connection lifecycle for you. The execute(SqlFormat)
allows arbitrary SQL statements to be executed, with the notable exception that the method takes a psdi.mbo.SqlFormat
object instead of a direct string, which encourages you to use the psdi.mbo.SqlFormat
features that we covered here. The executeQuery(String)
method executes the SQL query provided and returns the java.sql.ResultSet
object. Finally, the close()
method releases all the underlying JDBC connection resources and frees the connection back to the psdi.server.DBManager
connection pool.
Putting all that together, our previous example looks like the following:
DBShortcut = Java.type("psdi.mbo.DBShortcut");SqlFormat = Java.type("psdi.mbo.SqlFormat");main();function main() {var dbShortcut = DBShortcut();var connectionKey = userInfo.getConnectionKey();try {dbShortcut.connect(connectionKey);// create a new SqlFormat object to unblock blocked users.var sqlf = new SqlFormat("update maxuser set status = :1 where status = :2");sqlf.setObject(1, "MAXUSER", "STATUS", "ACTIVE");sqlf.setObject(2, "MAXUSER", "STATUS", "BLOCKED");dbShortcut.execute(sqlf);// get labor records where the labor code starts with a capital A.resultSet = dbShortcut.executeQuery("select laborcode from labor where laborcode like 'A%'");while (resultSet.next()) {// do something with the data.var laborCode = resultSet.getString(1);}} finally {dbShortcut.close();}}
from psdi.mbo import DBShortcut, SqlFormatdef main():dbShortcut = DBShortcut()connectionKey = userInfo.getConnectionKey()dbManager = MXServer.getMXServer().getDBManager()connectionKey = userInfo.getConnectionKey()try:dbShortcut.connect(connectionKey)# create a new SqlFormat object to unblock blocked users.sqlf = new SqlFormat("update maxuser set status = :1 where status = :2")sqlf.setObject(1, "MAXUSER", "STATUS", "ACTIVE")sqlf.setObject(2, "MAXUSER", "STATUS", "BLOCKED")dbShortcut.execute(sqlf)# get labor records where the labor code starts with a capital A.resultSet = dbShortcut.executeQuery("select laborcode from labor where laborcode like 'A%'")while resultSet.next():# do something with the data.var laborCode = resultSet.getString(1)}finally:dbShortcut.close()main()
In this post we showed how to access the database directly in a safe and responsible way. We demonstrated how to execute statements and queries both as static java.lang.String
statements and as a precompiled java.sql.PreparedStatemt
that can accept parameters for more efficient execution. We reviewed how resources should be closed or freed to avoid resource leaks and system performance issues.
Finally, this is the first post where we have provided examples in both JavaScript and Python, which has been often requested.
If you have any questions or comments please reach out to us at [email protected]