I am using Drools with Spring Boot 2.3 and I have implemented the persistent aware KieSession, in which MySQL is used for storing the session. I have successfully integrated the default EntityManagerFactory of Spring Boot with Drools but my problem is with transactions. By default, Drools uses Optimistic Lock during transactions but it allows us to use the Pessimistic Lock as well, which is what I want. Now while firing rules, Drools persists/updates the KieSession in MySQL with the following query:
update SessionInfo set lastModificationDate=?, rulesByteArray=?, startDate=?, OPTLOCK=? where id=? and OPTLOCK=?
Now the above statement is executed twice if I do not use transactions using the #Transactional annotation in the method, and if #Transactional is used then the above statement is executed only once after firing the rules.
Now, if I manually change the value of the OPTLOCK field then Drools throws an exception:
javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.drools.persistence.info.SessionInfo#1]
followed by:
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.drools.persistence.info.SessionInfo#1]
I am unable to post the entire Stacktrace due to text length limitations here. The entire stacktrace can be viewed in this GitHub project.
I am not sure whether Drools is using the Pessimistic Lock as defined in the environment. About my session implementation, I want to have a single KieSession since I am using KieSession as a Bean.
Below is my implementation:
The configuration class:
#Configuration
public class DynamicDroolsConfig {
private KieServices kieServices;
private KieFileSystem kieFileSystem;
#Autowired
private PersistentSessionDAO persistentSessionDAO;
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
#Autowired
private PlatformTransactionManager platformTransactionManager;
#PostConstruct
private void init() {
this.kieServices = KieServices.Factory.get();
this.kieFileSystem = kieServices.newKieFileSystem();
}
#Bean
public KieServices getKieServices() {
return this.kieServices;
}
#Bean
public KieContainer getKieContainer() {
kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem).buildAll();
KieModule kieModule = kb.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
#Bean
public KieFileSystem getFileSystem() {
return kieFileSystem;
}
#Bean
public KieSession kieSession() {
List<SessionInfo> sessionDetails = persistentSessionDAO.getSessionDetails();
if (sessionDetails.size() == 0) {
return kieServices.getStoreServices().newKieSession(getKieContainer().getKieBase(), null, getEnv());
} else {
return kieServices.getStoreServices().loadKieSession(sessionDetails.get(0).getId(), getKieContainer().getKieBase(), null, getEnv());
}
}
private Environment getEnv() {
Environment env = kieServices.newEnvironment();
env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, entityManagerFactory);
env.set(EnvironmentName.TRANSACTION_MANAGER, platformTransactionManager);
env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING, true);
env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING_MODE, LockModeType.PESSIMISTIC_FORCE_INCREMENT.name());
return env;
}
}
The controller class:
#RestController
public class MyController {
#Autowired
private KieSession kieSession;
#Transactional
#GetMapping("fire-person")
public void firePerson() {
Person person = new Person();
person.setName("Christy");
kieSession.insert(person);
kieSession.fireAllRules();
}
}
The Fact class
public class Person implements Serializable {
private String name;
private int age;
private String gender;
private String toCompareName;
private String toCompareGender;
// getters and setters
}
The repository interface:
public interface DroolsSessionRepository extends JpaRepository<SessionInfo, Long> {
}
The service class:
#Service
public class PersistentSessionDAO {
#Autowired
private DroolsSessionRepository droolsSessionRepository;
public List<SessionInfo> getSessionDetails() {
return droolsSessionRepository.findAll();
}
}
The runner class:
#EntityScan(basePackages = {"com.sam.springdroolspersistence.entity", "org.drools.persistence.info"})
#EnableJpaRepositories
#SpringBootApplication
public class SpringDroolsPersistenceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDroolsPersistenceApplication.class, args);
}
}
The Drools dependencies used:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-persistence-jpa</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-persistence-jpa</artifactId>
<version>${drools-version}</version>
</dependency>
The code implementation can also be found in this GitHub Project. Any kind of help/suggestions will be much appreciated. Thank you.
Pessimistic locking is implemented only in JBPM see here
There's no such functionality in Drools persistence, SessionInfo will always use OptimisticLocking based on JPA's #Version annotation.
If you need such feature, please file a feature request on Drools' Jira
Related
I have my DAO layers with an expensive method as followed:
#Component
#CacheConfig(cacheNames = {"userStories"})
public class UserStoryDaoImpl implements IUserStoryDao {
#Override
#Cacheable
public List<UserStory> getUserStoriesForProjectAndRelease(UserDto userDto, Set<Integer>
reportProjectId, int releaseId) {
//Slow performing business logic that returns a list
return new ArrayList();
}
and another as
#Component
#CacheConfig(cacheNames = {"features"})
public class FeatureDaoImpl implements IFeatureDao {
#Override
#Cacheable
public List<Features> geFeaturesForProjectAndRelease(UserDto userDto, Set<Integer> reportProjectId,
int releaseId) {
//Slow performing business logic that returns a list
return new ArrayList();
}
}
and my cache config class as :
#Configuration
public class CaffeineCacheConfig {
#Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("features", "userStories");
cacheManager.setCaffeine(caffeineCacheBuilder());
return cacheManager;
}
Caffeine< Object, Object > caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterAccess(5, TimeUnit.MINUTES)
.refreshAfterWrite(2, TimeUnit.MINUTES)
.weakKeys()
.recordStats();
}
}
I am using spring boot :: 2.2.6.RELEASE and my pom include :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.2</version>
</dependency>
Am I missing something in this? I want to maintain a cache "features" &"userStories" and update it asynchronously after call to the DAO method is made.
I am getting following error :
Cannot load configuration class: com.packageName.CaffeineCacheConfig
Caused by: java.lang.NoClassDefFoundError: com/github/benmanes/caffeine/cache/Caffeine
Caused by: java.lang.ClassNotFoundException: com.github.benmanes.caffeine.cache.Caffeine
I have found these reports related to a similar issue: CaffeineGit-1 and CaffeineGit-Related
I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.
I have created a Maven project using spring-data-neo4j. I have also installed the standalone Neo4j Server Community Edition 2.3.3. I am trying to save some Vertex objects to the database and then simply retrieve them to check everything works fine. Then, I would like to be able to open the created db in the standalone server for better visualization.
I am using as dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The configuration class is:
#Configuration
#ComponentScan("neo4j.example")
#EnableAutoConfiguration
#EnableNeo4jRepositories("neo4j.example.repository")
public class App extends Neo4jConfiguration {
public App() {
System.setProperty("username", "neo4j");
System.setProperty("password", "root");
}
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("neo4j.example.model");
}
#Override
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
#Override
public Neo4jServer neo4jServer() {
return new RemoteServer("http://localhost:7474");
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
My NodeEntity looks like:
#NodeEntity
public class Vertex {
private String name;
#GraphId
private Long id;
#Relationship(type = "PAIRS_WITH", direction = "UNDIRECTED")
public Set<Vertex> teammates;
public Vertex() { }
// getters, setters, equals, toString
}
The repository:
#Repository
public interface VertexRepository extends GraphRepository<Vertex> {
Vertex findByName(String name);
List<Vertex> findByTeammatesName(String name);
}
The service:
#Service
public class VertexServiceImpl implements VertexService {
#Autowired
private VertexRepository vertexRepository;
#Override
#Transactional
public Vertex create(Vertex vertex) {
return vertexRepository.save(vertex);
}
#Override
#Transactional
public Iterable<Vertex> findAll() {
return vertexRepository.findAll();
}
//....
}
Then I have a controller with two simple methods to save a vertex and then query the database.
#RestController
#RequestMapping("/api/")
public class GraphController {
#Autowired
VertexService vertexService;
#RequestMapping(value = "addvertex", method = RequestMethod.GET)
public void add() {
Vertex v = new Vertex();
v.setId(1l);
v.setName("name");
Vertex v2 = new Vertex();
v2.setId(2l);
v.worksWith(v2);
vertexService.create(v);
}
#RequestMapping(value = "all", method = RequestMethod.GET)
public Iterable<Vertex> getAll() {
return vertexService.findAll();
}
}
When I save the vertex to the db there is no error. When I call /all the db is empty. I checked messages.log and there is no exception...last lines being:
2016-03-26 14:25:15.716+0000 INFO [o.n.k.i.DiagnosticsManager] Interface Microsoft Wi-Fi Direct Virtual Adapter-WFP 802.3 MAC Layer LightWeight Filter-0000:
2016-03-26 14:25:15.716+0000 INFO [o.n.k.i.DiagnosticsManager] --- INITIALIZED diagnostics END ---
2016-03-26 14:25:15.747+0000 INFO [o.n.k.i.DiagnosticsManager] --- STOPPING diagnostics START ---
2016-03-26 14:25:15.747+0000 INFO [o.n.k.i.DiagnosticsManager] --- STOPPING diagnostics END ---
2016-03-26 14:25:15.747+0000 INFO [o.n.k.i.f.GraphDatabaseFacade] Shutdown started
Any help is fully appreciated!
I managed to solve my problem. The configuration is fine, the problem was I was trying to set the id property of a #NodeEntity object. Even if I remove the #GraphId property the vertex is not saved. This post addresses the same problem.
In Good Relations:The Spring Data Neo4j Guide Book it is mentioned that: "If the field is simply named 'id' then it is not necessary to annotate it with #GraphId as the OGM will use it automatically."
It would be nice if there was some kind of warning/ error message or more explicitly mentioned in the documentation that you cannot setId() and that the node will not be saved in the db if you do. Hope this will save somebody some time!
You are mixing embedded and remote server?
You should look for your data in the remote server.
Also you must have disabled auth for this to work in the server, or you have to provide username (neo4j) and password to your config.
DO NOT START AN EMBEDDED DATABASE ON THE SAME DIRECTORY AS THE SERVER USES
I'm trying to move away from manually-managed transactions to annotation based transactions in my Neo4j application.
I've prepared annotation-based Spring configuration file:
#Configuration
#EnableNeo4jRepositories("xxx.yyy.neo4jplanetspersistence.repositories")
#ComponentScan(basePackages = "xxx.yyy")
#EnableTransactionManagement
public class SpringDataConfiguration extends Neo4jConfiguration
implements TransactionManagementConfigurer{
public SpringDataConfiguration() {
super();
setBasePackage(new String[] {"xxx.yyy.neo4jplanetspojos"});
}
#Bean
public GraphDBFactory graphDBFactory(){
GraphDBFactory graphDBFactory = new GraphDBFactory();
return graphDBFactory;
}
#Bean
public GraphDatabaseService graphDatabaseService() {
return graphDBFactory().getTestGraphDB(); //new GraphDatabaseFactory().newEmbeddedDatabase inside
}
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return neo4jTransactionManager(graphDatabaseService());
}
}
I've marked my repositories with #Transactional:
#Transactional
public interface AstronomicalObjectRepo extends
GraphRepository<AstronomicalObject>{
}
I've marked my unit test classes and test methods with #Transactional and commented old code that used to manually manage transactions:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringDataConfiguration.class},
loader = AnnotationConfigContextLoader.class)
#Transactional
public class AstronomicalObjectRepoTest {
#Autowired
private AstronomicalObjectRepo repo;
#Autowired
private Neo4jTemplate neo4jTemplate;
(...)
#Test #Transactional
public void testSaveAndGet() {
//try (Transaction tx =
//neo4jTemplate.getGraphDatabaseService().beginTx()) {
AstronomicalObject ceres = new AstronomicalObject("Ceres",
1.8986e27, 142984000, 9.925);
repo.save(ceres); //<- BANG! Exception here
(...)
//tx.success();
//}
}
After that change the tests do not pass.
I receive:
org.springframework.dao.InvalidDataAccessApiUsageException: nested exception is org.neo4j.graphdb.NotInTransactionException
I have tried many different things (explicitly naming transaction manager in #Transactional annotation, changing mode in #EnableTransactionManagment...), nothing helped.
Will be very grateful for a clue about what I'm doing wrong.
Thanks in advance!
I found the reason...
SDN does not support newest Neo4j in the terms of transaction.
I believe it is because SpringTransactionManager in neo4j-kernel has gone in 2.2+ releases, but not 100% sure.
On github we can see that 7 hours ago the change was made to fix it:
https://github.com/spring-projects/spring-data-neo4j/blob/master/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/JtaTransactionManagerFactoryBean.java
A quick fix that worked for me was to override neo4jTransactionManager method from Neo4jConfiguration in my configuration, using Neo4jEmbeddedTransactionManager class:
#Override
public PlatformTransactionManager neo4jTransactionManager(GraphDatabaseService graphDatabaseService) {
Neo4jEmbeddedTransactionManager newTxMgr = new Neo4jEmbeddedTransactionManager(graphDatabaseService());
UserTransaction userTransaction = new UserTransactionAdapter( newTxMgr );
return new JtaTransactionManager( userTransaction, newTxMgr );
}
I am using this Spring AOP code in my Spring Boot starter project in STS. After debugging this for some time I don't see any problem with the AspectJ syntax. The Maven dependencies are generated by STS for a AOP starter project. Is there a glaring omission in this code like an annotation ? The other problem could be with the AOP starter project or with the way I try to test the code in a #PostConstruct method.
I installed AJDT but it appears STS should show AspectJ markers in the IDE on its own. Right ? I don't see the markers. What other AspectJ debugging options are included in STS ? -Xlint is what I used in Eclipse/AJDT.
StateHandler.java
public class StateHandler<EVENTTYPE extends EventType> {
private State<EVENTTYPE> state;
private Event<EVENTTYPE> event;
public StateHandler(State<EVENTTYPE> state, Event<EVENTTYPE> event) {
this.state = state;
this.event = event;
}
public void handle( Event<EVENTTYPE> event ){
state = state.handle( event );
}
public State<EVENTTYPE> getState() {
return state;
}
}
DeviceLogger .java
#Aspect
#Component
public class DeviceLogger {
private static Logger logger = Logger.getLogger("Device");
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public void log() {
logger.info( "Logger" );
}
}
LoggerApplication.java
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
SpringApplication.run(LoggerApplication.class, args);
}
#PostConstruct
public void log(){
DeviceState s = DeviceState.BLOCKED;
StateHandler<DeviceEvent> sh = new StateHandler<DeviceEvent>( s,
Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
}
There are 3 obvious things wrong and 1 not so obvious wrong.
Your aspect is wrong and breaks proper method execution. When using an around aspect you must always return Object and use a ProceedingJoinPoint and call proceed() on that.
You are creating new instances of classes yourself, Spring, by default, uses proxy based AOP and will only proxy beans it knows.
In a #PostConstruct method it might be that proxies aren't created yet and that nothing is being intercepted
You need to use class based proxies for that to be enabled add spring.aop.proxy-target-class=true to your application.properties. By default JDK Dynamic Proxies are used which are interface based.
Fix Aspect
Your current aspect doesn't use a ProceedingJoinPoint and as such never does the actual method call. Next to that if you now would have a method that returns a value it would all of a sudden return null. As you aren't calling proceed on the ProceedingJoinPoint.
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
logger.info( "Logger" );
return pjp.proceed();
}
Create a bean to fix proxying and #PostConstruct
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(LoggerApplication.class, args);
StateHandler<DeviceEvent> sh = context.getBean(StateHandler<DeviceEvent>.class);
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
#Bean
public StateHandler<DeviceEvent> auditMessageStateHandler() {
return new StateHandler<DeviceEvent>(DeviceState.BLOCKED, Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
}
}
Add property to enable class proxies
In your application.properties in src\main\resources add the following property with a value of true
spring.aop.proxy-target-class=true