Hilfe beim Quartz Scheduler

  • Themenstarter Themenstarter Gelöschtes Mitglied 68249
  • Beginndatum Beginndatum
G

Gelöschtes Mitglied 68249

Gast
Hallo zusammen,

wir hatten den Quartz schonmal zum Laufen gebracht, meine ich, aber das ist schon lange her und wegen Problemen beim CDI hatten wir ihn dann auch wieder abgeschaltet.

Jetzt hatten wir mal wieder Zeit, aber irgendwie klappt es nicht Jobs an zu legen.

Folgende Konfiguration
JavaEE Server WLP mit Java 11 und Jakarta Featureset.
Datenbankverbindungen sind im WLP konfiguriert und funktionieren im Rest der Anwendung einwandfrei.
persistence.xml:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0">

    <persistence-unit name="atcdb" transaction-type="JTA">
        <jta-data-source>jdbc/atcDataSource</jta-data-source>
        <class>main.java.persistence.entities.DashboardConfig</class>
        <class>main.java.persistence.entities.ActiveObject</class>
        <class>main.java.persistence.entities.AmeiseAbrufDaten</class>
        <class>main.java.persistence.entities.AmeiseDBObject</class>
        <properties>
            <property name="eclipselink.cache.shared.default" value="false" />
        </properties>
    </persistence-unit>
</persistence>

quartz.properties
Code:
org.quartz.scheduler.instanceName=AtcScheduler
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

org.quartz.jobStore.dataSource=cmtDS
org.quartz.dataSource.cmtDS.jndiURL=jdbc/atcDataSource

org.quartz.jobStore.nonManagedTXDataSource=nonManDS
org.quartz.dataSource.nonManDS.driver=oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.nonManDS.URL=URL
org.quartz.dataSource.nonManDS.user=USER
org.quartz.dataSource.nonManDS.password=PASSWORD
org.quartz.dataSource.nonManDS.maxConnections=30

org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.dontSetAutoCommitFalse=true
org.quartz.jobStore.useProperties=true

billige implementierung
Java:
package main.java.timer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.ejb.DependsOn;
import jakarta.ejb.Singleton;
import jakarta.ejb.Startup;

@Startup
@Singleton
public class QuartzTimer {

    private final static Logger LOG = LogManager.getLogger(QuartzTimer.class);
    private static Scheduler scheduler;

    @PostConstruct
    public void init() {
        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();

            // define the job and tie it to our HelloJob class
            JobDetail job = newJob(HelloJob.class).withIdentity("jobDBE", "groupDBE").build();

            // Trigger the job to run now, and then repeat every 40 seconds
            Trigger trigger = newTrigger().withIdentity("triggerDBE", "groupDBE").startNow()
                    .withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever()).build();

            // Tell quartz to schedule the job using our trigger
            scheduler.scheduleJob(job, trigger);

        } catch (SchedulerException se) {
            LOG.error("Quartz Timer Exception beim Start:", se);
        }
    }
}

