I was faced to a :
failed to lazily initialize a collection of role: ,no session or session was closed
When trying to access (from the controller, or a junit) the collection of "DataDictionaryEntry" that are in a "DataDictionary".
DataDictionary
#Entity
#Table( name = "IDS_RAVE_DATA_DICTIONARY",
uniqueConstraints={#UniqueConstraint(columnNames={"name"})
})
public class DataDictionary extends UnversionedObject {
#Column
private String name;
#OneToMany(mappedBy="dataDictionary",fetch=FetchType.EAGER)
private Collection<DataDictionaryEntry> dataDictionaryNames;
/* constructor */
public DataDictionary() {
super();
}
/* getters & setters */
}
DataDictionaryEntry
#Entity
#Table( name = "IDS_RAVE_DATA_DICTIONARY_ENTRY",
uniqueConstraints={#UniqueConstraint(columnNames={"dataDictionary","codedData"})
})
public class DataDictionaryEntry extends UnversionedObject {
#ManyToOne
#JoinColumn(name="dataDictionary")
private DataDictionary dataDictionary;
#Column
private String codedData;
#Column
private Integer ordinal;
#Column
private String userDataString;
#Column
private Boolean specify;
/* constructor */
public DataDictionaryEntry() {
super();
}
/* getters & setters */
}
I do have an abstract service object and another service extending it :
Generic service
#Transactional
public abstract class RaveGeneralServiceImpl<T> implements RaveGeneralService<T> {
private JpaRepository<T, Long> repo;
/**
* Init the general rave services with your specific repo
* #param repo
*/
protected void init(JpaRepository<T, Long> repo){
this.repo = repo;
}
#Override
public List<T> findAll(){
return repo.findAll();
}
#Override
public T save(T obj){
return repo.save(obj);
}
#Override
public void flush(){
repo.flush();
}
}
DataDictionaryServiceImpl
#Service
public class DataDictionaryServiceImpl extends RaveGeneralServiceImpl<DataDictionary> implements DataDictionaryService {
#Resource
private DataDictionaryRepository dataDictionaryRepository;
#PostConstruct
public void init() {
super.init(dataDictionaryRepository);
}
}
I could find replies on how to solve it. The first solution often seen is to change to LAZY to a EAGER. When I printed the generated query when accessing a FINDALL() method it shows the following :
Hibernate:
/* select
generatedAlias0
from
DataDictionary as generatedAlias0 */ select
datadictio0_.ID as ID81_,
datadictio0_.createdByUser as createdB2_81_,
datadictio0_.createdTime as createdT3_81_,
datadictio0_.lastUpdateTime as lastUpda4_81_,
datadictio0_.lastUpdateUser as lastUpda5_81_,
datadictio0_.VERSION as VERSION81_,
datadictio0_.name as name81_
from
IDS_RAVE_DATA_DICTIONARY datadictio0_
Hibernate:
/* load one-to-many com.bdls.ids.model.rave.DataDictionary.dataDictionaryNames */ select
datadictio0_.dataDictionary as dataDic11_81_1_,
datadictio0_.ID as ID1_,
datadictio0_.ID as ID82_0_,
datadictio0_.createdByUser as createdB2_82_0_,
datadictio0_.createdTime as createdT3_82_0_,
datadictio0_.lastUpdateTime as lastUpda4_82_0_,
datadictio0_.lastUpdateUser as lastUpda5_82_0_,
datadictio0_.VERSION as VERSION82_0_,
datadictio0_.codedData as codedData82_0_,
datadictio0_.dataDictionary as dataDic11_82_0_,
datadictio0_.ordinal as ordinal82_0_,
datadictio0_.specify as specify82_0_,
datadictio0_.userDataString as userDat10_82_0_
from
IDS_RAVE_DATA_DICTIONARY_ENTRY datadictio0_
where
datadictio0_.dataDictionary=?
The 2nd solution we often see is to make a call to the .size() of the component that is being lazily initialized. So indeed by changing my service to this :
#Service
public class DataDictionaryServiceImpl extends RaveGeneralServiceImpl<DataDictionary> implements DataDictionaryService {
#Resource
private DataDictionaryRepository dataDictionaryRepository;
#PostConstruct
public void init() {
super.init(dataDictionaryRepository);
}
#Override
public List<DataDictionary> findAll() {
List<DataDictionary> results = super.findAll();
for (DataDictionary dd : results) {
dd.getDataDictionaryNames().size();// init lazy
}
return results;
}
}
The lazy exception is also gone ! But the end result is the same query... So the what is the added value of keeping it LAZY if the end-query is the same ? Or did I do it wrong ?
Suppose that for the front-end you would have a data table that displays only basic information (the name for example), it would call the findAll() but still query the complete dependencies of that object ?
While the results with this method are pretty much exactly the same, the value of keeping it lazy is that if you don't need it fetched in other queries, you don't automatically have it eagerly fetched. Making the relationship eager applies to every method of accessing that entity, while calling size on a collection forces it to be fetched for that one occurrence.
There are other ways that might be more efficient, such as using a join fetch qualifier in the JPA query itself, allowing the provider to fetch the relationship using a single select.
You can either use: Hibernate.initialize() to initialize Lazy collections.
Or using spring to avoid LazyException use filer in your web.xml:
<filter>
<filter-name>hibernateFilterChain</filter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
But remember, using lazy fetching is bad idea if you are thinking about good application design and performance.
Related
Environment:
spring-boot v2.0.4 RELEASE
spring-data-aerospike v2.0.1.RELEASE
java - 8
Here are my application code and properties.
// application.properties
aerospike.hosts=xxx:3000
aerospike.namespace=test
// aerospike configuration class
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(AerospikeConfiguration.AerospikeConfigurationProperties.class)
#EnableAerospikeRepositories(basePackageClassses = TestAeroRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
private final AerospikeConfigurationProperties aerospikeConfigurationProperties;
#Override
protected Collection<Host> getHosts() {
return Host.parseServiceHosts(aerospikeConfigurationProperties.getHosts());
}
#Override
protected String nameSpace() {
return aerospikeConfigurationProperties.getNamespace();
}
#Data
#Validate
#ConfigurationProperties("aerospike")
public static class AerospikeConfigurationProperties {
#NotEmpty
String hsots;
#NotEmpty
String namespace;
}
}
# Entity class
#Value
#Document
#Builder(toBuilder = true)
#AllArgsConstructor
public class testEntity() {
#Id
int id;
#Field
String name;
#Field
String timestamp;
}
#Repository
public interface TestAeroRepository extends AerospikeRepository<TestEntity, Integer> {
}
public interface TestAeroService {
void save();
}
#Service
#RequiredArgsConstructor
public class TestAeroServiceImpl implements TestAeroService {
private final TestAeroRepository testAeroRepository;
#Override
public void save(TestEntity entity) {
testAeroRepository.save(entity);
}
}
I checked Aerospike client connection has no problem.
But error is occurred when save() method is executed.
org.springframework.cglib.core.ReflectUtils.defineClass(Ljava/lang/String;[BLjava/lang/ClassLoader;Ljava/security/ProtectionDomain;Ljava/lang/Class;)Ljava/lang/Class;
Have to make sets before execute the application? I didn't make sets.
Any problem with my code?
You’re using an old version of spring-data-aerospike (2.0.1.RELEASE was released on April 2019) is there any chance you can upgrade to the latest version? 2.4.2.RELEASE
You can see how to setup a simple spring data aerospike application here: https://medium.com/aerospike-developer-blog/simple-web-application-using-java-spring-boot-aerospike-database-and-docker-ad13795e0089
Please share the entire project’s code and the entire exception.
I would look into:
The configuration class (The Aerospike Beans creation).
The content of the testEntity class - are you using #Id annotation on the primary key field?
Extending the repository class with specifying the testEntity object (… extends AerospikeRepository<testEntity, Object> {) you can see an example in the link I added.
The set is automatically created and takes the name of your object class, which is testEntity in your case. For example, based on your code, if you do not specify a collection in the #Document annotation a set named "testEntity" will automatically be created. I added the #Document(collection = "testEntitys") annotation and all I did was create two set. Once you insert your first record, run the "SHOW SETS" aql command and it will be there. So that's one way to do it.
I want to have similar functionality as I get with the JPA #PrePersist but in a mongodb database. Reading the spring data mongodb documentation I found the entity callbacks: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#entity-callbacks. They seem to work for what I need so I'm trying to implement some callbacks. I know there are some alternatives for what I'm doing (auditing annotations) but I want to keep with this for the moment.
This is how I register the callback, my entity definition and the repository:
#Configuration
public class BeforeSaveCallbackConfiguration {
#Bean
BeforeSaveCallback<Measurement> beforeSaveMeasurement() {
return (entity, document, collection) -> {
entity.setTimestamp(System.currentTimeMillis());
System.out.println("Before save, timestamp: " + entity.getTimestamp());
return entity;
};
}
}
public interface MeasurementRepository extends MongoRepository<Measurement, String> {
}
#Document
public class Measurement {
private String id;
private long timestamp;
private Float value1;
private Float value2;
// constructor, getters, setters ...
}
I save the entity using measurementRepository.save method of the repository. I actually see the printed line from the callback with the timestamp. However the data saved in the mongodb collection always have timestamp set to 0. Does anyone have any hint?
You implement BeforeConvertCallback interface can work for you:
#Component
public class TestCallBackImpl implements BeforeConvertCallback<Measurement> {
#Override
public Measurement onBeforeConvert(Measurement entity, String collection) {
entity.setTimestamp(System.currentTimeMillis());
return entity;
}
}
I'm trying to create a repository that has a method which doesn't fit the usual JpaRepository with #Query annotations.
I've created a custom repository interface:
public interface CustomVoteRepository {
List<VoteCountResult> countVotesForSession();
}
And the implementation:
#Repository
public class CustomVoteRepositoryImp implements CustomVoteRepository {
private JdbcTemplate jdbcTemplate;
public CustomVoteRepositoryImp(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#Override
public List<VoteCountResult> countVotesForSession() {
return jdbcTemplate.query("SELECT video_id, COUNT(votes.id) FROM votes WHERE session_id=2 GROUP BY video_id",
new CustomRowMapper());
}
}
However, this gives me this error:
No property countVotesForSession found for type Vote!
I don't understand why it's trying to map a property on the Vote class. I understand it does this for the "auto-generated" method names, but this is supposed to be a custom one.
I've come across this article: https://www.mkyong.com/spring-data/spring-data-add-custom-method-to-repository/ which explains what I'm doing, and yet it's trying to map a property of the model for a custom repository.
I'm sure I missed something stupid.
Thanks!
Edit:
Here's the VoteCountResult dto:
#Data
#AllArgsConstructor
public class VoteCountResult {
private String count;
private String title;
private String url;
}
What if you change your custom method name to votesForSessionCount ? I think this way you won't face with method name conflict.
I have a repository called BananaRepositoryImpl that contains a function that return a list of BananaDTO ( the legacy code can't return the mapped entity ( Banana.java ),it's a constraint and I can't change this behavior :( )
public class BananaRepositoryImpl implements BananaRepository{
#Autowired
EntityManager em;
public List<BananaDTO> findAllBananes(){
//logic to get list of bananasDTO object types using Query query = em.createQuery(JPQL_QUERY_HERE);
}
}
Knowing that the BananaDTO object is a DTO for Banana.java class which looks like this :
#Data
#Entity
public class Banana{
private Long id;
private Double price;
private Double weight;
}
What I should do is to implement pagination over the findAllBananes() method so that I can return a Page using spring Data ( or another approach ).
Assuming the attributes of the BananaDTO is a subset of the Banana entities attributes you can use class-based projection support of Spring Data JPA, i.e. you just add a Pageable as a parameter to your method and return a Page<BananaDTO>:
interface BananaRepository extends CrudRepository<Banana, Long> {
Page<BananaDTO> findAllBananes(Pageable page)
}
I'm running in to a small problem with persisting an object. Here's an example of what my entity class looks like.
#Entity
public class Example(){
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private int number;
private String sentence;
/* No arg const, getters and setters omitted */
CrudRepository Interface:
#Repository
public interface ExampleRepository extends CrudRepository<Example, Integer>
{}
Service that implements the Interface:
#Service
public class ExampleService{
#Autowired
public ExampleRepository exampleRepository;
public void save(Example example){
exampleRespository.save(example)
}
}
Inside of CommandLineRunner:
Example example1 = new Example();
example1.sentence("Hello World!");
exampleService.save(example1);
Now the problem that I'm running into is that even though I didn't assign any value to the property number, it is still getting persisted as 0. How do I stop that property from getting assigned a value of 0 and make it null?
Change
private int number;
into
private Integer number;
the above solution is fine but again if you see in insert query for your save function, It insert all 3 column even you assigned only once column example1.sentence("Hello World!");
you can used #DynamicInsert(true) #DynamicUpdate(true) on Entity level,
this will fire query as
insert into example(sentence) values('Hello World!');
so query performance will increase