Multithreading Process
Liferay DXP 7.4 U10+ or Liferay Portal 7.4 GA14+
Your Upgrade Processes may involve making complex changes to large data sets. If performance is critical, use the processConcurrently()
method in the UpgradeProcess
class of your application. This method executes in multiple threads and can shorten your upgrade times.
Deploy Version 1.0.0
Start a new Liferay DXP instance by running
docker run -it -m 8g -p 8080:8080 liferay/dxp:2024.q2.11
Sign in to Liferay at http://localhost:8080 using the email address test@liferay.com and the password test. When prompted, change the password to learn.
Then, follow these steps:
-
Download and unzip Multithreading Process.
curl https://resources.learn.liferay.com/dxp/latest/en/building-applications/data-frameworks/upgrade-processes/liferay-j7z3.zip -O
unzip liferay-j7z3.zip
-
Move into the
1.0.0
directory, build and deploy.cd 1.0.0
../gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
NoteThis command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.
-
Confirm the deployment in the Liferay Docker container console.
STARTED com.acme.j7z3.api_1.0.0 [1030] STARTED com.acme.j7z3.service_1.0.0 [1031]
Add Entries to the App
-
Navigate to the Script console at Control Panel → Server Administration → Script.
-
Add some entries by executing the following script.
import com.acme.j7z3.service.J7Z3EntryLocalServiceUtil; import com.liferay.portal.kernel.dao.orm.QueryUtil; entry1 = J7Z3EntryLocalServiceUtil.createJ7Z3Entry(1); entry2 = J7Z3EntryLocalServiceUtil.createJ7Z3Entry(2); entry3 = J7Z3EntryLocalServiceUtil.createJ7Z3Entry(3); entry1.setName("able"); entry2.setName("able"); entry3.setName("able"); J7Z3EntryLocalServiceUtil.addJ7Z3Entry(entry1); J7Z3EntryLocalServiceUtil.addJ7Z3Entry(entry2); J7Z3EntryLocalServiceUtil.addJ7Z3Entry(entry3);
The J7Z3_J7Z3Entry table now has three entries.
Execute the Upgrade
-
Move into the
1.0.1
directory, build and deploy.cd ../1.0.1
../gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
-
Navigate to the Gogo shell console at Control Panel → Gogo Shell.
-
Verify that the 1.0.1 upgrade is available by entering the command
upgrade:list com.acme.j7z3.service
. The 1.0.1 version appears as a registered upgrade process in the output window. -
Execute the upgrade by entering the command
upgrade:execute com.acme.j7z3.service
. The output window shows that the upgrade was completed. -
The J7Z3_J7Z3Entry table entries have now been updated.
Implement the Multithreading Method
In the UpgradeProcess
class of your application, override the processConcurrently()
method.
@Override
protected void doUpgrade() throws Exception {
processConcurrently(
"select j7z3EntryId, name from J7Z3_J7Z3Entry",
resultSet -> new Object[] {
resultSet.getLong("j7z3EntryId"), resultSet.getString("name")
},
columns -> {
long j7z3EntryId = (Long)columns[0];
try (PreparedStatement preparedStatement =
connection.prepareStatement(
"update J7Z3_J7Z3Entry set name = ? where " +
"j7z3EntryId = ?")) {
preparedStatement.setString(1, "baker");
preparedStatement.setLong(2, j7z3EntryId);
preparedStatement.executeUpdate();
}
},
null);
}
The method has four parts:
-
SQL Query - An SQL statement to query the database data.
-
Gathering objects - Receive a result set and return an array of objects based on the result set.
-
Processing objects - Execute business logic on the array of objects.
-
Exception - Send a message if an exception occurs.
The sample project shows a simple example where the name
field is processed and modified by the processConcurrently()
method.
-
select j7z3EntryId, name from J7Z3_J7Z3Entry
- The SQL statement queries all the entries. -
resultSet -> new Object[] { resultSet.getLong("j7z3EntryId"), resultSet.getString("name") }
The objects are gathered and stored in the
resultSet
array. -
columns -> { long j7z3EntryId = (Long)columns[0]; try (PreparedStatement preparedStatement = connection.prepareStatement( "update J7Z3_J7Z3Entry set name = ? where " + "j7z3EntryId = ?")) { preparedStatement.setString(1, "baker"); preparedStatement.setLong(2, j7z3EntryId); preparedStatement.executeUpdate(); } }
For every
j7z3EntryId
, thename
field is set asbaker
. -
The exception is set to
null
.
Alternate Implementations of processConcurrently()
There are two alternative signatures of the processConcurrently()
method. The first uses Auto Batching for improved performance. In the J7Z3EntryUpgradeProcess.java
file, replace the usage of processConcurrently()
with the following code:
processConcurrently(
"select j7z3EntryId, name from J7Z3_J7Z3Entry",
"update J7Z3_J7Z3Entry set name = ? where j7z3EntryId = ?",
resultSet -> new Object[] {
resultSet.getLong("j7z3EntryId"), resultSet.getString("name")
},
(columns, preparedStatement) -> {
long j7z3EntryId = (Long)columns[0];
preparedStatement.setString(1, "baker");
preparedStatement.setLong(2, j7z3EntryId);
preparedStatement.addBatch();
},
null);
This signature has the following parameters:
-
Like the original implementation, the first argument is a SQL query for obtaining the data to be processed.
-
The second argument is the SQL query to insert, update, or delete data in a batch controlled by the method.
-
The third argument is a function similar to the one in the original implementation:
resultSet -> new Object[] { resultSet.getLong("j7z3EntryId"), resultSet.getString("name") }
-
The fourth argument is also a similar function:
(columns, preparedStatement) -> { long j7z3EntryId = (Long)columns[0]; preparedStatement.setString(1, "baker"); preparedStatement.setLong(2, j7z3EntryId); preparedStatement.addBatch(); }
-
The last argument is a
String
to be used in case an exception occurs.
Use this signature for simple upgrades that only update or insert SQL data. Use the previous signature for upgrades that do more than inserting data (e.g. deleting files or adding images).
The other signature passes an array as the source. See the BaseDBProcess javadocs to learn more.