Fehlermeldung
Code:
[2023-06-09T15:43:33] [ERROR] [Default Executor-thread-6] main.java.timer.QuartzTimer:98 - Quartz Timer Exception beim Start:
org.quartz.JobPersistenceException: Couldn't store job: Driver's Blob representation is of an unsupported type: oracle.sql.BLOB
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1123) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$2.executeVoid(JobStoreSupport.java:1067) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3780) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3778) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreCMT.executeInLock(JobStoreCMT.java:245) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJobAndTrigger(JobStoreSupport.java:1063) ~[quartz-2.3.2.jar:?]
    at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:855) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249) ~[quartz-2.3.2.jar:?]
    at main.java.timer.QuartzTimer.init(QuartzTimer.java:44) [classes/:?]
    at jdk.internal.reflect.GeneratedMethodAccessor935.invoke(Unknown Source) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at com.ibm.ejs.container.interceptors.InterceptorProxy.invokeInterceptor(InterceptorProxy.java:200) [com.ibm.ws.ejbcontainer.jakarta_1.0.71.jar:?]
    at [internal classes]
    at jdk.internal.reflect.GeneratedMethodAccessor902.invoke(Unknown Source) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at com.ibm.ejs.container.interceptors.InterceptorProxy.invokeInterceptor(InterceptorProxy.java:189) [com.ibm.ws.ejbcontainer.jakarta_1.0.71.jar:?]
    at [internal classes]
    at org.jboss.weld.module.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:81) [io.openliberty.org.jboss.weld4_1.0.71.jar:4.0.3.Final]
    at com.ibm.ws.cdi.ejb.interceptor.WeldSessionBeanInterceptorWrapper.aroundInvoke(WeldSessionBeanInterceptorWrapper.java:58) [com.ibm.ws.cdi.2.0.ejb.jakarta_1.0.71.jar:?]
    at [internal classes]
    at jdk.internal.reflect.GeneratedMethodAccessor901.invoke(Unknown Source) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at com.ibm.ejs.container.interceptors.InterceptorProxy.invokeInterceptor(InterceptorProxy.java:189) [com.ibm.ws.ejbcontainer.jakarta_1.0.71.jar:?]
    at [internal classes]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
    at java.lang.Thread.run(Thread.java:836) [?:?]
Caused by: java.sql.SQLException: Driver's Blob representation is of an unsupported type: oracle.sql.BLOB
    at org.quartz.impl.jdbcjobstore.oracle.OracleDelegate.writeDataToBlob(OracleDelegate.java:597) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.oracle.OracleDelegate.insertJobDetail(OracleDelegate.java:183) ~[quartz-2.3.2.jar:?]
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1117) ~[quartz-2.3.2.jar:?]
    ... 51 more

Also Oracle-Treiber verwende ich im Classpath ojdbc8.jar. Oracle.sql.BLOB ist in der Klasse drin. Die häufigsten "Lösungen" die ich bisher gefunden habe, waren alle für andere App-Server.

Wenn ihr spontan keine Lösung für das Problem habt, aber das grundlegende Problem lösen könnt, bin ich gerne bereit etwas anderes zu versuchen.
Grundlegend war unser Problem, dass wir Timer mit @Schedule steuern. Das führt zu mehreren Problemen:
1. Wenn man einen Timer zu anderen Zeiten laufen lassen möchte, muss man neu deployen
2. Wenn man einen redundanten Server hat, dann werden bestimmte Jobs doppelt ausgeführt, d.h. wir bekommen viele Mails doppelt, weil eben zwei Server dasselbe tun.

Wenn ihr diese Probleme mit einer anderen Lösung anpacken könnt, dann gerne her damit. Ich bau gerne nochmal alles um, wenn es dann nur funktioniert.
 
Moin,
die Idee kam mir auch schon, aber wenn der Name sich nicht irgendwann grundlegend geändert hat, dann nicht. Es liegt auch noch ein mssql Treiber im Classpath, weil wir den auch noch brauchen, das sollte aber eigentlich nicht stören.
Es gab doch in Eclipse eine Tastenkombination, um nach Klasse zu suchen, da müsste ich doch eine Dopplung erkennen können, oder?
 
Der mssql-Treiber kann nicht die Ursache sein. Es sieht so aus als würde die Klasse oracle.sql.BLOB benutzt, welche aber nicht kompatibel mit der erwarteten Klasse oracle.sql.BLOB zu sein scheint. Ich habe das Problem schon einmal bei einem Kollegen gesehen. Dort kam ein ojdbc6.jar aus der Installation des Servers mit, zusätzlich hatte er ojdbc8.jar eingebunden.
Wir verwenden allerdings den Oracle Weblogic Server. Dort lässt sich das relativ einfach lösen, indem man in der weblogic.xml den Classpath so einstellt, das Klassen aus dem Package oracle.* nur aus der Anwendung gezogen werden. Entsprechende Klassen aus dem Classpath des Servers sieht die Anwendung dann nicht mehr.
Normalerweise sollte die JEE-Anwendung selbst überhaupt keinen JDBC-Treiber kennen. Man richtet im Server entsprechende Datasources ein. Diese werden dann per JNDI über ihren Namen verwendet. Alles andere ist eher unüblich unter JEE:
Java:
# Example of a Datasource referenced from an Application Server

