I'm creating a Spring REST app using Spring Boot 2, Spring Data REST, Spring HATEOAS.
I created this controller:
#Api(tags = "City Entity")
#RepositoryRestController
#RequestMapping(path = "/api/v1")
#PreAuthorize("isAuthenticated()")
public class CityController {
#Autowired
private LocalValidatorFactoryBean validator;
#Autowired
private PagedBeanResourceAssembler<City> pagedBeanResourceAssembler;
#Autowired
private CityService cityService;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
#GetMapping(path = "/cities/search/autocomplete")
public ResponseEntity<?> autocomplete(#RequestParam(name = "city") String city, #RequestParam(name = "country", required = false) String country, Pageable pageable, Locale locale) {
return new ResponseEntity<>(pagedBeanResourceAssembler.toResource(cityService.autocomplete(city, country, pageable)), HttpStatus.OK);
}
}
The service method is:
#Transactional(readOnly = true)
public Page<City> autocomplete(String text, String country, Pageable pageable) {
//my logic
return elasticSearchManager.search(ElasticSearchUtil.getIndexName(City.class), null, City.class, filters, null, pageable);
}
As you can see City bean is not stored in the DB. In fact the bean is:
public class City implements Persistable<Long> {
private Long id;
#NotBlank
private String name;
private String district;
private String region;
private String zipCode;
#NotNull
#Size(min = 2, max = 2)
private String country;
}
and finally this is my PagedBeanResourceAssembler:
#Component
public class PagedBeanResourceAssembler<T> implements ResourceAssembler<Page<T>, PagedResources<T>> {
#Autowired
private EntityLinks entityLinks;
#Override
public PagedResources<T> toResource(Page<T> page) {
PagedResources<T> pagedResources = new PagedResources<T>(page.getContent(), asPageMetadata(page));
return pagedResources;
}
private PagedResources.PageMetadata asPageMetadata(Page<?> page) {
Assert.notNull(page, "Page must not be null!");
return new PagedResources.PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
}
}
When I make a http call I see a WARNING message in the console:
08/02/2019 11:09:35,526 WARN http-nio-8082-exec-1 RepositoryRestMvcConfiguration$ResourceSupportHttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
08/02/2019 11:09:35,527 WARN http-nio-8082-exec-1 MappingJackson2HttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
Not using a PagedResources the error goes away. I don't understand where I'm doing something wrong. I got that HalResourcesSerializer has not a default constructor, but I don't use it directly and I don't understand either why with Entity persisted in the db a controller such this works fine.
How can I fix this problem continuing to use a PagedResource?
======== UPDATE ==========
I add my configuration to give a more detailed view:
CustomConfiguration:
#Configuration
#EnableRetry
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
public static CustomConfiguration INSTANCE;
#PostConstruct
public void init() {
INSTANCE = this;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringSecurityAuditorAware springSecurityAuditorAware() {
return new SpringSecurityAuditorAware();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/i18n/messages");
// messageSource.setDefaultEncoding("UTF-8");
// set to true only for debugging
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
#Bean
public MessageSourceAccessor messageSourceAccessor() {
return new MessageSourceAccessor(messageSource());
}
/**
* Enable Spring bean validation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
*
* #return
*/
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
/**
* Utility class from Google to work with phone numbers {#link https://github.com/googlei18n/libphonenumber}
*
* #return
*/
#Bean
public PhoneNumberUtil phoneNumberUtil() {
return PhoneNumberUtil.getInstance();
}
/**
* To enable SpEL expressions
*
* #return
*/
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
/**
* Define the specific storage manager to use (disk, S3, etc)
*
* #return
*/
#Bean
public StorageManager storageManager() {
return new S3StorageManager();
}
/**
* GRACEFUL SHUTDOWN
*/
#Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
#Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
}
GlobalRepositoryRestConfigurer:
#Configuration
public class GlobalRepositoryRestConfigurer implements RepositoryRestConfigurer {
private Logger log = LogManager.getLogger();
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Autowired
private Validator validator;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry().addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
.allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
}
/**
* ValidationException serialiazer
*
* #return
*/
#Bean
public ValidationExceptionSerializer validationExceptionSerializer() {
return new ValidationExceptionSerializer();
}
#Bean
public CustomValidationExceptionSerializer customValidationExceptionSerializer() {
return new CustomValidationExceptionSerializer();
}
#Bean
public ConstraintViolationExceptionSerializer constraintViolationExceptionSerializer() {
return new ConstraintViolationExceptionSerializer();
}
/**
* Customize Object Mapper
*/
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
/**
* Custom serializer for ConstraintViolationException
* (https://jira.spring.io/browse/DATAREST-593)
*/
try {
SimpleModule constraintExceptionModule = new SimpleModule();
constraintExceptionModule.addSerializer(ConstraintViolationException.class, constraintViolationExceptionSerializer());
constraintExceptionModule.addSerializer(ValidationException.class, validationExceptionSerializer());
constraintExceptionModule.addSerializer(cloud.optix.server.exceptions.ValidationException.class, customValidationExceptionSerializer());
objectMapper.registerModule(constraintExceptionModule);
this.objectMapperBuilder.configure(objectMapper);
} catch (Exception e) {
log.error("", e);
}
}
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
/**
* Adding converter to donwload files in{#link org.springframework.web.bind.annotation.RestController}
*
* #param messageConverters
*/
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
// super.configureHttpMessageConverters(messageConverters);
messageConverters.add(new ResourceHttpMessageConverter());
}
}
WebMvcConfiguration:
#Configuration
// Enable entity links for Spring HATEOAS
#EnableHypermediaSupport(type = {HypermediaType.HAL})
public class WebMvcConfiguration implements WebMvcConfigurer {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private TenantRestClient tenantRestClient;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Autowired
public WebMvcConfiguration() {
}
#Bean
public LocaleResolver localeResolver() {
return new SmartLocaleResolver();
}
public class SmartLocaleResolver extends CookieLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
return super.determineDefaultLocale(request);
}
return request.getLocale();
}
}
/**
* Custom exception in WEB MVC
*
* #return
*/
#Bean
public CustomErrorAttributes myCustomErrorAttributes() {
return new CustomErrorAttributes();
}
/**
* Global CORS security configuration
*
* #param registry
*/
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins).allowedHeaders(corsAllowedHeaders)
.allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
Try commenting out this line in your configuration:
this.objectMapperBuilder.configure(objectMapper);
RepositoryRestConfigurer configures the objectMapper for itself quite well I assume.
If you need it for automatically adding more modules from your classpath, then add/configure those modules manually.
You will get same error when you use RepresentationModelAssembler<Object, PersistentEntityResource> resourceAssembler as parameter to your controller method. When you use PersistentEntityResourceAssembler resourceAssembler, spring will create right instance for you.
I suggest you to try to examine class hierarchy of PagedBeanResourceAssembler class and find some more specific class/implementation.
Related
I am trying to implement Lazy loading for our Fund entity that has one-to-many relationship with FundAlternateId entity by using FetchType.Lazy
When I try to access the fund endpoint, I get the following error:
Caused by: org.hibernate.LazyInitializationException: failed to lazily
initialize a collection of role:
com.example.model.Fund.fundAlternateIds, could not initialize proxy -
no Session
JsonMappingException: failed to lazily initialize a collection of
role: com.example.model.model.Fund.fundAlternateIds, could not
initialize proxy - no Session (through reference chain:
java.util.Collections$UnmodifiableRandomAccessList[0]->
com.example.model.model.Fund["fundAlternateIds"]) ; at
com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:397)
;
Here are my project files:
Fund Entity
#Entity(name="fund")
#Table(name="mv_fund_info")
#Getter
#Setter
public class Fund implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "fund_port_id")
private String fundPortId;
#Column(name = "fund_full_name")
private String fundFullName;
#Column(name = "fund_short_name")
private String fundShortName;
#OneToMany(
mappedBy = "associatedFund",
fetch = FetchType.LAZY
)
#JsonManagedReference
private List<FundAlternateId> fundAlternateIds;
}
FundAlternateId Class
#Entity(name="fundAlternateId")
#Table(name="mv_fund_alternate_id")
#Getter
#Setter
public class FundAlternateId implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonIgnore
#Column(name="alternate_id_ins_id")
private Long alternateIdInsId;
#Column(name="alternate_id_value")
#Text
private String alternateIdValue;
#Column(name="alternate_id_type")
#Text
private String alternateIdType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="fund_port_id")
#JsonBackReference
private Fund associatedFund;
}
Rest Controller
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getPublicFundAttributes() {
List<Fund> publicFunds = fundService.getAllPublicFunds(offset,limit, sortBy);
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(publicFunds).build();
}
FundService
#Service
#Transactional("myTransactionManager")
public class FundServiceImpl implements FundService {
#Autowired
FundDao fundDao;
#Override
public List<Fund> getAllPublicFunds(Integer pageNo, Integer pageSize, String sortBy) {
List<Fund> fundList = fundDao.getAllPublicFunds(pageNo, pageSize, sortBy);
return fundList;
}
}
FundDao
#Named("fundDao")
public class FundDaoImpl implements FundDao {
#Autowired
#Qualifier("fundRepository")
FundRepository fundRepository;
#Override
public List<Fund> getAllPublicFunds() {
List<Fund> pagedResult = fundRepository.findAll();
return pagedResult;
}
}
FundRepository
#Repository("fundRepository")
public interface FundRepository extends PagingAndSortingRepository<Fund, String> {}
Configuration Class
#Configuration
#EnableJpaRepositories(
basePackages = {"com.example.repository"},
entityManagerFactoryRef = "myEntityManagerFactory",
transactionManagerRef = "myTransactionManager"
)
#EnableTransactionManagement
public class ApplicationDatabaseConfiguration {
#Bean(name = "myEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory(#Qualifier("postGresDataSource") HikariDataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan(new String[] { "com.example.model"});
em.setJpaVendorAdapter(hibernateJpaVendorAdapter());
em.setJpaPropertyMap(buildJpaPropertyMap());
return em;
}
#Bean
public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL9Dialect");
adapter.setShowSql(false);
adapter.setGenerateDdl(false);
return adapter;
}
#Bean(name = "postGresDataSource")
public HikariDataSource getDataSource() {
HikariDataSource dataSource = new HikariDataSource();
try {
dataSource.setDriverClassName("org.postgresql.Driver");
} catch (Exception e) {
logger.error("ApplicationDatabaseConfiguration.getDataSource() has issue to get data source:", e);
}
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUsername(userId);
dataSource.setPassword(password);
dataSource.setAutoCommit(true);
dataSource.setMaximumPoolSize(25);
dataSource.setMaxLifetime(300000);
dataSource.setIdleTimeout(30000);
return dataSource;
}
#Bean(name = "sleeveTransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(getEntityManagerFactory(getDataSource()).getObject());
tm.setDataSource(getDataSource());
return tm;
}
}
As suggested by #code_mechanic in comments, there are two ways to solve this problem:
Initialize all the lazy references in Transaction (your service layer)
Set all the lazy references to null in Controller before returning the API response.
I have developed two utility methods, which you can use to dynamically check whether the lazy object was initialized or not. You can use these methods in controller layer:
/**
* Was collection initialized.
*
* #param c the c
* #return true, if successful
*/
public static boolean wasCollectionInitialized(Object c) {
if (!(c instanceof PersistentCollection)) {
return true;
}
PersistentCollection pc = (PersistentCollection) c;
return pc.wasInitialized();
}
/**
* Was object initialized.
*
* #param c the c
* #return true, if successful
*/
public static boolean wasObjectInitialized(Object c) {
if (!(c instanceof HibernateProxy)) {
return true;
}
HibernateProxy pc = (HibernateProxy) c;
return !pc.getHibernateLazyInitializer().isUninitialized();
}
Tried to create a configuration class which depends on another bean class using #DependsOn annotation and later found that #DependsOn only work along with #Bean and #Component and not with #Configuration.
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final String URL_MAPPINGS = "/*";
private static final Class<?>[] classes = new Class[]{Application.class, Config1.class};
public static void main(String[] args) {
SpringApplication.run(classes, args);
}
...
}
#Configuration
public class MongoConfig {
#Value("${db.name}")
private String dbName;
#Value("${db.url}")
private String url;
#Override
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb:"+url);
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), dbName);
}
}
#Import({ MongoConfig.class, Config2.class})
#Configuration
public class Config1 {
public BeanA beanA(MongoTemplate mongoTemplate, Environment environment) {
return new BeanA(mongoTemplate);
}
}
#Configuration
#ComponentScan("com.test")
//this should be processed after BeanA is created, similar to #DependsOn
public class Config2 {
...
}
public class BeanA {
private MongoTemplate mongoTemplate;
public BeanA(MongoTemplate mongoTemplate, Environment environment) {
this.mongoTemplate = mongoTemplate;
this.applicationPropertySource = new ApplicationPropertySource(SOURCE_NAME);
MutablePropertySources mutablePropertySources = ((StandardEnvironment) environment).getPropertySources();
mutablePropertySources.addFirst(applicationPropertySource);
getExistingProperties().forEach((key, val) -> applicationPropertySource.set(key, val));
}
private Map<String, String> getExistingProperties() {
//assume values are obtained from DB
}
public static class ApplicationPropertySource extends PropertiesPropertySource {
public ApplicationPropertySource(String name) {
super(name, new HashMap());
}
public Object set(String key, String value) {
return getSource().put(key, value);
}
}
I like to achieve this to avoid adding #DependsOn annotation on all the beans in the project, is this even possible?
Spring version is v1.5.12.RELEASE
This question already has answers here:
How to autowire #ConfigurationProperties into #Configuration?
(2 answers)
Closed 3 years ago.
Java 8 and Spring Boot 1.5.8 here. I have the following application.properties file:
logging:
config: 'logback.groovy'
myapp:
hystrixTimeoutMillis: 500
jwt:
expiry: 86400000
secret: 12345
machineId: 12345
spring:
cache:
type: none
Which maps to the following #ConfigurationProperties POJO:
#ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
private Jwt jwt;
private Long hystrixTimeoutMillis;
private String machineId;
public Jwt getJwt() {
return jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Long getHystrixTimeoutMillis() {
return hystrixTimeoutMillis;
}
public void setHystrixTimeoutMillis(Long hystrixTimeoutMillis) {
this.hystrixTimeoutMillis = hystrixTimeoutMillis;
}
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public static class Jwt {
private Long expiry;
private String secret;
public Long getExpiry() {
return expiry;
}
public void setExpiry(Long expiry) {
this.expiry = expiry;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
}
And I have the following #Configuration (injector) class:
#Configuration
public class MyAppInjector implements ApplicationContextAware {
private Logger log = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
#Autowired
private MyAppConfig myAppConfig;
#Bean
public AuthService authService(MyAppConfig myAppConfig) {
return new JwtAuthService(myAppConfig);
}
}
And the following JwtAuthService class:
public class JwtAuthService implements AuthService {
private static final String BEARER_TOKEN_NAME = "Bearer";
private Logger log = LoggerFactory.getLogger(this.getClass());
private MyAppConfig myAppConfig;
#Autowired
public JwtAuthService(MyAppConfig myAppConfig) {
this.myAppConfig = myAppConfig;
}
#Override
public boolean isValidAuthToken(String authToken) {
return true;
}
}
At startup I get the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field myAppConfig in com.example.myapp.spring.MyAppInjector required a bean of type 'com.example.myapp.spring.MyAppConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.myapp.spring.MyAppConfig' in your configuration.
Why am I getting this error? Where am I injecting/configuring things incorrectly?
You are not declaring MyAppConfig as a bean anywhere in your example, #ConfigurationProperties doesn't make annotated class a bean. You can do it as part of MyAppInjector configuration:
#Configuration
public class MyAppInjector {
#Bean
public AuthService authService() {
return new JwtAuthService(myAppConfig());
}
#Bean
public MyAppConfig myAppConfig() {
return new MyAppConfig();
}
}
Class with #ConfigurationProperties should also be bean. You need to annotate it as #Component or manually register in #Configuration class with #Bean annotation (instead of trying to autowire it there)
what does this error message mean?
2016-01-23 19:07:24,914 WARN ta.neo4j.mapping.Neo4jPersistentProperty: 73 - Owning ClassInfo is null for field: private java.lang.Long com.xenoterracide.rpf.AbstractPersistable.id and propertyDescriptor: org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=id]
here's this class
public abstract class AbstractPersistable implements Identified<Long> {
private Long id;
#Override
public Long getId() {
return this.id;
}
}
I restructured my packages, and the component scan for Neo4j as defined in my config, was no longer correct. So if you get this error make sure that the class is within the scan path of the neo4j session.
#Configuration
#Profile( Strings.Profiles.EMBEDDED )
class EmbeddedConfig extends Neo4jConfiguration {
#Bean
#Override
#Scope( value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )
public Session getSession() throws Exception {
return super.getSession();
}
#Bean
#Override
public Neo4jServer neo4jServer() {
return new InProcessServer();
}
#Bean
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory( Strings.PackagePaths.getModelPackages() );
}
}
There are are many questions of same type, but none works for me.
I have Spring MVC hibernate application.
Here are my two model classes
Config.java
public class Config implements java.io.Serializable {
private Integer configId;
private String configName;
private Set<ConfigFields> ConfigFieldses = new HashSet<ConfigFields>(0);
//getters and setters
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="configuration")
public Set<ConfigFields> getConfigFieldses() {
return this.ConfigFieldses;
}
public void setConfigFieldses(Set<ConfigFields> ConfigFieldses) {
this.ConfigFieldses = ConfigFieldses;
}
}
ConfigFields.java
public class ConfigFields implements java.io.Serializable {
private Integer configFieldId;
private Confign config;
private String configFieldName;
//getteres and setters
#XmlTransient
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ConfigId")
public Config getConfig() {
return this.config;
}
public void setConfig(Config configu) {
this.config = config;
}
}
Here is GenericHibernateDao.java
#Repository
#Transactional
public class GenericHibernateDao<T extends Serializable>
implements GenericDao<T>{
#Resource
protected SessionFactory sessionFactory;
#Override
public void insert(T transientInstance) {
sessionFactory.getCurrentSession().persist(transientInstance);
}
#Override
public void update(T instance) {
sessionFactory.getCurrentSession().saveOrUpdate(instance);
}
#Override
public void delete(T persistentInstance) {
sessionFactory.getCurrentSession().delete(persistentInstance);
}
#SuppressWarnings("unchecked")
#Override
public T merge(Serializable detachedInstance) {
return (T) sessionFactory.getCurrentSession().merge(detachedInstance);
}
#SuppressWarnings("unchecked")
#Override
public T findById(Class<?> clazz, Serializable id) {
T t= (T) sessionFactory.openSession().get(clazz, id);
return t;
}
#SuppressWarnings("unchecked")
public List<T> findByNamedQuery(Class<T> clazz, String queryName, Map<String, Object> queryParams) {
Query namedQuery = sessionFactory.getCurrentSession().getNamedQuery(queryName);
for (String s : queryParams.keySet()) {
namedQuery.setParameter(s, queryParams.get(s));
}
return namedQuery.list();
}
}
In my controller I have this method
#RequestMapping(value = "/deleteConfig/{configId}", method = RequestMethod.POST)
#ResponseBody
#Transactional
public String deleteConfiguration(#PathVariable Integer configId, HttpServletResponse response) throws IOException {
try {
Config config=configService.findById(configId);
logger.info("Deleting configuration...");
configService.delete(config);
} catch(Exception e) {
logger.debug(e.getMessage());
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
return "success";
}
My test case
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration("classpath:webapptest")
#ContextConfiguration(locations = {"classpath:test-applicationcontext.xml"})
public class ConfigurationsControllerTest {
private MockMvc springMvc;
#Autowired
WebApplicationContext wContext;
#Before
public void init() throws Exception {
springMvc = MockMvcBuilders.webAppContextSetup(wContext).build();
}
#Test
public void deleteConfiguration() throws Exception {
ResultActions resultActions=springMvc.perform(MockMvcRequestBuilders.post("/deleteConfig/117").accept(MediaType.APPLICATION_JSON));
resultActions.andDo(MockMvcResultHandlers.print());
resultActions.andExpect(MockMvcResultMatchers.status().isOk());
}
}
When I run the testcase in console, logger showing
Illegal attempt to associate a collection with two open sessions
And JUnit test case stacktrace is
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.transaction.UnexpectedRollbackException: JTA transaction unexpectedly rolled back (maybe due to a timeout); nested exception is bitronix.tm.internal.BitronixRollbackException: transaction was marked as rollback only and has been rolled back
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:932)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:801)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:66)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:168)
In Config class, I have Set which is set to CASCADE ALL. SO I am able to insert set of configfields while inserting config too. But now I want to delete by passing config object. So it should delete 1 row from config table and few rows from configfields table based on configId.
What is wrong here? And how to solve without affecting application(I mean insert)