Hibernate isInitialized outside of session - java

Is there any way to check whether an entity's lazy collection attribute is initialized once we're outside the Transactional session?
For example, my Job entity has a lazy association to a list of Step, which is only sometimes initialized. In my controller, I would like to know if the steps collection has been initialized, although I'm met with a LazyInitializationException instead:
org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [com.overflow.stack.Job.steps] - no session and settings disallow loading outside the Session
at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.throwLazyInitializationException(EnhancementHelper.java:199) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:89) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.loadAttribute(LazyAttributeLoadingInterceptor.java:76) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.fetchAttribute(LazyAttributeLoadingInterceptor.java:72) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.handleRead(LazyAttributeLoadingInterceptor.java:53) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at com.overflow.stack.Job.$$_hibernate_read_steps(Job.java) ~[classes/:?]
at com.overflow.stack.Job.getSteps(Job.java:135) ~[classes/:?]
...
#Entity
public class Step {
...
}
#Entity
public class Job {
#OneToMany(mappedBy = "job", fetch = FetchType.LAZY)
private List<Step> steps = new ArrayList<>();
public List<Step> getSteps() {
return steps;
}
...
}
#Controller
public JobController {
public List<Step> getJobSteps(Long id) {
// Transactional service class method which fetches by id (does not initialize steps)
Job job = jobService.getJob(id);
if (Hibernate.isInitialized(job.getSteps())) { // Throws LazyInitializationException
return job.getSteps();
} else {
return List.of();
}
}
}
#Service
#Transactional
public JobService {
#Autowired JobRepository jobRepo;
public Job getJob(Long id) {
Job job = jobRepo.findById(id).get();
Hibernate.unproxy(job);
return job;
}
}
In this case, the exception is thrown when it attempts to execute job.getSteps() in the if statement.
I know that the session is closed by the time I leave the service call (the controller is not Transactional), although I'm wondering if there's any way to check if it's been initialized, or is the getSteps() method completely out of reach at that point?

Hibernate.isInitialized(job.getSteps()) shouldn't throw LazyInitializationException (LIE).
job.getSteps() returns a proxy, to have LIE you need to call a method of a proxy, for example job.getSteps().size().
job.getSteps() can throw LIE, if job is a proxy itself.
Anyway using entities as a response of REST API is a very bad idea. You will have a lot of issues with that.
Better to use JobEntity for an entity and Job for DTO. Just convert from JobEntity to Job on the service level. You can call Hibernate.isInitialized(jobEntity.getSteps()) on the service level and set an empty collection for the steps to the Job.
To unproxy entity
Hibernate.unproxy() returns a different object (not the same proxy). Also keep in mind that "If the proxy is uninitialized, it automatically triggers an initialization".
public Job getJob(Long id) {
Job job = jobRepo.findById(id).get();
return Hibernate.unproxy(job, Job.class);
}

Related

Is it possible to nest transactions with TransactionManager?

I have the following code:
public ResultProcessDTO process() {
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return transactionTemplate.execute(status -> {
...
SomeEntity entity = service.findById(id);
...
otherBean.someMethod();
...
});
}
And, in another bean:
public void someMethod() {
...
service.save(entity);
}
I need someMethod() to be REQUIRES_NEW, to perform the save and commit the transaction regardless of what happens with the rest of the process().
I've already tried #Transactional to leave this up to Spring, but the process() is triggered via KafkaListener, which leads me to have problems with lazy load entities that are fetched along the way.
For example, if I call entity.getChildList() I get a LazyInitializationException. This exception is not thrown if I use TransactionTemplate.
Any suggestions on what I'm doing wrong or how to make it work as I hope?

How to test spring batch job within #Transactional SpringBootTest test case?