org.quartz.dataSource.myOtherDS.jndiURL=jdbc/myDataSource
org.quartz.dataSource.myOtherDS.java.naming.factory.initial=com.evermind.server.rmi.RMIInitialContextFactory
org.quartz.dataSource.myOtherDS.java.naming.provider.url=ormi://localhost
org.quartz.dataSource.myOtherDS.java.naming.security.principal=admin
org.quartz.dataSource.myOtherDS.java.naming.security.credentials=123
 
Das Problem ist aber, dass Quartz zwei Verbindungen zur Datenbank braucht, wenn man eine Servermanaged Verbindung aufbauen möchte.
Deswegen Muss eben eine Teilverbindung über diese zweite Schiene laufen und dann baut Quartz eben alles selbst auf und greift scheinbar beim Oracle-Treiber ins Klo. Ich finde aber auch nichts, was er stattdessen für oracle.jdbc.driver.OracleDriver ziehen könnte. Aber vielleicht suche ich auch falsch.
 
Das Problem ist aber, dass Quartz zwei Verbindungen zur Datenbank braucht, wenn man eine Servermanaged Verbindung aufbauen möchte.
Warum sollte es nicht möglich sein 2 Datasources im Server zu konfigurieren? Ich habe mal eine JEE-Anwendung gebaut, welche PDF für viele andere Anwendungen erzeugen kann. Dort gab es zeitweise über 30 Datasources.
 
Achso, ne, da haben wir aneinander vorbei geredet. Ich hab in dem Server auch 3 Datasources, aber Quartz sagt halt folgendes für die Config:
JDBCJobStore is used to store scheduling information (job, triggers and calendars) within a relational database. There are actually two seperate JDBCJobStore classes that you can select between, depending on the transactional behaviour you need.

JobStoreCMT relies upon transactions being managed by the application which is using Quartz. A JTA transaction must be in progress before attempt to schedule (or unschedule) jobs/triggers. This allows the “work” of scheduling to be part of the applications “larger” transaction. JobStoreCMT actually requires the use of two datasources - one that has it’s connection’s transactions managed by the application server (via JTA) and one datasource that has connections that do not participate in global (JTA) transactions. JobStoreCMT is appropriate when applications are using JTA transactions (such as via EJB Session Beans) to perform their work.
Also muss diese zweite Verbindung von der Anwendung aufgebaut werden, oder verstehe ich das nicht richtig?
 
Die JNDI-Datasource kann ich im Oracle-Weblogic als JTA- oder Non-JTA-Datasource anlegen. Also JNDI und JTA sind nicht das Gleiche.
 
Okay, und dann binde ich das in Quatz einfach auch über seinen JNDI Namen ein?
Auf die Idee bin ich einfach nicht gekommen, weil ich nicht wusste, dass das über die Persistence.xml auch geht.
Heißt das ich nehme eine weitere Source mit rein und stelle die auf RESSOURCE_LOCAL?
Und kann ich da einen JNDI vom Server doppelt verwenden?
Bzw. was muss die Source auf dem Server können, damit es funktiniert?
 
Ergänzung:
Ich habe jetzt auf dem Server eine neue DS eingerichtet:
XML:
<dataSource jndiName="jdbc/nonJtaAtc" id="nonJtaAtc" type="javax.sql.DataSource" isolationLevel="TRANSACTION_READ_COMMITTED" transactional="false">
    <jdbcDriver libraryRef="OracleJDBC"></jdbcDriver>
    <properties.oracle URL="jdbc:oracle:thin:@SERVER:1521/DB" user="einer" password="geheim"></properties.oracle>
