I have an Java EE application that runs on a WildFly server. The configuration in the persistence unit is as follows:
<persistence-unit name="default" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:/jboss/datasources/templateDS</jta-data-source>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<validation-mode>AUTO</validation-mode>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="use_sql_comments" value="false" />
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<!-- <property name="hibernate.cache.region.factory_class" vvalue="org.hibernate.transaction.CMTTransactionFactory"/>-->
<!-- <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory" />-->
<property name="hibernate.cache.infinispan.cachemanager" value="java:jboss/infinispan/container/hibernate" />
<!-- <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>-->
<property name="hibernate.generate_statistics" value="false" />
<!-- none | validate | update | create | create-drop -->
<property name="hibernate.hbm2ddl.auto" value="none" />
</properties>
</persistence-unit>
I have some business logic which runs to send Firebase push notifications to users as a form of targeted communication. The user base is quite large (> 10k) hence we decided to paginate and batch the message recipients in batches of 10 to make sure there are no memory issues. I have an entity called MessageRecipient which manages the user receiving the message. It has a column to manage state and when the logic runs we want to update the state of each sent message from UNSENT to SENT or FAILED. The following is my business logic running in a method:
private void sendTargetedMessages(CsvFile dueFile) {
LOGGER.debug("in send targeted messages");
String filename = dueFile.getFilename();
String batchRef = filename.substring(0, filename.indexOf('.'));
// Calculate number of recipients.
int numberOfRecipients = crudService.findAllByColumn(MessageRecipient.class, batchRef, "batchRef").size();
int pageNumber = 0;
int pageSize = 10; //TODO: Change this to config entry
Integer num_sent = 0;
// Calculate number of pages. Use calculated number of pages to run the logic in a do while loop that increments
do {
List<MessageRecipient> recipients = crudService.findAllByColumn(MessageRecipient.class, batchRef, "batchRef", new Paging(pageNumber, pageSize));
// Execute the logic
if (recipients != null) {
// send targeted messages
User u;
for (MessageRecipient r : recipients) {
u = null;
if (RecipientState.UNSENT.equals(r.getState())) {
if (UserReferenceType.CVS_REF.equals(dueFile.getUserReferenceType())) {
u = userRepository.findUserByCvsRef(r.getReference(), Status.ACTIVE);
} else {
try {
u = userRepository.findUserByUserId(Long.parseLong(r.getReference()), Status.ACTIVE);
} catch (NumberFormatException e) {
r.setState(RecipientState.FAILED);
LOGGER.error("Reference cannot be converted to long.", e);
}
}
if (u != null) {
pushNotificationService.sendTargetedCommunication(u, dueFile.getCommId());
r.setState(RecipientState.SENT);
num_sent++;
}
//crudService.merge(r);
entityManager.merge(r);
entityManager.flush();
}
}
} else {
LOGGER.error("No recipients found for targeted communication.");
}
// Increment page number
pageNumber++;
} while (pageNumber <= (calculateNumberOfPages(numberOfRecipients, pageSize) - 1));
// Send email report once done
if (!StringUtils.isBlank(dueFile.getReportEmails())) {
String[] reportEmails = dueFile.getReportEmails().split(",");
List<String> reportEmailsList = Arrays.asList(reportEmails);
messagingReportService.sendEmailReport(reportEmailsList, batchRef, numberOfRecipients, num_sent, dueFile.getBucketName());
}
}
I call entityManager merge() and flush() to try to update the state of each MessageRecipient record after the push notification is sent but the state is not updated in my database (a MySQL database). I have turning on SQL statement logging in my application and I see Hibernate executing the update SQL statement although there is no update in my database. How can I solve this problem?
11:04:54,017 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) Hibernate:
11:04:54,018 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) update
11:04:54,018 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) message_recipient
11:04:54,019 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) set
11:04:54,019 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) last_updated=?,
11:04:54,019 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) batch_ref=?,
11:04:54,020 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) reference=?,
11:04:54,020 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) state=?
11:04:54,021 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) where
11:04:54,021 INFO [stdout] (EE-ManagedScheduledExecutorService-default-Thread-1) id=?
Initially I was just calling entityManager.merge(T entity) and Hibernate was not generating the update SQL statement when trying to update the entity but I added the entityManager.flush() and now I see it in my logs. However, my issue is the database is not updated as well.
You are using second level cache it might be that cache didn't flush the transaction yet, try reading changes in your application if it is there when you try to read, no problem it works as expected then it means its at cache not flushed to DB yet.
Another thing make sure you are operating in container managed TX scope using EJBs and @TransactionManagement(TransactionManagementType.CONTAINER)