Impossibile implementare Drools KieSession Persistence in Spring Boot

Oct 06 2020

Stavo cercando di implementare Drools con la funzione di persistenza di KieSession, in un progetto Spring Boot Maven. Ho seguito questa documentazione per l'implementazione. È stato in grado di farlo in una normale applicazione Java, ma ricevo delle eccezioni mentre provo a farlo in un'applicazione Spring Boot.

Di seguito è riportata l'implementazione.

La struttura del progetto

Classe di configurazione

@Configuration
public class PersistentDroolConfig {

    public static Long KIE_SESSION_ID;
    private final KieServices kieServices = KieServices.Factory.get();

    @Bean
    public KieSession kieSession() {
        KieSession kieSession = kieServices.getStoreServices().newKieSession(getKieBase(), null, getEnv());
        PersistentDroolConfig.KIE_SESSION_ID = kieSession.getIdentifier();
        return kieSession;
    }

    public KieServices getKieServices() {
        initDataSource();
        return kieServices;
    }

    public KieBase getKieBase() {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
        final KieRepository kieRepository = kieServices.getRepository();
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        KieModule kieModule = kb.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        return kieContainer.getKieBase();
    }

    public Environment getEnv() {
        Environment env = kieServices.newEnvironment();
        env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, Persistence.createEntityManagerFactory("org.drools.persistence.jpa"));
        env.set(EnvironmentName.TRANSACTION_MANAGER, TransactionManagerServices.getTransactionManager());
        return env;
    }

    private void initDataSource() {
        PoolingDataSource ds = new PoolingDataSource();
        ds.setUniqueName("jdbc/BitronixJTADataSource");
        ds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setMaxPoolSize(3);
        ds.setAllowLocalTransactions(true);
        ds.getDriverProperties().put("user", "root");
        ds.getDriverProperties().put("password", "1234");
        ds.getDriverProperties().put("URL", "jdbc:mysql://localhost:3306/drool_demo");
        ds.init();
    }
}

Classe controller

@RestController
public class OfferController {

    @Autowired
    private KieSession kieSession;

    @GetMapping("/order/{card-type}/{price}")
    public Order order(@PathVariable("card-type") String cardType, @PathVariable int price) {
        Order order = new Order(cardType, price);
        kieSession.insert(order);
        kieSession.fireAllRules();
        return order;
    }
}

Classe di fatti

public class Order implements Serializable {

    private String name;
    private String cardType;
    private int discount;
    private int price;

    public Order(String cardType, int price) {
        this.cardType = cardType;
        this.price = price;
    }

    // setters and getters

    // toString()
}

persistence.xml

<persistence version="2.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd
                      http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
    xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="org.drools.persistence.jpa"
        transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/BitronixJTADataSource</jta-data-source>
        <class>org.drools.persistence.info.SessionInfo</class>
        <class>org.drools.persistence.info.WorkItemInfo</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.max_fetch_depth" value="3" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup" />
        </properties>
    </persistence-unit>
</persistence>

