I am having some strange issues with Hibenate lazy loading. I have 2 entities ProcessEntity and SectionEntity. A ProcessEntity can have many SectionEntity's and the SectionEntity should know which ProcessEntity it belongs to (#OneToMany). The problem I am having is when I load a ProcessEntity with hibernateTemplate.get(id) and then call my custom function fetchLazyCollections(entity, ...) which loops the entities methods until it finds a PersistentCollection and then forces a lazy load on that method.
When the ProcessEntity has its Set lazy loaded it recursively loads ALL the data in the SectionEntity meaning it loads the ProcessEntity which loads the SectionEntitys which load the ProcessEntity and so on! This causes a stack overflow when I try to serialize the data and must be terrible for performance. This doesn't happen when I make an HQL query etc.
Here is my setup for the Entities:
ProcessEntity:
#javax.persistence.Entity(name = "processes")
public class ProcessEntity extends Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id = Entity.UNSAVED_ID;
...
#OneToMany(fetch=FetchType.LAZY, mappedBy="process")
private Set<SectionEntity> sections = new HashSet<SectionEntity>();
...
public Set<SectionEntity> getSections() {
return sections;
}
public void setSections(Set<SectionEntity> sections) {
this.sections = sections;
}
...
}
SectionEntity:
#javax.persistence.Entity(name = "sections")
public class SectionEntity extends Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id = Entity.UNSAVED_ID;
...
#ManyToOne(fetch = FetchType.LAZY, targetEntity = ProcessEntity.class)
#JoinColumn(name="process", referencedColumnName="id")
private ProcessEntity process;
#Override
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
...
public ProcessEntity getProcess() {
return process;
}
public void setProcess(ProcessEntity process) {
this.process = process;
}
...
}
fetchLazyCollections:
public <E extends Entity> E fetchLazyCollections(E entity, String... specifiedCollections) {
if(getCurrentSession() == null) {
throw new SessionException("No session found for fetching collections.");
}
// Fetch the collections using reflection
Class<? extends Entity> clazz = entity.getClass();
for(Method method : clazz.getMethods()) {
Class<?> returnType = method.getReturnType();
if(ReflectUtils.isClassCollection(returnType)) {
// Check if the collection type is specified via the getter
List<String> specified = Arrays.asList(specifiedCollections);
if(!specified.isEmpty()) {
if(!specified.contains(method.getName())) {
continue;
}
}
try {
// Check that the collection is persistent
Collection collection = (Collection) method.invoke(entity);
if(collection instanceof PersistentCollection) {
collection.size(); // invokes lazy loading
}
}
catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
}
}
return entity;
}
I am using Spring on my back-end making use of #Transactional. Here is my Hibernate spring module (#Configuration):
#Configuration
#Import({
MappingModule.class
})
#ImportResource("classpath:nz/co/doltech/ims/properties.xml")
#EnableTransactionManagement
public class HibernateModule {
private static int statisticId = 0;
private #Value("#{app['root.path']}") String projectPath;
private #Value("#{app['database.jndiname']}") String databaseJndiName;
private #Value("#{app['hibernate.dialect']}") String hibernateDialect;
private #Value("#{app['hibernate.hbm2ddl']}") String hibernateHbm2dll;
private #Value("#{app['hibernate.show_sql']}") String hibernateShowSql;
private #Value("#{app['hibernate.format_sql']}") String hibernateFormatSql;
private #Value("#{app['hibernate.generate_statistics']}") String hibarnateStatistics;
private #Value("#{app['hibernate.cache.provider_class']}") String hibarnateCacheProviderClass;
private #Value("#{app['hibernate.cache.use_query_cache']}") String hibarnateQueryCache;
private #Value("#{app['hibernate.cache.use_second_level_cache']}") String hibarnateSecondLevelCache;
private #Value("#{app['hibernate.cache.use_structured_entries']}") String hibernateStructuredEntries;
private #Value("#{app['net.sf.ehcache.configurationResourceName']}") String hibernateEhcacheResource;
private #Value("#{app['flyway.enabled']}") String flywayEnabled;
private #Value("#{app['flyway.basePath']}") String flywayBasePath;
#Bean(name="dataSource")
public JndiObjectFactoryBean getDriverManagerDataSource() {
JndiObjectFactoryBean dataSource = new JndiObjectFactoryBean();
dataSource.setJndiName(databaseJndiName);
dataSource.setCache(true);
return dataSource;
}
#Bean(name="sessionFactory")
#DependsOn({"dataSource", "flyway"})
public AnnotationSessionFactoryBean getAnnotationSessionFactoryBean() {
AnnotationSessionFactoryBean sessionFactory = new AnnotationSessionFactoryBean();
sessionFactory.setDataSource((DataSource) getDriverManagerDataSource().getObject());
sessionFactory.setPackagesToScan(new String[] {
projectPath + ".server.entities",
projectPath + ".server.entities.joins"
});
Properties props = new Properties();
props.setProperty("hibernate.dialect", hibernateDialect);
props.setProperty("hibernate.show_sql", hibernateShowSql);
props.setProperty("hibernate.hbm2ddl.auto", hibernateHbm2dll);
props.setProperty("hibernate.format_sql", hibernateFormatSql);
props.setProperty("hibernate.generate_statistics", hibarnateStatistics);
props.setProperty("hibernate.cache.provider_class", hibarnateCacheProviderClass);
props.setProperty("hibernate.cache.use_query_cache", hibarnateQueryCache);
props.setProperty("hibernate.hibernate.cache.provider_configuration_file_resource_path", hibernateEhcacheResource);
props.setProperty("hibernate.use_second_level_cache", hibarnateSecondLevelCache);
props.setProperty("hibernate.cache.use_structured_entries", hibernateStructuredEntries);
props.setProperty("javax.persistence.validation.mode", "none");
// caching resource
//props.setProperty("net.sf.ehcache.configurationResourceName", hibernateEhcacheResource);
//props.setProperty("hibernate.transaction.manager_lookup_class", "nz.co.doltech.ims.server.persistence.TransactionManagerLookup");
//props.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");
sessionFactory.setHibernateProperties(props);
return sessionFactory;
}
#Bean(name="transactionManager")
#DependsOn("sessionFactory")
public HibernateTransactionManager getHibernateTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(getAnnotationSessionFactoryBean().getObject());
return transactionManager;
}
#Bean(name="hibernateTemplate")
#DependsOn("sessionFactory")
public HibernateTemplate getHibernateTemplate() {
HibernateTemplate hibernateTemplate = new HibernateTemplate();
hibernateTemplate.setSessionFactory(getAnnotationSessionFactoryBean().getObject());
return hibernateTemplate;
}
#Bean(name="jmxExporter")
#DependsOn("hibernateStatisticsBean")
public MBeanExporter getJmxExporter() {
MBeanExporter exporter = new MBeanExporter();
Map<String, Object> map = new HashMap<>();
Properties props = AppProperties.getProperties();
String name = props.getProperty(AppProperties.CLIENT_MODULE_NAME);
String type = props.getProperty(AppProperties.CLIENT_RELEASE_STAGE);
map.put("Hibernate:"+name+"[" + ++statisticId + "]-"+type+"=Statistics",
getHibernateStatisticsBean());
exporter.setBeans(map);
return exporter;
}
#Bean(name="hibernateStatisticsBean")
public StatisticsService getHibernateStatisticsBean() {
StatisticsService statsBean = new StatisticsService();
statsBean.setStatisticsEnabled(true);
statsBean.setSessionFactory(getAnnotationSessionFactoryBean().getObject());
return statsBean;
}
#Bean(name="incidentDao")
#DependsOn("hibernateDao")
public IncidentHibernateDao getIncidentDao() {
IncidentHibernateDao incidentDao = new IncidentHibernateDao();
//incidentDao.registerBroadcasterId(AtmosphereConst.UPDATE_ID_KEY);
return incidentDao;
}
#Bean(name="transactionTemplate")
#DependsOn({"transactionManager"})
#Scope("prototype")
public TransactionTemplate getTransactionTemplate() {
return new TransactionTemplate(getHibernateTransactionManager());
}
}
This is the Hibernate output:
hibernateDao.get(...) called:
Hibernate: select processent0_.id as id27_0_, processent0_.description as descript2_27_0_, processent0_.name as name27_0_, processent0_.nametoken as nametoken27_0_, processent0_.order_value as order5_27_0_, processent0_.removed as removed27_0_ from processes processent0_ where processent0_.id=?
hibernateDao.fetchLazyCollections(...) called:
Hibernate: select incidentjo0_.process_id as process3_27_1_, incidentjo0_.incident_id as incident2_1_, incidentjo0_.process_id as process3_1_, incidentjo0_.incident_id as incident2_21_0_, incidentjo0_.process_id as process3_21_0_, incidentjo0_.completed as completed21_0_ from incident_process incidentjo0_ where incidentjo0_.process_id=?
Hibernate: select sections0_.process as process27_1_, sections0_.id as id1_, sections0_.id as id29_0_, sections0_.description as descript2_29_0_, sections0_.name as name29_0_, sections0_.order_value as order4_29_0_, sections0_.process as process29_0_, sections0_.removed as removed29_0_ from sections sections0_ where sections0_.process=?
Nothing else is called from that point.
Does anyone have any idea what is going on here? I am out of ideas.
Appreciate any help I can get!
Cheers,
Ben
Related
I'm trying to do a simple get query on springboot using mongodb as database engine
I have tried with several stuff(sending the data as ObjectId and even changing the repository)
public ResponseEntity<Track> get(String trackId) {
Track find = mongoTemplate.findById(new ObjectId(trackId), Track.class);
Optional<Track> track = tracksRepository.findById(trackId);
if (track.isPresent()) {
return new ResponseEntity<>(track.get(), HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
with mongo config
#Configuration
#EnableMongoRepositories(basePackages = "data.store.repositories")
public class MongoConfig extends AbstractMongoClientConfiguration {
private final Logger LOGGER = Logger.getLogger(this.getClass().getSimpleName());
#Primary
#Bean
#Override
public MongoClient mongoClient() {
return MongoClients.create(MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.hosts(Arrays.asList(new ServerAddress(host, port))))
.build());
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(username, database, password.toCharArray());
}
#Bean
public MongoTemplate mongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), getDatabaseName());
mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
return mongoTemplate;
}
protected String getDatabaseName() {
return database;
}
#Override
public boolean autoIndexCreation() {
return false;
}
}
EDIT: Adding class for context
#Document("track")
public class Track {
#Id
#Field(ATTR_ID)
#JsonProperty(ATTR_ID)
public String id;
public static final String ATTR_ID = "id";
}
and getting always null, with existing keys on my database. could you help me find the issue?
Thanks in advance
I tried this with similar configuration class and found the following worked fine creating/accessing data using MongoTemplate.
The POJO class:
public class Test {
#MongoId(FieldType.OBJECT_ID)
private String id;
private String name;
public Test() {
}
public Test(String s) {
super();
this.name = s;
}
// get, set methods
public String toString( ) {
return id + " - " + name;
}
}
From Spring's CommandLineRunner.run():
// Insert a document into the database
Test t1 = new Test("alpha");
t1 = mt.insert(t1);
System.out.println(t1); // 61e7de9f5aadc2077d9f4a58 - alpha
// Query from the database using the _id
ObjectId id = new ObjectId("61e7de9f5aadc2077d9f4a58");
Test t2 = mt.findById(id, Test.class);
System.out.println(t2);
Note that you need to do this from the class where you are running the code:
#Autowired private MongoTemplate mt;
You can use the #MongoId or #Id annotations in our POJO class to represent MongoDB _id field. The type of the field can be a String or ObjectId. It depends upon how you define.
See this from Spring Data MongoDB documentation on How the _id Field is Handled in the Mapping Layer using:
#MongoId
#Id
Solution is to add to MongoId annotation field type object id
#MongoId(FieldType.OBJECT_ID)
private String id;
I have implemented auditing through Sping Data JPA but now I want to be able to control the update and create timestamps with custom logic. So I want to write a custom auditing entity listener that extends the Auditingentitylistener.
I have the following generic entity class where I register the custom auditing entity listener:
#MappedSuperclass
#Audited
#EntityListeners(CustomAuditingEntityListener.class)
public class AuditableEntity extends BaseEntity {
#CreatedDate
#Column(name = "created", nullable = false)
private Date created;
#CreatedBy
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "created_by_id", foreignKey = #ForeignKey(name = "fk_entity_created_by_id"))
private Account createdBy;
#LastModifiedDate
#Column(name = "last_updated", nullable = false)
private Date lastUpdated;
#LastModifiedBy
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "last_updated_by_id", foreignKey = #ForeignKey(name = "fk_entity_last_updated_by_id"))
private Account lastUpdatedBy;
protected AuditableEntity() {
}
...
}
The CustomAuditingEntityListener class is defined as
#Configurable
public class CustomAuditingEntityListener extends AuditingEntityListener {
public CustomAuditingEntityListener() {
super();
}
#Override
#PrePersist
public void touchForCreate(Object target) {
if (//custom logic) {
super.touchForCreate(target);
}
}
#Override
#PreUpdate
public void touchForUpdate(Object target) {
if (//custom logic) {
super.touchForUpdate(target);
}
}
}
The custom auditing entity listener is called correctly but when the super.touchForCreate(target) or super.touchForUpdate(target) gets called the timestamps are not set because the handler in the AuditingEntityListener class is null.
This is the code of the AuditingEntityListener class:
package org.springframework.data.jpa.domain.support;
#Configurable
public class AuditingEntityListener implements ConfigurableObject {
private ObjectFactory<AuditingHandler> handler;
public AuditingEntityListener() {
JoinPoint var2 = Factory.makeJP(ajc$tjp_1, this, this);
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
if (this != null && this.getClass().isAnnotationPresent(Configurable.class) && AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class))) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$before$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$1$e854fa65(this);
}
if ((this == null || !this.getClass().isAnnotationPresent(Configurable.class) || !AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class))) && this != null && this.getClass().isAnnotationPresent(Configurable.class) && AbstractDependencyInjectionAspect.ajc$if$6f1(var1)) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
if (!AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)this.getClass().getAnnotation(Configurable.class)) && AbstractDependencyInjectionAspect.ajc$if$6f1(var2)) {
AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
}
public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {
Assert.notNull(auditingHandler, "AuditingHandler must not be null!");
this.handler = auditingHandler;
}
#PrePersist
public void touchForCreate(Object target) {
if (this.handler != null) {
((AuditingHandler)this.handler.getObject()).markCreated(target);
}
}
#PreUpdate
public void touchForUpdate(Object target) {
if (this.handler != null) {
((AuditingHandler)this.handler.getObject()).markModified(target);
}
}
static {
ajc$preClinit();
}
}
Can someone explain how I can ensure that the AuditingHandler is not null and is set like it happens for the default AuditingEntityListener class?
With Spring 5.1 there is a rather straightforward way to autowire Spring components in the custom entity listener, so that the AuditingHandler can be used.
1. Declare the customer entity listener as a Spring component. Use constructor injection.
#Component
public class CustomAuditingEntityListener {
private ObjectFactory<AuditingHandler> handler;
public CustomAuditingEntityListener(ObjectFactory<AuditingHandler> auditingHandler) {
this.handler = auditingHandler;
}
#Override
#PrePersist
public void touchForCreate(Object target) {
if (//custom logic) {
AuditingHandler object = handler.getObject();
if (object != null) {
object.markCreated(target);
}
}
}
#Override
#PreUpdate
public void touchForUpdate(Object target) {
if (//custom logic) {
AuditingHandler object = handler.getObject();
if (object != null) {
object.markModified(target);
}
}
}
}
2. Tell Hibernate to use Spring as the bean provider by setting the appropriate configuration property:
#Configuration
public class JpaConfig extends JpaBaseConfiguration {
#Autowired
private ConfigurableListableBeanFactory beanFactory;
protected JpaConfig(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> properties = new HashMap<>();
// This is the important line
properties.put(org.hibernate.cfg.AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
return properties;
}
}
--Edit-- Variation of 2. Alternatively the SpringBeanContainer can be configured this way, with the benefit of not losing the Hibernate properties that Spring Boot populates:
#Configuration
public class JpaConfig {
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
EntityManagerFactoryBuilder builder, ConfigurableListableBeanFactory beanFactory) {
return builder.dataSource(dataSource) //
.packages(BaseEntity.class) //
.persistenceUnit("myunit") //
.properties(Map.of(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory))) //
.build();
}
}
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();
}
I have an ExampleRequest entity that can optionally have one or more ExampleRequestYear. It's currently configured this way (unrelated fields and gettters/setters omitted for brevity, please let me know if you need anything else):
#Entity
#Table(name = "EXAMPLE_REQUEST")
#SequenceGenerator(name = "EXAMPLE_REQUEST_ID_SEQ", sequenceName = "EXAMPLE_REQUEST_ID_SEQ", allocationSize = 1)
#Cacheable(false)
public class ExampleRequest implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EXAMPLE_REQUEST_ID_SEQ")
#Column(name="EXAMPLE_REQUEST_ID", nullable = false)
private Long exampleRequestId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exampleRequest")
private List<ExampleRequestYear> exampleRequestYearList;
public ExampleRequest() {
}
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
}
return this.exampleRequestYearList;
}
public void setExampleRequestYearList(List<ExampleRequestYear> exampleRequestYearList) {
this.exampleRequestYearList = exampleRequestYearList;
}
public ExampleRequestYear addExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().add(exampleRequestYear);
exampleRequestYear.setExampleRequest(this);
return exampleRequestYear;
}
public ExampleRequestYear removeExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().remove(exampleRequestYear);
exampleRequestYear.setExampleRequest(null);
return exampleRequestYear;
}
}
#Entity
#Table(name = "EXAMPLE_REQUEST_YEAR")
#IdClass(ExampleRequestYearPK.class)
public class ExampleRequestYear implements Serializable {
#Id
#Column(nullable = false)
private Integer year;
#Id
#ManyToOne
#JoinColumn(name = "EXAMPLE_REQUEST_ID", referencedColumnName = "EXAMPLE_REQUEST_ID")
private ExampleRequest exampleRequest;
public ExampleRequestYear() {
}
public void setExampleRequest(ExampleRequest exampleRequest) {
this.exampleRequest = exampleRequest;
}
public ExampleRequest getExampleRequest() {
return exampleRequest;
}
}
Part of the code was auto-generated by the IDE and I'm still wrapping my head around JPA so there're probably design mistakes all around.
My app works (apparently) when I create a new ExampleRequest:
ExampleRequest exampleRequest = new ExampleRequest();
ExampleRequestYear exampleRequestYear = new ExampleRequestYear(2020);
request.addExampleRequestYear(exampleRequestYear);
However, I can't figure out how to edit an existing ExampleRequest because I'm unsure on how I'm meant to retrieve the linked entities. According to articles I've read, lazy fetching should be automatic, yet when I try this:
ExampleRequest exampleRequest = employeeRequestsController.getExampleRequestById(123);
System.out.println(exampleRequest.getExampleRequestYearList().size());
... I get a null pointer exception upon .size() because the getter runs but neither initialises an empty list, nor retrieves items from DB:
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
// Field is null and conditional is entered
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
// After initialisation, field is still null!
}
return this.exampleRequestYearList;
}
Also, switch to FetchType.EAGER solves this particular problem entirely. What am I missing?
Further details regarding app design. The Resource classes that handle HTTP requests interact with a set of Controller classes like this:
#Stateless(name = "ISomeActionController", mappedName = "ISomeActionController")
public class SomeActionController implements ISomeActionController {
#EJB
private IFooDAO fooDao;
#EJB
private IBarDAO barDao;
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId) {
return fooDao.getEntityById(exampleRequestId);
}
}
It's in the DAO classes where EntityManager is injected an used:
#Local
public interface IGenericDAO<T> {
public T persistEntity(T o);
public T persistEntityCommit(T o);
public void removeEntity(T o);
public void removeEntity(long id);
public T mergeEntity(T o);
public List<T> getEntitiesFindAll();
public List<T> getEntitiesFindAllActive();
public T getEntityById(Object id);
}
public interface IFooDAO extends IGenericDAO<ExampleRequest> {
public void flushDAO();
public ExampleRequest getExampleRequestById(Long exampleRequestId);
}
#Stateless(name = "IFooDAO", mappedName = "IFooDAO")
public class FooDAO extends GenericDAO<ExampleRequest> implements IFooDAO {
public FooDAO() {
super(ExampleRequest.class);
}
#Override
public void flushDAO(){
em.flush();
}
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId){
String sql = "...";
Query query = em.createNativeQuery(sql, ExampleRequest.class);
//...
}
}
When i tries to open
http://localhost:8080/tailor/orders
which should return all the orders in database.but it's generating error
{"message":"There was an error processing your request. It has been logged (ID fe49a13e76c59894)."}
I'm unable to trace the problem what's causing this.
I'm using dropwizard for restful web service and hibernate from dropwizard for sqlight database.
Class:
Resources class:
#Path("/tailor")
#Produces(MediaType.APPLICATION_JSON)
public class TailorResource {
OrderDAO orderdao;
public TailorResource(OrderDAO odao) {
this.orderdao = odao;
}
#GET
#Path("/orders")
#Produces(MediaType.APPLICATION_JSON)
public List<OrderModel> getAllOrders() {
return orderdao.findAll();
}
#GET
#Path("/orders/{id}")
#Produces(MediaType.APPLICATION_JSON)
public OrderModel getOrderById(#PathParam("id") int id) {
return orderdao.findById(id);
}
}
OrderDAO class:
public class OrderDAO extends AbstractDAO<OrderModel>{
public OrderDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
public OrderModel findById(int id) {
return get(id);
}
public OrderModel create(OrderModel o) {
return persist(o);
}
public List<OrderModel> findAll() {
return list(namedQuery("com.yammer.dropwizard.tailor.model.OrderModel.findAll"));
}}
Order Class:
#NamedQueries({
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.OrderModel.findAll",
query = "SELECT o FROM OrderModel o"
),
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.OrderModel.findById",
query = "SELECT o FROM OrderModel o WHERE o.ID = :ID"
)
})
#Entity
#Table(name = "Order")
public class OrderModel {
#Id
#GeneratedValue
#Column(name = "o_id")
int ID;
#Column(name = "o_shirt_quantity")
int shirtQuantity;
#Column(name = "o_longshirt_quantity")
int longshirtQuantity;
#Column(name = "o_trouser_quantity")
int trouserQuantity;
#Column(name = "o_coat_quantity")
int coatQuantity;
#Column(name = "o_deliverydate")
Date deliveryDate;
#Column(name = "o_orderdate")
Date orderDate;
#Column(name = "o_shirt_price")
Double shirtPrice;
#Column(name = "o_longshirt_price")
Double longshirtPrice;
#Column(name = "o_trouser_price")
Double trouserPrice;
#Column(name = "o_coat_price")
Double coatPrice;
#Column(name = "o_totalamount")
Double totalAmount;
#Column(name = "o_discount")
Double discount;
#Column(name = "o_advancedpayment")
Double advancedPayment;
#Column(name = "o_remainingpayment")
Double remainingPayment;
#Column(name = "o_orderstatus")
int orderStatus;
}
Database configuration class:
public class databaseConfiguration extends Configuration {
#Valid
#NotNull
#JsonProperty
DataSourceFactory dbconfigurations = new DataSourceFactory();
public DataSourceFactory getDataSourceFactory() {
//return dbconfigurations;
Map<String,String> s=new HashMap<String,String>();
s.put("hibernate.dialect","Hibernate.SQLightDialect.SQLiteDialect");
dbconfigurations.setProperties(s);
return dbconfigurations;
}
}
Main service Class:
public class TailorApplication extends Application<databaseConfiguration> {
public static void main(String[] args) throws Exception {
new TailorApplication().run(args);
}
private final HibernateBundle<databaseConfiguration> hibernate = new HibernateBundle<databaseConfiguration>(CustomerModel.class) {
{
#Override
public DataSourceFactory getDataSourceFactory(databaseConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
#Override
public void initialize(Bootstrap<databaseConfiguration> bootstrap) {
// TODO Auto-generated method stub
bootstrap.addBundle(hibernate);
}
#Override
public void run(databaseConfiguration configuration, Environment environment)
throws Exception {
final OrderDAO odao = new OrderDAO(hibernate.getSessionFactory());
environment.jersey().register(new TailorResource(odao));
}
}
YML file:
dbconfigurations:
# the name of your JDBC driver
driverClass: org.sqlite.JDBC
# the username
user:
# the password
password:
url: jdbc:sqlite:TailorDB.db
Help please?
Make sure you filled the application class with the entities:
private final HibernateBundle<AppConfiguration> hibernateBundle = new HibernateBundle<AppConfiguration>(
//
//
//ADD ENTITIES HERE
//
//
Person.class
,Product.class
) {
#Override
public DataSourceFactory getDataSourceFactory(
AppConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
On the face of it, looks like you have missed the #UnitOfWork annotation on getAllOrders.
Having said that, the error you have shared is the external messaging that DW provides by default. Instead you should look at your web service logs for the exact error and precise stacktrace. If you run your service in terminal with java -jar path/to/shaded.jar server my.yml you should see elaborate error on the console. Please share that so that community can help better.