Impossibile implementare Drools KieSession Persistence in Spring Boot
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
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.
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