Le dipendenze incluse nel file pom.xml sono le seguenti:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.github.marcus-nl.btm</groupId>
            <artifactId>btm</artifactId>
            <version>3.0.0-mk1</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>${drools-version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools-version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-persistence-jpa</artifactId> <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

Lo stacktrace di errore:

Caused by: org.hibernate.engine.jndi.JndiException: Unable to lookup JNDI name [jdbc/BitronixJTADataSource]

Caused by: javax.naming.NameNotFoundException: unable to find a bound object at name 'jdbc/BitronixJTADataSource'

Il progetto può anche essere trovato qui in questo repository .

AGGIORNAMENTO 1:

Dopo aver implementato la risposta di @jccampanero, ho uno stacktrace più recente:

Caused by: org.hibernate.HibernateException: Unable to perform isolated work

Caused by: java.sql.SQLSyntaxErrorException: Table 'drool_demo.sessioninfo_id_seq' doesn't exist

AGGIORNAMENTO 2:

Dopo aver scavato ulteriormente, ho visto che Drools non sta creando le tabelle necessarie a causa di alcuni errori di sintassi. Ho pubblicato solo i messaggi di eccezione importanti qui poiché Stackoverflow ha un limite di testo. Eccolo:

Hibernate: drop table if exists SessionInfo
Hibernate: drop table if exists WorkItemInfo
Hibernate: create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM
2020-10-09 23:49:59.554  WARN 11376 --- [           main] o.h.t.s.i.ExceptionHandlerLoggedImpl     : GenerationTarget encountered exception accepting command : Error executing DDL "create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM" via JDBC Statement


org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM" via JDBC Statement

Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'type=MyISAM' at line 1

Hibernate: create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM
2020-10-09 23:49:59.556  WARN 11376 --- [           main] o.h.t.s.i.ExceptionHandlerLoggedImpl     : GenerationTarget encountered exception accepting command : Error executing DDL "create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM" via JDBC Statement

org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM" via JDBC Statement

Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'type=MyISAM' at line 1

Risposte

1 jccampanero Oct 08 2020 at 15:23

Penso che ci sia un problema nella tua configurazione. Il getKieServicesmetodo della PersistentDroolConfigclasse non viene mai invocato e, di conseguenza, nemmeno il metodo initDataSourceche inizializza l'origine dati.

Forse puoi provare a modificare il tuo PersistentDroolConfig, qualcosa del genere:

@Configuration
public class PersistentDroolConfig {

    public static Long KIE_SESSION_ID;

    private KieServices kieServices;

    @PostContruct
    private void init() {
        this.initDataSource();
        this.kieServices = KieServices.Factory.get();
    }

    @Bean
    public KieSession kieSession() {
        KieSession kieSession;
        if (KIE_SESSION_ID == null) {
            kieSession = createNewKieSession();
            KIE_SESSION_ID = kieSession.getIdentifier();
            return kieSession;
        } else {
            kieSession = getPersistentKieSession();
            KIE_SESSION_ID = kieSession.getIdentifier();
            return kieSession;
        }
    }

    public KieBase getKieBase() {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
        final KieRepository kieRepository = kieServices.getRepository();
        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        KieModule kieModule = kb.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        KieBase kieBase = kieContainer.getKieBase();
        return kieBase;
    }

    public Environment getEnv() {
        Environment env = kieServices.newEnvironment();
        env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, Persistence.createEntityManagerFactory("org.drools.persistence.jpa"));
        env.set(EnvironmentName.TRANSACTION_MANAGER, TransactionManagerServices.getTransactionManager());
        return env;
    }

    private KieSession createNewKieSession() {
        KieSession kieSession = kieServices.getStoreServices().newKieSession(getKieBase(), null, getEnv());
        PersistentDroolConfig.KIE_SESSION_ID = kieSession.getIdentifier();
        return kieSession;
    }

    private KieSession getPersistentKieSession() {
        return kieServices.getStoreServices().loadKieSession(KIE_SESSION_ID, getKieBase(), null, getEnv());
    }

    private void initDataSource() {
        PoolingDataSource ds = new PoolingDataSource();
        ds.setUniqueName("jdbc/BitronixJTADataSource");
        ds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setMaxPoolSize(3);
        ds.setAllowLocalTransactions(true);
        ds.getDriverProperties().put("user", "root");
        ds.getDriverProperties().put("password", "1234");
        ds.getDriverProperties().put("URL", "jdbc:mysql://localhost:3306/drool_demo");
        ds.init();
    }
}

AGGIORNARE

Come affermato nei diversi commenti, dopo aver apportato queste modifiche, è sorto un problema relativo alla SESSIONINFO_ID_SEQsequenza utilizzata per generare i valori del campo iddell'entità SessionInfo.

Il problema sembrava essere correlato alla versione di Hibernate e MySQL utilizzata.

Per risolvere il problema, è necessario apportare diverse modifiche al persistence.xmlfile.

Innanzitutto, dovrebbe essere inclusa la seguente proprietà:

<property name="hibernate.id.new_generator_mappings" value="false" />

Viene spesso utilizzato negli esempi e nei casi di test di Drools . Vedi questo fantastico articolo di Vlad Mihalcea per una spiegazione approfondita.

L'inclusione di questa proprietà di configurazione ha generato un nuovo problema relativo al dialetto MySQL utilizzato. Dovrebbe essere cambiato in:

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>

Con questa configurazione, l'applicazione dovrebbe funzionare senza problemi.

1 Mike Oct 08 2020 at 14:48

Hai configurato l'origine dati jndi nel tuo contenitore?

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
    maxTotal="100" maxIdle="30" maxWaitMillis="10000"
    username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/javatest"/>

Esempio di origine dati jndi Tomcat