I'm trying to make a springboot Redis caching demo.
Here is the Redis configuration class:
#Configuration
#EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
#Value("${custom.config.redis.ttl}")
private Integer timeTtl;
#Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(timeTtl),
this.getRedisCacheConfigurationMap()
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(10);
redisCacheConfigurationMap
.put("SsoCache", this.getRedisCacheConfigurationWithTtl(24 * 60 * 60));
redisCacheConfigurationMap
.put("BasicDataCache", this.getRedisCacheConfigurationWithTtl(30 * 60));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(BasicPolymorphicTypeValidator.builder().build(),
ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
redis service:
#Service
#Slf4j
public class RedisService {
private final StringRedisTemplate redisTemplate;
public RedisService(StringRedisTemplate redisTemplate) {
Assert.notNull(redisTemplate, "redisTemplate is required");
this.redisTemplate = redisTemplate;
}
#Cacheable(value = "user", key = "#userRedis.getId()")
public UserRedis saveUser(UserRedis userRedis) {
log.trace("user{} is saved", userRedis);
return userRedis;
}
}
test class:
#SpringBootTest
class RedisServiceTest {
#Autowired
private RedisService redisService;
#Autowired
private StringRedisTemplate stringRedisTemplate;
#Autowired
private RedisTemplate redisTemplate;
#Test
void saveUser() {
UserRedis japhy = UserRedis.builder().id(3L).name("japhy").age(10L).build();
UserRedis userRedis = redisService.saveUser(japhy);
Assertions.assertThat(userRedis).isNotNull();
Assertions.assertThat(userRedis.getId()).isEqualTo(3L);
Assertions.assertThat(userRedis.getName()).isEqualTo("japhy");
Assertions.assertThat(userRedis.getAge()).isEqualTo(10L);
}
}
model class:
#Builder
public class UserRedis implements Serializable {
private static final long serialVersionUID = -1541408164202572383L;
private Long id;
private String name;
private Long age;
public UserRedis() {
}
public UserRedis(Long id, String name, Long age) {
this.id = id;
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
}
application.yml:
spring:
redis:
database: 0
# Redis服务器地址
host: 39.99.155.113
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
lettuce:
pool:
max-active: 8
max-wait: -1s
max-idle: 8
min-idle: 0
cache:
redis:
time-to-live: 180s #缓存超时时间ms
cache-null-values: false #是否缓存空值
type: redis
custom:
config:
redis:
ttl: 1800
I can save the modal class to Redis successfully. But when I try to read it from Redis. It throws an exception:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'com.japhy.examples.model.UserRedis' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution
at [Source: (byte[])"["com.japhy.examples.model.UserRedis",{"id":3,"name":"japhy","age":10}]"; line: 1, column: 39]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.japhy.examples.model.UserRedis' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution
at [Source: (byte[])"["com.japhy.examples.model.UserRedis",{"id":3,"name":"japhy","age":10}]"; line: 1, column: 39]
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:75)
at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:48)
at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:272)
at org.springframework.data.redis.cache.RedisCache.deserializeCacheValue(RedisCache.java:260)
at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:94)
at org.springframework.cache.support.AbstractValueAdaptingCache.get(AbstractValueAdaptingCache.java:58)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:73)
at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:554)
at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:519)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:401)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at com.japhy.examples.RedisService$$EnhancerBySpringCGLIB$$e0f4e77a.saveUser(<generated>)
at com.japhy.examples.RedisServiceTest.saveUser(RedisServiceTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.japhy.examples.model.UserRedis' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution
at [Source: (byte[])"["com.japhy.examples.model.UserRedis",{"id":3,"name":"japhy","age":10}]"; line: 1, column: 39]
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
at com.fasterxml.jackson.databind.DeserializationContext.invalidTypeIdException(DeserializationContext.java:1758)
at com.fasterxml.jackson.databind.DatabindContext._throwSubtypeClassNotAllowed(DatabindContext.java:291)
at com.fasterxml.jackson.databind.DatabindContext.resolveAndValidateSubType(DatabindContext.java:248)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver._typeFromId(ClassNameIdResolver.java:72)
at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:66)
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:156)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:97)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:712)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3309)
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:73)
... 79 more
I have read the Spring document in https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:serializer. But still have no idea how to fix the problem.
Any advice? thanks!
As deprecated method is still available, we can stole the implementation from there :)
This is how it looks in my code now:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
The main point here is LaissezFaireSubTypeValidator.instance, cause it creates the necessary PolymorphicTypeValidator.
Related
I have searched StackOverflow for an answer to my question. While I have found many offered solutions, none work in my situation.
I have to use MapStruct for a project at work, so I am trying to learn it through Tutorialspoint. However, instead of using Maven as the tutorial suggests, I am using Gradle, which I have to do because my company uses Gradle instead of Maven. I have seen answers in StackOverflow that moving the project to Maven gets it to work, but that is not an option in my case.
I am using version 1.4.2.Final instead of 1.5.0.RC1 because I had a different issue when using the latter that resolved by switching to the former.
Here are the parts of my code:
build.gradle
plugins {
id 'org.springframework.boot' version '2.6.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.tutorialspoint'
version = '2.4.2'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation group: 'org.mapstruct', name: 'mapstruct', version: '1.4.2.Final'
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final"
compileOnly 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
tasks.named('test') {
useJUnitPlatform()
}
compileJava {
options.compilerArgs += [
'-Amapstruct.suppressGeneratorTimestamp=true',
'-Amapstruct.suppressGeneratorVersionInfoComment=true',
'-Amapstruct.verbose=true'
]
}
Student.java
package com.tutorialspoint.mapstructproject;
public class Student {
private int id;
private String name;
private String className;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
StudentEntity.java
package com.tutorialspoint.mapstructproject;
public class StudentEntity {
private int id;
private String name;
private String classVal;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassVal() {
return classVal;
}
public void setClassVal(String classVal) {
this.classVal = classVal;
}
}
StudentMapper.java
package com.tutorialspoint.mapstructproject;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
#Mapper(componentModel="spring")
public interface StudentMapper {
#Mapping(target="className", source="classVal")
Student getModelFromEntity(StudentEntity student);
#Mapping(target="classVal", source="className")
StudentEntity getEntityFromModel(Student student);
}
StudentMapperTest.java
package com.tutorialspoint.mapstructproject;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import com.tutorialspoint.mapstructproject.StudentEntity;
import com.tutorialspoint.mapstructproject.StudentMapper;
import com.tutorialspoint.mapstructproject.Student;
public class StudentMapperTest {
private StudentMapper studentMapper = Mappers.getMapper(StudentMapper.class);
#Test
public void testEntityToModel() {
StudentEntity entity = new StudentEntity();
entity.setClassVal("X");
entity.setName("John");
entity.setId(1);
Student model = studentMapper.getModelFromEntity(entity);
assertEquals(entity.getClassVal(), model.getClassName());
assertEquals(entity.getName(), model.getName());
assertEquals(entity.getId(), model.getId());
}
#Test
public void testModelToEntity() {
Student model = new Student();
model.setId(1);
model.setName("John");
model.setClassName("X");
StudentEntity entity = studentMapper.getEntityFromModel(model);
assertEquals(entity.getClassVal(), model.getClassName());
assertEquals(entity.getName(), model.getName());
assertEquals(entity.getId(), model.getId());
}
}
Here is the stack trace:
java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.tutorialspoint.mapstructproject.StudentMapper
at org.mapstruct.factory.Mappers.getMapper(Mappers.java:61)
at com.tutorialspoint.mapstructproject.StudentMapperTest.<init>(StudentMapperTest.java:11)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:550)
at org.junit.jupiter.engine.execution.ConstructorInvocation.proceed(ConstructorInvocation.java:56)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:73)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:355)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:302)
at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:280)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
at java.base/java.util.Optional.orElseGet(Optional.java:369)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271)
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:84)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:768)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.lang.ClassNotFoundException: Cannot find implementation for com.tutorialspoint.mapstructproject.StudentMapper
at org.mapstruct.factory.Mappers.getMapper(Mappers.java:75)
at org.mapstruct.factory.Mappers.getMapper(Mappers.java:58)
... 68 more
UPDATE: Someone asked about the build file. I don't see a build file in my project, probably because the build never succeeds due to the error. Here is my file structure:
For me works:
implementation group: 'org.mapstruct', name: 'mapstruct', version: '1.5.3.Final'
implementation group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.5.3.Final'
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.5.3.Final'
Taken from https://github.com/mapstruct/mapstruct-examples/blob/main/mapstruct-lombok/build.gradle
Optionally works without (please confirm it):
annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.5.3.Final'
Using a Pageable parameter in a Spring Data JPA Repository allows for specifying fields to sort by like: PageRequest.of(0, 50, Sort.by("field1", "field2")), which would sort by field1 and field2 ascending.
It works by appending an ORDER BY clause directly by doing SQL injection which would result in a JPA query like SELECT a FROM SomeEntity a ORDER BY field1, field2. However, if a non-existing field name is passed in it would result in a org.springframework.dao.InvalidDataAccessApiUsageException as seen below.
How do you whitelist, only allow specific fields, or validate the sorting without adding boilerplate code in a service that wraps the repository? Same goes for in a #RestController ensuring that a 400 level HttpStatus.BAD_REQUEST is returned to the API?
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:531)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:154)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:149)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy340.searchPaged(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.zeroturnaround.jrebel.integration.springdata.RepositoryReloadingProxyFactoryBuilder$ReloadingMethodHandler.invoke(RepositoryReloadingProxyFactoryBuilder.java:80)
...
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:725)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:113)
at jdk.internal.reflect.GeneratedMethodAccessor749.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
at com.sun.proxy.$Proxy265.createQuery(Unknown Source)
at jdk.internal.reflect.GeneratedMethodAccessor749.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
at com.sun.proxy.$Proxy265.createQuery(Unknown Source)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.createJpaQuery(AbstractStringBasedJpaQuery.java:150)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateQuery(AbstractStringBasedJpaQuery.java:86)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:226)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:175)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 127 more
Caused by: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.hibernate.QueryException.generateQueryException(QueryException.java:120)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:604)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:716)
... 154 more
Caused by: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:77)
at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:71)
at org.hibernate.persister.entity.AbstractEntityPersister.toType(AbstractEntityPersister.java:2043)
at org.hibernate.hql.internal.ast.tree.FromElementType.getPropertyType(FromElementType.java:412)
at org.hibernate.hql.internal.ast.tree.FromElement.getPropertyType(FromElement.java:520)
at org.hibernate.hql.internal.ast.tree.DotNode.getDataType(DotNode.java:694)
at org.hibernate.hql.internal.ast.tree.DotNode.prepareLhs(DotNode.java:269)
at org.hibernate.hql.internal.ast.tree.DotNode.resolve(DotNode.java:209)
at org.hibernate.hql.internal.ast.HqlSqlWalker.resolve(HqlSqlWalker.java:1053)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.expr(HqlSqlBaseWalker.java:1303)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderExpr(HqlSqlBaseWalker.java:1887)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderExprs(HqlSqlBaseWalker.java:1681)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderClause(HqlSqlBaseWalker.java:1654)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:666)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:325)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:273)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
... 160 more
I ended up using JSR-303 validations on the repository methods to whitelist the sort fields.
Enable method validation post processor to run JSR-303 validation annotations at the method level.
ValidationConfig.java
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Create a validation that takes in a list of sort fields to validate against.
AllowSortFields.java
#Documented
#Constraint(validatedBy = {AllowSortFieldsValidator.class})
#Target({ANNOTATION_TYPE, TYPE, FIELD, PARAMETER})
#Retention(RUNTIME)
public #interface AllowSortFields {
String message() default "Sort field values provided are not within the allowed fields that are sortable.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Specify an array of fields that are allowed.
*
* #return the allowed sort fields
*/
String[] value() default {};
}
AllowSortFieldsValidator.java
/**
* Validates a list of sort fields within a Pageable against an allowed list.
*/
public class AllowSortFieldsValidator implements ConstraintValidator<AllowSortFields, Pageable> {
private List<String> allowedSortFields;
static final String PROPERTY_NOT_FOUND_MESSAGE = "The following sort fields [%s] are not within the allowed fields. "
+ "Allowed sort fields are: [%s]";
#Override
public void initialize(AllowSortFields constraintAnnotation) {
allowedSortFields = Arrays.asList(constraintAnnotation.value());
}
#Override
public boolean isValid(Pageable value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (CollectionUtils.isEmpty(allowedSortFields)) {
return true;
}
// ignore unsorted
Sort sort = value.getSort();
if (sort.isUnsorted()) {
return true;
}
String fieldsNotFound = fieldsNotFoundAsCommaDelimited(sort);
// all found fields are allowed
if (StringUtils.isEmpty(fieldsNotFound)) {
return true;
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(String.format(PROPERTY_NOT_FOUND_MESSAGE, fieldsNotFound, String.join(",", allowedSortFields)))
.addConstraintViolation();
return false;
}
private String fieldsNotFoundAsCommaDelimited(Sort sort) {
String fieldsNotFound = sort.stream()
.map(order -> order.getProperty())
.filter(property -> !allowedSortFields.contains(property))
.collect(joining(","));
return fieldsNotFound;
}
}
AllowSortFieldsValidatorSmallTest.java
public class AllowSortFieldsValidatorSmallTest {
private static final String[] ALLOWED_SORT_FIELDS = new String[]{"allowed1", "allowed2"};
private static final String ALLOWED_SORT_FIELDS_DELIMITED = String.join(",", Arrays.asList(ALLOWED_SORT_FIELDS));
private static Validator validator;
#BeforeClass
public static void setupValidator() throws Exception {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void isValid_TwoOfFourFieldsAllowed_FalseWithExpectedMessageExplainingDisallowedFields() {
List<String> sortFields = List.of("allowed1", "allowed2|desc", "notfound1", "not.found2");
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
String expected = String.format(AllowSortFieldsValidator.PROPERTY_NOT_FOUND_MESSAGE, "notfound1,not.found2", ALLOWED_SORT_FIELDS_DELIMITED);
String actual = getConstraintMessages(constraintViolations);
assertEquals(expected, actual);
}
#Test
public void isValid_NoSortFields_True() {
List<String> sortFields = null;
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_EmptyAllowedSortFields_True() {
List<String> sortFields = List.of("allowed1", "allowed2|desc", "notfound1", "not.found2");
EmptyAllowedSortFields toValidate = newEmptyAllowedSortFields(sortFields);
Set<ConstraintViolation<EmptyAllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_AllSortFieldsFoundAsAllowed_True() {
List<String> sortFields = Arrays.asList(ALLOWED_SORT_FIELDS);
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_NullValue_True() {
AllowedSortFields toValidate = new AllowedSortFields();
toValidate.pageable = null;
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
private String getConstraintMessages(Set<ConstraintViolation<AllowedSortFields>> constraintViolations) {
String actual = constraintViolations.stream()
.map(c -> c.getMessage())
.collect(joining(","));
return actual;
}
private AllowedSortFields newAllowedSortFields(List<String> sortFields) {
AllowedSortFields toValidate = new AllowedSortFields();
toValidate.pageable = new CustomPageable().sort(sortFields);
return toValidate;
}
private EmptyAllowedSortFields newEmptyAllowedSortFields(List<String> sortFields) {
EmptyAllowedSortFields toValidate = new EmptyAllowedSortFields();
toValidate.pageable = new CustomPageable().sort(sortFields);
return toValidate;
}
public class AllowedSortFields {
#AllowSortFields({"allowed1", "allowed2"})
public Pageable pageable;
}
public class EmptyAllowedSortFields {
#AllowSortFields
public Pageable pageable;
}
}
Finally the usage within the repository. Be sure to put #Validated at the top of the class.
ExampleSearchRepository.java
public interface ExampleSearchRepository extends JpaRepository<ExampleSearch, Integer>,
JpaSpecificationExecutor<ExampleSearch>, PagingAndSortingRepository<ExampleSearch, Integer> {
public Page<ExampleSearch> search(
#Param("searchCriteria") ExampleSearchCriteria searchCriteria,
#AllowSortFields({"field1","subfield.name"}) Pageable pageable);
I need to test a public method in spring Controller class. Unfortunately my understanding of Spring is miniscule. All i know is that there are 2 repository classes that are wired to database and the method i want to test retrieves record from both databases like in the example:
#Controller
public class CalculateController {
static double ZERO = 0.00;
static double PROFIT_BOUND = 0.01;
static int DECIMAL_PLACES = 2;
static double MARGIN_PERCENT = 10;
static double PERCENT_DIVIDER = 100;
static boolean renderTable = false;
#Autowired (required = false)
private StateRepository stateRepository;
#Autowired (required = false)
private ProductRepository productRepository;
#Autowired (required = false)
private InternationalTransportCostsRepository internationalTransportCostsRepository;
// some other code unrelated to topic
public Double[] calculateProfitUSA(State state, Product product, Double finalPrice) {
Double[] tab;
if (getTax(state.getId(), product.getCategory()) == ZERO) {
tab = calculateMarginWithoutTaxUSA(state, product, finalPrice);
} else {
tab = calculateMarginWithTaxUSA(state, product, finalPrice);
}
return tab;
}
private Double[] calculateMarginWithTaxUSA(State state, Product product, Double finalPrice) {
Double[] tab = new Double[3];
double profit = calcUSA(state, product, finalPrice);
double estimatedPrice = (product.getCurrentPrice() + state.getTransportCost());
estimatedPrice = estimatedPrice + (estimatedPrice * (getTax(state.getId(), product.getCategory()) / PERCENT_DIVIDER));
double margin = estimatedPrice * MARGIN_PERCENT / PERCENT_DIVIDER;
estimatedPrice = estimatedPrice + (estimatedPrice * MARGIN_PERCENT / PERCENT_DIVIDER);
double currentProfit = calcUSA(state, product, estimatedPrice);
while (currentProfit < margin) {
estimatedPrice += PROFIT_BOUND;
currentProfit = calcUSA(state, product, estimatedPrice);
}
tab[0] = Precision.round(profit, DECIMAL_PLACES);
tab[1] = Precision.round(estimatedPrice, DECIMAL_PLACES);
tab[2] = Precision.round(margin, DECIMAL_PLACES);
return tab;
}
public Double getTax(Long id, String categoryName) {
if (categoryName.equals("Groceries")) {
return stateRepository.findById(id).get().getGroceriesTax();
} else if (categoryName.equals("Prepared-food")) {
return stateRepository.findById(id).get().getPreparedFoodTax();
} else if (categoryName.equals("Prescription-drug")) {
return stateRepository.findById(id).get().getPrescriptionDrugTax();
} else if (categoryName.equals("Non-prescription-drug")) {
return stateRepository.findById(id).get().getNonPrescriptionDrugTax();
} else if (categoryName.equals("Clothing")) {
return stateRepository.findById(id).get().getClothingTax();
} else if (categoryName.equals("Intangibles")) {
return stateRepository.findById(id).get().getIntangiblesTax();
}
return null;
}
I have been trying to search a solution on google for 2 days, I failed. All i managed to come up with is this:
#WebMvcTest(CalculateController.class)
public class CalculateAlgorithmsTests {
#InjectMocks
private CalculateController calc;
#Mock
private StateRepository stateRepository;
#Mock
private ProductRepository productRepository;
private List<State> states;
private List<Product> products;
#Test
public void calculateProfitUSATest(){
states = CSVReader.readStatesFromCSV("csvFiles/states.csv");
products = CSVReader.readProductsFromCSV("csvFiles/products.csv");
productRepository.saveAll(CSVReader.readProductsFromCSV("csvFiles/products.csv"));
stateRepository.saveAll(CSVReader.readStatesFromCSV("csvFiles/states.csv"));
State Connecticut = states.get(5);
Product Apple = products.get(0);
double [] expected = {25,12,32};
Assertions.assertEquals(expected,calc.calculateProfitUSA(Connecticut,Apple,30.23));
}
}
And I get this response from console:
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:141)
at com.taxcalc.controllers.CalculateController.getTax(CalculateController.java:215)
at com.taxcalc.controllers.CalculateController.calculateProfitUSA(CalculateController.java:167)
at com.taxcalc.CalculateAlgorithmsTests.calculateProfitUSATest(CalculateAlgorithmsTests.java:41)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:117)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:184)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:180)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1507)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1507)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
It looks like there is no result for your given id when calling stateRepository.findById(id).
You should mock that in your test by using sth like
State someState = new State()
Mockito.when(stateRepository.findById(someId)).thenReturn(Optional.of(someState))
You need to make sure that someId has the same value as the State you are calling the public method with.
The calls of stateRepository.saveAll() could then be omitted because you would explicitly mock the behaviour rather than using the given data from the CSV.
This is just a suggestion.
Another suggestion is to move this kind of logic into a service. Controllers usually define the HTTP related behaviour rather than haven business logic.
I am very new in Spring Data JPA and I have the following problem trying to insert a record into a database table (I am using MariaDB) in a Spring Boot application.
This is my database table named customer_order:
'id', 'bigint(20)', 'NO', 'PRI', NULL, 'auto_increment'
'name', 'varchar(255)', 'NO', '', NULL, ''
'address', 'varchar(255)', 'NO', '', NULL, ''
'product', 'varchar(255)', 'NO', '', NULL, ''
'order_date_time', 'datetime', 'NO', '', NULL, ''
'quantity', 'tinyint(4)', 'NO', '', NULL, ''
This table is mapped with the CustomerOrder model class in my application:
#Entity // This tells Hibernate to make a table out of this class
#Table(name = "customer_order")
public class CustomerOrder {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name = "name")
private String fullName;
private String address;
private String product;
#Column(name = "order_date_time")
private String orderDate;
private int quantity;
public CustomerOrder() {
super();
}
public CustomerOrder(String fullName, String address, String product, String orderDate, int quantity) {
super();
//this.id = id;
this.fullName = fullName;
this.address = address;
this.product = product;
this.orderDate = orderDate;
this.quantity = quantity;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
public String getOrderDate() {
return orderDate;
}
public void setOrderDate(String orderDate) {
this.orderDate = orderDate;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
#Override
public String toString() {
return "CustomerOrder [id=" + id + ", fullName=" + fullName + ", address=" + address + ", product=" + product
+ ", orderDate=" + orderDate + ", quantity=" + quantity + "]";
}
}
I have this Spring Data JPA repository interface:
public interface OrderRepository extends CrudRepository<CustomerOrder, Integer> {
}
This is used by this service class to perfrom the database interaction:
#Service
public class OrderServiceImpl implements OrderService {
#Autowired
OrderRepository orderRepository;
#Override
public List<CustomerOrder> getOrdersList() {
List<CustomerOrder> result = (List<CustomerOrder>) orderRepository.findAll();
return result;
}
#Override
public void insertOrder(CustomerOrder order) {
orderRepository.save(order);
}
}
Finnally I have this test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { Application.class })
#WebAppConfiguration
#ActiveProfiles(profiles = { "no-liquibase" })
public class ExcelResourceIntegrationTest {
#Autowired
OrderServiceImpl orderService;
#Test
public void insertOrderServiceTest() {
CustomerOrder testOrder = new CustomerOrder("TEST1",
"Test Street 35 - New York - USA",
"TEST PRODUCT 1", "2020-03-24 12:10:00",
3);
orderService.insertOrder(testOrder);
}
#Test
public void getOrdersListServiceTest() {
List<CustomerOrder> ordersList = orderService.getOrdersList();
assertThat(ordersList).isNotEmpty();
}
}
The problem is that performing the previous insertOrderServiceTest() it enters into the called service method but when this line is executed:
orderRepository.save(order);
I obtain the following error message into the console:
2020-03-24 04:20:56.915 INFO 4843 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#24e7087a, org.springframework.security.web.context.SecurityContextPersistenceFilter#3a45308f, org.springframework.security.web.header.HeaderWriterFilter#731fae, org.springframework.security.web.authentication.logout.LogoutFilter#52bc6fcf, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#64b8eb96, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#789d8fd6, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#5bb3dee7, org.springframework.security.web.session.SessionManagementFilter#4ae6451d, org.springframework.security.web.access.ExceptionTranslationFilter#58ad0586, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#1e36baca]
2020-03-24 04:20:57.670 INFO 4843 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-24 04:20:58.368 INFO 4843 --- [ main] c.s.e.i.ExcelResourceIntegrationTest : Started ExcelResourceIntegrationTest in 8.519 seconds (JVM running for 11.638)
2020-03-24 04:21:07.638 WARN 4843 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1146, SQLState: 42S02
2020-03-24 04:21:07.642 ERROR 4843 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : (conn:63) Table 'db_test.hibernate_sequence' doesn't exist
Query is: select nextval(hibernate_sequence)
and this exception into the stacktrace:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:281)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy106.save(Unknown Source)
at com.springboot.excelapi.service.OrderServiceImpl.insertOrder(OrderServiceImpl.java:27)
at com.springboot.excelapi.integration.ExcelResourceIntegrationTest.insertOrderServiceTest(ExcelResourceIntegrationTest.java:104)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:40)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:71)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:137)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:63)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:67)
at org.hibernate.id.enhanced.SequenceStructure$1.getNextValue(SequenceStructure.java:95)
at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40)
at org.hibernate.id.enhanced.SequenceStyleGenerator.generate(SequenceStyleGenerator.java:523)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:115)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:710)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:696)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
at com.sun.proxy.$Proxy100.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:554)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:371)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:204)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:657)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:621)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 61 more
Caused by: java.sql.SQLSyntaxErrorException: (conn:63) Table 'db_test.hibernate_sequence' doesn't exist
Query is: select nextval(hibernate_sequence)
at org.mariadb.jdbc.internal.util.ExceptionMapper.get(ExceptionMapper.java:139)
at org.mariadb.jdbc.internal.util.ExceptionMapper.getException(ExceptionMapper.java:101)
at org.mariadb.jdbc.internal.util.ExceptionMapper.throwAndLogException(ExceptionMapper.java:77)
at org.mariadb.jdbc.MariaDbStatement.executeQueryEpilog(MariaDbStatement.java:224)
at org.mariadb.jdbc.MariaDbClientPreparedStatement.executeInternal(MariaDbClientPreparedStatement.java:232)
at org.mariadb.jdbc.MariaDbClientPreparedStatement.executeQuery(MariaDbClientPreparedStatement.java:177)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57)
... 93 more
Caused by: org.mariadb.jdbc.internal.util.dao.QueryException: Table 'db_test.hibernate_sequence' doesn't exist
Query is: select nextval(hibernate_sequence)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readErrorPacket(AbstractQueryProtocol.java:1098)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readPacket(AbstractQueryProtocol.java:1030)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.getResult(AbstractQueryProtocol.java:985)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.executeQuery(AbstractQueryProtocol.java:161)
at org.mariadb.jdbc.MariaDbClientPreparedStatement.executeInternal(MariaDbClientPreparedStatement.java:223)
... 97 more
The reason is this property, it uses a sequence from table db_test.hibernate_sequence.
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
if you want to use MySQL incrementing id, please use
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
You can tweak Hibernate to create schema on startup in tests, if you need the sequence.
I have two #NodeEntities mapped via SDN using simple mapping, PersonNode and FamilyNode. FamilyNode has a #RelatedTo collection, children. I also have a FamilyService (using Spring's #Service annotation) with #Transactional annotation on an updateFamily method. This method loads the FamilyNode given an id, and uses a callback interface to modify the node. In one implementation of the callback, I am adding a PersonNode to the children collection, and this is generating the NotInTransactionException, specifically at the point that Neo4J is attempting to create the relationship between the FamilyNode and the PersonNode.
Source code can be found at github and in particular a failing test. Here are relevant bits of the code:
FamilyNode.java:
#NodeEntity
public class FamilyNode implements Family {
#Indexed(indexName = "families", unique = true)
private String id;
#GraphId
private Long identifier;
#RelatedTo(elementClass = PersonNode.class, type = "CHILD")
private Set<Person> children;
void addChild(Person child) {
if (this.children == null) {
this.children = new HashSet<>();
}
this.children.add(child);
}
}
PersonNode.java:
#NodeEntity
public class PersonNode implements Person {
#RelatedTo(elementClass = FamilyNode.class, type = "CHILD", direction = INCOMING)
private Family childOf;
#Indexed(indexName = "people", unique = true)
private String id;
#GraphId
private Long identifier;
}
FamilyRepository.java:
public interface FamilyRepository extends GraphRepository<Family> {
public FamilyNode findById(String id);
}
FamilyServiceImpl.java:
#Service
public class FamilyServiceImpl implements FamilyService {
#Autowired
private FamilyRepository families;
#Autowired
private Neo4jTemplate template;
#Override
public List<Family> getFamilies(String[] ids) {
List<Family> families = new ArrayList<>();
for (String id : ids) {
families.add(getFamily(id));
}
return families;
}
#Override
public Family getFamily(String id) {
return familyNode(id);
}
#Override
#Transactional
public Family createFamily(Family family) {
return lazyLoadRelationships((FamilyNode) this.families.save(family));
}
#Override
#Transactional
public Family updateFamily(String id, FamilyNodeUpdater updater) {
return this.families.save(updater.update(familyNode(id)));
}
private FamilyNode familyNode(String id) {
return lazyLoadRelationships(this.families.findById(id));
}
private FamilyNode lazyLoadRelationships(FamilyNode family) {
this.template.fetch(family.getFather());
this.template.fetch(family.getMother());
this.template.fetch(family.getChildren());
return family;
}
}
and the failing test, FamilyServiceTest.java:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { TestConfig.class })
public class FamilyServiceTest {
#Configuration
#ComponentScan(basePackageClasses = { com.bonevich.ancestral.family.FamilyServiceImpl.class }, resourcePattern = "FamilyServiceImpl.class")
#EnableNeo4jRepositories(basePackageClasses = { com.bonevich.ancestral.family.FamilyNode.class })
static class TestConfig extends Neo4jConfiguration {
#Bean
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabaseBuilder("/data/neo4j/ancestral-familyservicetest/")
.newGraphDatabase();
}
}
#Autowired
private FamilyService families;
#Autowired
private GraphDatabaseService graphDatabaseService;
#Autowired
private Neo4jTemplate neo4jTemplate;
#Test
public void testUpdateFamily() {
this.families.createFamily(FamilyNode.instance("testFamily"));
Transaction tx = this.graphDatabaseService.beginTx();
PersonNode person = PersonNode.instance("John", "Johanson", "M", "a_person");
PersonNode expectedChild = this.neo4jTemplate.save(person);
final long childId = expectedChild.getIdentifier();
tx.success();
tx.finish();
Family actualFamily = this.families.updateFamily("testFamily", new FamilyNodeUpdater() {
#Override
public FamilyNode update(FamilyNode family) {
family.addChild(FamilyServiceTest.this.neo4jTemplate.findOne(childId, PersonNode.class));
return family;
}
});
assertThat(actualFamily.getId(), is("testFamily"));
assertThat(actualFamily.getChildren().get(0), is((Person) expectedChild));
}
}
Running this test yields the following exception stack:
org.neo4j.graphdb.NotInTransactionException
at org.neo4j.kernel.impl.core.NoTransactionState.acquireWriteLock(NoTransactionState.java:43)
at org.neo4j.kernel.impl.transaction.LockType$2.acquire(LockType.java:51)
at org.neo4j.kernel.impl.core.NodeManager.getNodeForProxy(NodeManager.java:473)
at org.neo4j.kernel.InternalAbstractGraphDatabase$6.lookup(InternalAbstractGraphDatabase.java:733)
at org.neo4j.kernel.impl.core.NodeProxy.createRelationshipTo(NodeProxy.java:207)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.obtainSingleRelationship(RelationshipHelper.java:62)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createSingleRelationship(RelationshipHelper.java:142)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createAddedRelationships(RelationshipHelper.java:96)
at org.springframework.data.neo4j.fieldaccess.RelatedToFieldAccessor.createAddedRelationships(RelatedToFieldAccessor.java:78)
at org.springframework.data.neo4j.fieldaccess.RelatedToCollectionFieldAccessorFactory$RelatedToCollectionFieldAccessor.setValue(RelatedToCollectionFieldAccessorFactory.java:68)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.updateValue(ManagedFieldAccessorSet.java:112)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.update(ManagedFieldAccessorSet.java:100)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.add(ManagedFieldAccessorSet.java:126)
at com.bonevich.ancestral.family.FamilyNode.addChild(FamilyNode.java:124)
at com.bonevich.ancestral.family.FamilyServiceTest$1.update(FamilyServiceTest.java:64)
at com.bonevich.ancestral.family.FamilyServiceImpl.updateFamily(FamilyServiceImpl.java:42)
at com.bonevich.ancestral.family.FamilyServiceTest.testUpdateFamily(FamilyServiceTest.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
I have got to believe I am missing something in configuring SDN or Transactions, but have been unable to track it down.
I recently answered this question here:
Spring Data Neo4J - NotInTransactionException while modifying set of nodeEntity
In short, it has to do with the fact that these List are special list which are backed by SDN and any modification is immediately persisted to the DB. If you want to prevent this behaviour, you should use Iterable in your model classes. If you have some more question after reading the link, just post them as a comment.