</dataSource>
Bei der scheint er auch den JNDI zu fressen.
Fehler bleibt derselbe.
Ist das Problem evtl. der Treiber auf dem Sever?
 
Das hat doch gar nichts mit der Persistence.xml zu tun. Du musst die 2. Datasource in die quartz.properties einbinden. Natürlich musst du dann auch den Oracle-JDBC-Treiber aus dem Classpath entfernen.
 
Jetzt hab ich mal zwei Tage lang versucht statt Quartz JobRunr zu verwenden. Zumindest läuft das besser an, als Quartz. Aber zurück zu Quartz:
Ich habe die Datasource aus meinem letzten Post in Quartz als zweite Datenquelle eingebunden. Die Sache mit der persistence.xml war ein Brainlag. Leider kommt es zum selben Fehler. Die Oracle Klasse brauche ich im System, weil ich auch noch manuelle Oracle-Verbindungen zu anderen Systemen aufbaue. Außerdem verwende ich dieselbe Klasse, die ich auch im Server verwende, somit müsste es in beiden Klassen zum selben Fehler kommen.

Dann noch zu JobRunr, hier habe ich wohl ein einfaches CDI-Problem:
Java:
@ApplicationScoped
@DependsOn("DatabaseCheck")
public class JobRunrJobs {

    private final static Logger LOG = LogManager.getLogger(JobRunrJobs.class);

    @Inject    transient private AvailableInterface availableInterface;
    
    public void onStart(@Observes @Initialized(ApplicationScoped.class) Object pointless) {
        try {
            OracleDataSource dataSource = createDataSource();
            StorageProvider provider = new OracleStorageProvider(dataSource, DatabaseOptions.SKIP_CREATE);
            JobScheduler jobScheduler = JobRunr.configure().useStorageProvider(provider)
                    .useBackgroundJobServer().useDashboard(65432).initialize().getJobScheduler();

            jobScheduler.enqueue(() -> availableInterface.checkAllSystems());
            
        } catch (Exception e) {
            LOG.error("JobRunr Exception beim Start:", e);
        }
    }
}
In Zeile 16 komme ich ohne Probleme an, aber in der AvailableInterface ist jede Injection null zur Laufzeit:
Java:
@Dependent
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    private static final int MAX_CHAR = 299;
    
    private LocalDateTime time;
    private UnavailableConfig unavailableConfig;

    @Resource private ManagedExecutorService mes;
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject transient private ToolAvailableInterface toolAvailableInterface;
    @Inject transient private ToolMaintenanceTimeInterface toolMaintenanceTimesInterface;
    @Inject transient private ConfigurationFactory configurationFactory;
    @Inject private Mailer mailer;

    @Inject private Instance<ReferenceController> refControllers;

    public void checkAllSystems() {
        unavailableConfig = configurationFactory.getUnavailableConfig();
        time = LocalDateTime.now();
        // alle Tasks ausführen, auf das Ergebnis warten.
        List<Future<ConnectorStatus>> results = new ArrayList<>();
        try {
            results = mes.invokeAll(createTasks());
        } catch (InterruptedException e) {
            LOG.error("Fehler im Available Executor:", e);
        }

        List<ConnectorStatus> stateList = new ArrayList<>();
        List<ToolAvailable> availableList = toolAvailableInterface.loadAllToolsByServer();
        
        for (Future<ConnectorStatus> result : results) {
            try {
                ConnectorStatus c = result.get();
                stateList.add(c);
            } catch (Exception ex) {
                LOG.error("Fehler im Available Executor:", ex);
            }
        }

        updateAvailableList(stateList, availableList);
        checkMailSend(availableList);
        toolAvailableInterface.persistList(availableList);
        connectorStatusDbInterface.insertConnectorStates(stateList);
    }
}

Ich verstehe nicht ganz warum das alles null ist, und was ich ändern sollte. Wenn ich die Klasse ApplicationScoped mache, dann knallt es schon beim einfachen Aufruf.
 

Zurück
Oben