I just can't seem to win today...
Is there a way to read from a OneToMany relationship in a Spock SpringBootTest integration test, without annotating the test as #Transactional or adding the unrealistic spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true?
OR, is there a way to launch a Spring-Batch Job from within a #Transactional test case?
Let me elaborate...
I'm trying to get a simple Spring Boot Integration test working for my Spring Batch reporting process, which reads from tangled web of DB2 tables and generates a series of change messages for interested systems. I'm using the Groovy Spock testing framework and an H2 in-memory database filled with a representative slice of my DB2 tables' data.
At the beginning of the test, I'm attempting to use every entity from a given Table to generate entries in a change-tracking table that drives my messaging.
setup:
List allExistingTestPeople = peopleRepository.findAll()
Collections.shuffle(allExistingTestPeople)
allExistingTestPeople?.each { Person person ->
Nickname nicknames = person.nicknames
nicknames?.each { Nickname nickname ->
changeTrackingRepository.save(new Change(personId: person.id, nicknameId: nickname.id, status: NEW))
}
}
Given these as my DB2 domain classes:
#Entity
#Table(name = "T_PERSON")
public class Person {
#Id
#Column(name = "P_ID")
private Integer id;
#Column(name = "P_NME")
private String name;
#OneToMany(targetEntity = Nickname.class, mappedBy = "person")
private List<Nickname> nicknames;
}
#Entity
#Table(name = "T_NICKNAME")
public class Nickname{
#EmbeddedId
private PersonNicknamePK id;
#Column(name = "N_NME")
private String nickname;
#ManyToOne(optional = false, targetEntity = Person.class)
#JoinColumn(name = "P_ID", referencedColumnName="P_ID", insertable = false, updatable = false)
private Person person;
}
#Embeddable
public class PersonNicknamePK implements Serializable {
#Column(name="P_ID")
private int personId;
#Column(name="N_ID")
private short nicknameId;
}
But I'm getting this LazyInitializationException, even though I'm reading from that OneToMany relationship within the context of a test case...
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.my.package.db2.model.Person.nicknames, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:161)
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:350)
I came across the advice online to annotate my test case with the #Transactional annotation, which definitely got me a little further, allowing me to read from this OneToMany relationship. However, when I then attempt to launch the Spring Batch Job I'd like to test from my when clause:
#Transactional
def "Happy path test to validate I can generate a report of changes"() {
setup:
//... See above
when:
service.launchBatchJob()
then:
//... Messages are generated
}
I'm getting the exception that a Spring Batch Job can't be launched from the context of a transaction! Even though I'm using an in-memory Job manager via ResourcelessTransactionManager and MapJobRepositoryFactoryBean, since this is just a short lived scheduled script I'm writing...
java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove #Transactional annotations from client).
at org.springframework.batch.core.repository.support.AbstractJobRepositoryFactoryBean$1.invoke(AbstractJobRepositoryFactoryBean.java:177)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy125.createJobExecution(Unknown Source)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:134)
at com.my.package.service.MyService.launchBatchJob(MyService.java:30)
The only thing that seems to work so far is if I scrap the #Transactional annotation and instead add spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true to my application-test.properties file. BUT, this doesn't seem like a very good idea, because it's not realistic - if I add this, then even if there's a bug in my code due to a lazy-initialization-exception, I'd never see it in the tests.
Sorry for the novel, hoping someone can point me in the right direction :(
EDIT:
Also here's my In-memory Spring-Batch configuration, in which I've tried turning off the transaction validation. Unfortunately, while this gets me a little further, the Spring Batch partioner's autowired EntityManager is suddenly failing to run queries in the H2 database.
#Configuration
#EnableBatchProcessing
public class InMemoryBatchManagementConfig {
#Bean
public ResourcelessTransactionManager resourceslessTransactionManager() {
ResourcelessTransactionManager resourcelessTransactionManager = new ResourcelessTransactionManager();
resourcelessTransactionManager.setNestedTransactionAllowed(true);
resourcelessTransactionManager.setValidateExistingTransaction(false);
return resourcelessTransactionManager;
}
#Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(ResourcelessTransactionManager txManager)
throws Exception {
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(txManager);
factory.setValidateTransactionState(false);
factory.afterPropertiesSet();
return factory;
}
#Bean
public JobRepository jobRepository(MapJobRepositoryFactoryBean factory) throws Exception {
return factory.getObject();
}
#Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
launcher.afterPropertiesSet();
return launcher;
}
#Bean
public JobExplorer jobExplorer(MapJobRepositoryFactoryBean factory) {
return new SimpleJobExplorer(factory.getJobInstanceDao(), factory.getJobExecutionDao(),
factory.getStepExecutionDao(), factory.getExecutionContextDao());
}
#Bean
public BatchConfigurer batchConfigurer(MapJobRepositoryFactoryBean mapJobRepositoryFactory,
ResourcelessTransactionManager resourceslessTransactionManager,
SimpleJobLauncher jobLauncher,
JobExplorer jobExplorer) {
return new BatchConfigurer() {
#Override
public JobRepository getJobRepository() throws Exception {
return mapJobRepositoryFactory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return resourceslessTransactionManager;
}
#Override
public JobLauncher getJobLauncher() throws Exception {
return jobLauncher;
}
#Override
public JobExplorer getJobExplorer() throws Exception {
return jobExplorer;
}
};
}
}
This error happens because your code will be already executed in a transaction driven by Spring Batch. So running the job in the scope of a transaction is not correct. However, if you still want to disable the transaction validation done by the job repository, you can set the validateTransactionState to false, see AbstractJobRepositoryFactoryBean#setValidateTransactionState.
That said, running the job in a transaction is not the way to fix org.hibernate.LazyInitializationException. The property spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true is there for a reason, and if it works for you, I believe it is a better approach than running the entire job in a transaction (and btw, if I had to use a transaction for that, I would narrow its scope to the minimum (for example the step) and not the entire job).
You can do transactions programmatically using TransactionTemplate to run only the "setup" inside a transaction (instead of having everything in #Transactional). Unfortunately this way the transaction will be committed and you will need to do some manual cleanup.
It can be autowired as any other bean:
#Autowired
private TransactionTemplate transactionTemplate;
...and it's used this way:
transactionTemplate.execute((transactionStatus) -> {
// ...setup...
return null; // alternatively you can return some data out of the callback
});

Prevent hibernate entity changes from being persisted

I am updating my application from Spring Boot 1.4.5 / Hibernate 4.3.5 to Spring Boot 2.0.9 / Hibernate 5.2.18 and code that used to work in the previous configuration is no longer working.
The scenario is as follows:
Start a transaction by entering a method annotated with #Transactional
Hydrate the entity
Change the entity
Make another query
Detect a problem. As a result of this problem, determine that changes should not persist.
Evict the entity
Exit the method / transaction
With Hibernate 4.3.5, calling entityManager.detach() would prevent the changes from being persisted. However, with Hibernate 5.2.18, I'm finding that changes are persisted even with this call. I have also tried to evict() from the session and I have tried to clear() all entities from the session (just to see what would happen).
So I ask - is it possible to discard entity changes in Hibernate 5.2.18 the way that I was able to do in Hibernate 4.3.5?
The relevant code is below...
#Entity
public class Agreement {
private Long agreementId;
private Integer agreementStateId;
#Id
#Column(name = "agreement_id")
public Long getAgreementId() {
return agreementId;
}
public void setAgreementId(Long agreementId) {
this.agreementId = agreementId;
}
#Basic
#Column(name = "agreement_state_id", nullable = false)
public Integer getAgreementStateId() {
return agreementStateId;
}
public void setAgreementStateId(Integer agreementStateId) {
this.agreementStateId = agreementStateId;
}
}
#Component
public class Repo1 {
#PersistenceContext(unitName = "rights")
private EntityManager entityManager;
public void evict(Object entity) {
entityManager.detach(entity);
}
public Agreement getAgreement(Long agreementId) {
// Code to get entity is here.
// Agreement with an agreementStateId of 5 is returned.
}
public void anotherQuery() {
// Code to make another query is here.
}
}
#Component
public class Service1 {
#Autowired
Repo1 repo;
#Transactional
public void doSomething() {
Agreement agreement = repo.getAgreement(1L);
// Change agreementStateId. Very simple for purposes of example.
agreement.setAgreementStateId(100);
// Make another query
repo.anotherQuery();
// Detect a problem here. Simplified for purposes of example.
if (agreement.getAgreementStateId() == 100) {
repo.evict(agreement);
}
}
}
I have found the problem and it has nothing to do with evict(). It turns out that an additional query was causing the session to flush prior to the evict() call.
In general, the application uses QueryDSL to make queries. Queries made in this way did not result in the session flushing prior to making a query. However in this case, the query was created via Session.createSQLQuery(). This uses the FlushMode already assigned to the session which was FlushMode.AUTO.
I was able to prevent the flush by calling setHibernateFlushMode(FlushMode.COMMIT) on the query prior to making the query. This causes the session FlushMode to temporarily change until after the query has been run. After that, the evict() call worked as expected.

Lazy initialisation and Spring Data DomainClassConverter

I'm trying to use the DomainClassConverter from Spring Data to load entities in a controller and then use these entities in a service.
The problem is that I get a LazyInitializationException when I access lazy loaded collection from my Service.
Adding Transactional annotation to the controller does not help, I guess the conversion occurs before the start of the controller transaction.
Is there a way to use this converter in this kind of use case ? can I reattach the entity to the current session someway ?
My controller:
#RestController
#RequestMapping("/api/quotation-requests/{id}/quotation")
public class QuotationResource {
#RequestMapping(value = "/lines", method = RequestMethod.POST, params="freeEntry")
#Timed
public ResponseEntity<PricingLineDTO> addFreeEntryLine(#PathVariable("id") Quotation quotation, #RequestBody PricingLineDTO pricingLineTo)
{
PricingLine pricingLine = conversionService.convert(pricingLineTo, PricingLine.class);
pricingLine = quotationService.addFreeLineToQuotation(quotation, pricingLine);
return new ResponseEntity<>(conversionService.convert(pricingLine, PricingLineDTO.class), HttpStatus.OK);
}
}
The service:
#Service
#Transactional
public class QuotationService {
public PricingLine addFreeLineToQuotation(Quotation quotation, PricingLine pricingLine) {
quotation.getPricingLines().add(pricingLine); // org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: x.y.z.Quotation.pricingLines, could not initialize proxy
}
}
And the entity
#Entity
public class Quotation {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<PricingLine> pricingLines = new ArrayList<>();
}
If it is not possible, what is my best alternative :
make the controller transactionnal, inject data repositories in it and still offer a Service API that takes Entities parameters instead of IDs.
Cons: controller become transactional, which seem to be commonly considered as a bad practice; it introduces boilerplate code.
make the Service API takes IDs as parameters, letting the controller out of the transaction.
Cons: The API become harder to read and can be error prone as every entities as referred as "Long" object, especially when a Service method needs severals entities as input. For example:
void addUserToGroup(Long userId, Long groupId)
One could easily switch parameters.

Unexpected detached entities with extended persistent context

I'm trying to maintain state across multiple calls by using an EXTENDED_PERSISTENT_CONTEXT. My understanding is that managed entities will not detach between calls however I keep getting errors related to detached entities in calls after I have previously thrown validation errors. The state is being maintained in a stateful session bean:
#Named(SessionFacadeBean.SEAM_NAME)
#SessionScoped
#Stateful
#LocalBean
#AccessTimeout(value = 10, unit = TimeUnit.SECONDS)
public class SessionFacadeBean implements Serializable
{
public static final String SEAM_NAME = "sessionCacheBean";
#PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME, type = PersistenceContextType.EXTENDED)
private EntityManager em;
private ParentOne sessionData;
public synchronized ParentOne getSessionData() {
if(sessionData == null) {
sessionData = new ChildTwo();
}
return sessionData;
}
public boolean getLock() {
return true;
}
public void clearLock() {
}
// Other stuff I don’t ‘think’ is relevant.
}
The (simplified) state is being stored using hibernate. It consists of three classes (a parent, and two children, one of which contains a list of children):
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Class", length = 50)
#Entity
public class ParentOne
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlElement(name = "ID")
private Long iD;
#XmlElement(name = "name")
protected String friendlyName = "";
}
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Entity
public class ChildOne extends ParentOne
{
public ChildOne(String name, ParentOne child) {
super(name);
myChild = child;
}
#ManyToOne(cascade = CascadeType.ALL)
protected ParentOne myChild;
}
#XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
#XmlAccessorType(XmlAccessType.NONE)
#Entity
public class ChildTwo extends ParentOne
{
public ChildTwo() {
super(“common”);
}
}
I’m accessing the stateful bean from a stateless bean like so:
#Stateless
#LocalBean
#Path("/")
public class MyService
{
#PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME)
private EntityManager em;
#Inject
private SessionFacadeBean sessionBean;
#POST
#Path("/create/item")
#ValidateRequest
public ComponentShortSummary addItem(#Form NewItemForm itemForm)
{
if(sessionBean.getLock()) {
try {
if(itemForm.getName().equals("INVALID") == true) {
throw new ConstraintViolationException("Failed", new HashSet<ConstraintViolation<?>>());
}
ChildOne child = new ChildOne(itemForm.getName(), sessionBean.getSessionData());
em.persist(child);
return null;
}
finally {
sessionBean.clearLock();
}
} else {
return null;
}
}
}
To reproduce the problem, I perform the following sequence:
Call addItem with a valid name (this persists the item to the database).
Call addItem with a name ‘INVALID’, this throws the constraint exception.
Call addItem with a valid name (this results in a detached entity error on the line em.persist(child).
What I don’t understand is how/why I’m ending up with detached entities. In the real code, I would be performing some request / state validation, before modifying the state (so there is no reason that I can see for the state to have been detached).
If I remove the call to sessionBean.getLock() then the problem goes away (the objects persist correctly). The purpose of the lock methods is essentially to serialize access to the session state, however currently the getLock() method is empty, it feels like the problem might be related to the fact that I’m calling into the stateful bean before throwing the exception.
Can anybody explain what’s going on that results in my entities becoming detached / if there is a way to avoid it (and ideally point me at any documentation that supports the explanation)?
Whilst there are probably ways that I can work around the current issue, performing validation before accessing the stateful bean at all, I’m concerned about the general case (where any exception is thrown after the stateful bean has been accessed in the call). Is there an accepted strategy for dealing with exceptions when I don’t want the entities from the extended persistent context to be detached?
It looks like this is expected behaviour. Thanks to Scott Marlow's reference to the JPA spec, section 3.3.2.
Transaction Rollback
For both transaction-scoped and extended
persistence contexts, transaction rollback causes all pre-existing
managed instances and removed instances[31] to become detached. The
instances’ state will be the state of the instances at the point at
which the transaction was rolled back. Transaction rollback typically
causes the persistence context to be in an inconsistent state at the
point of rollback. In particular, the state of version attributes and
generated state (e.g., generated primary keys) may be inconsistent.
Instances that were formerly managed by the persistence context
(including new instances that were made persistent in that
transaction) may therefore not be reusable in the same manner as other
detached objects—for example, they may fail when passed to the merge
operation.[32]
So, entities that are involved in the active transaction are detached when the transaction is rolled back and by calling out to the sessionBean I am involving it in the transaction.
One way around this appears to be to decorate acceptable exceptions with the #AppicationException annotation. This marks the exception as non-fatal and prevents the transaction from being rolled back. This approach is described in some detail by David Blevin.

Categories