Avoidy Lazy Initialization except in dozer mapping for null collections - spring

#Override
RegistrationDto isCandidateUnfit(RegistrationDto dto) {
Long nationId = 0L;
if(dto.getNationMast() != null){
nationId = dto.getNationMast().getNationId();
}
Registration reg = registrationRepo.findCandidateMedicalStatus(dto.getPassportNo(),nationId,
dto.getCivilId(), ServiceConstants.CANDIDATE_MED_STATUS_UNFIT_ID);
return getMapper().map(reg, RegistrationDto.class) ;
}
While mapping the doing dozer mapping if one caollection(appointments) is null, it throw exception failed to lazily initialize a collection of role: om.gov.moh.model.cdc.Registration.appointments
It can't be changed from Lazy to eager
//RegistrationDto
#SuppressWarnings("serial")
public class RegistrationDto extends SearchDto implements java.io.Serializable {
// Fields
private Long regId;
#JsonIgnore
private Set<AppointmentDto> appointments = new HashSet<>(0);
private String orderStatus;
/** setters & getters **/
}

You have the possibility to create custom converters as shown here.
Create a custom converter and when you have an uninitialized collection, just set it to null or empty. You can do the check with Hibernate.isInitiliazed(Object proxy). Reference here.

Related

Bidirectional #OneToOne Spring Data JPA, Hibernate

I am using Bidirectional #OneToOne from Hibernate documentation. I have created an identical model for the test.
I can't get Phone via PhoneDetails. I get an error - Message Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.example.model.Phone#1] - no Session.
I've tried many options and it doesn't work.
Please tell me how to get the Phone correctly? I sit all day trying to do this. I did not find any options on the Internet, so I ask here.
Phone.java
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#OneToOne(mappedBy = "phone",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
// Getters and setters are omitted for brevity
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
PhoneDetails.java
#Entity(name = "PhoneDetails")
public class PhoneDetails {
#Id
#GeneratedValue
private Long id;
private String provider;
private String technology;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
// Getters and setters are omitted for brevity
}
LifecycleController.java
#Controller
public class LifecycleController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/savePhoneAndPhoneDetails")
public String savePersonAddress () {
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );
phone.addDetails( details );
serviceJpa.savPhone( phone );
return "/savePhoneAndPhoneDetails";
}
#GetMapping(value = "/getPhone")
public String addPersonAddress () {
PhoneDetails address = serviceJpa.findPhoneDetailsById(2L).orElseThrow();
Phone phone = address.getPhone();
/*
An error appears here -
could not initialize proxy
[com.example.model.Phone#1] - no Session
*/
System.out.println(phone.getNumber());
return "/getPhone";
}
}
ServiceJpa.java
#Service
#Transactional
public class ServiceJpa {
#Autowired
PhoneJpa phoneJpa;
#Autowired
PhoneDetailsJpa phoneDetailsJpa;
#Transactional
public void savPhone(Phone phone) {
phoneJpa.save(phone);
}
#Transactional
public Optional<PhoneDetails> findPhoneDetailsById(Long id) {
return phoneDetailsJpa.findById(id);
}
}
interface PhoneJpa.java
#Repository
public interface PhoneJpa extends JpaRepository<Phone, Long> {
}
interface PhoneDetailsJpa.java
#Repository
public interface PhoneDetailsJpa extends JpaRepository<PhoneDetails, Long> {
}
I agree with Andriy's comment with a slight addition of "You should not access [lazily loaded] entity details outside transaction bounds". But, for starters, is there some reason you want the OneToOne to be FetchType.LAZY to begin with? If you changed it to EAGER, your "lazy" problem would be resolved by virtue of it no longer being a lazy reference but being a real hydrated object.
If that is not the exact route you want to take, there are a dozen ways to EAGERLY fetch things in general and frankly too many to present a single solution here as best/ideal. As your code exists, since all the dereferencing (for now) is happening inside your Controller, then Andriy's suggestion to add #Transaction to the Controller may suffice in that it will be lazily fetched when you need it.
But in the future, if you have Lazy elements in a POJO that get returned to the stack higher than the controller, say, just before they are serialized to JSON for example, then even the CONTROLLER's #Transactional wouldn't be "high" enough in the stack and you'll end up with the same Lazy init problem..
Also, by having it be Lazy and then dereferencing it elsewhere, you're guaranteeing two trips to the Database. With proper FETCH/JOIN eager loads, you would limit that to one, which can be another performance benefit.
So either way, you're back to the real problem at hand.. looking for ways to ensure your operations occur ENTIRELY inside a Transaction boundary OR having to completely hydrate the object so no "Lazy" danglers get dereferenced outside of that.. i.e. by making them eager or by force-initializing any potential Lazy proxies/collections.

RestController: returning Resource<> makes FetchType.LAZY to behave as FetchType.EAGER

I am having below mappings as Company having 1:N relation with CompanyFunds
#Entity
public class Company{
#Id
private Integer companyId;
private String name;
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
}
#Entity
public class CompanyFunds{
#Id
private Integer fundId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
private Company company;
}
I am using Spring-data-Jpa for my persistence layer and below are the controller and service methods:
//controller
#GetMapping(value = "/{companyId}")
public Resource<Company> find(#PathVariable Integer companyId) {
Resource<Company> companyResource = companyService.find(companyId);
return companyResource;
}
//service
public Resource<CompanyTypeOther> find(Integer companyId) {
Company company = companyRepository.findById(companyId);
return restResourceAssembler.toResource(company);
}
#Component
public class RestResourceAssembler implements ResourceAssembler<T, Resource<T>> {
private EntityLinks entityLinks;
public RestResourceAssembler(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
#Override
public Resource<T> toResource(T entity) {
Resource<T> resource = new Resource<>(entity);
resource.add(entityLinks.linkToSingleResource(entity.getClass(), entity.getId()).withSelfRel());
return resource;
}
}
Now the weird thing is, until the return companyResource;(in controller) doesn't get executed, the companyResource contains null for companyFunds i.e the LAZY loading is working fine till that point. But the moment the return companyResource; executes, something goes inside the Spring and the Select statement for CompanyFund gets fired. I debugged the steps and below is the code(try block) responsible for this:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
......
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
........other code
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
.....
}
There are no toString() declared in entities, also no getCompanyFund() called. Can't understand what Spring is doing with returnValue above, so that some getters(or something) are getting called.
One more thing I noticed is, this problem only occurs when the Resource<Company> is returned. If I return Company from the controller, nothing unexpected happens. Lazy loading works fine.
Since I want to lazy load the entity, a little fix/hack solved the problem for me(as of now).
#JsonIgnore
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
#JsonIgnore prevents LAZY loaded entity from being serialized. So I guess the jackson is the culprit here.
This is not a fix is my view but just a hack to do the thing. Still waiting someone from Spring team to reply.

Hibernate -validator group sequence provider getDefaultSequenceProvider gets null as input

I am using the hibernate validator group sequence and want to execute the groups in a sequence based on business rules. But the input to the groupSequenceProvider for its getValidationGroups is always null, and hence custom sequence never gets added.
My request object:
#GroupSequenceProvider(BeanSequenceProvider.class)
public class MyBean {
#NotEmpty
private String name;
#NotNull
private MyType type;
#NotEmpty(groups = Special.class)
private String lastName;
// Getters and setters
}
Enum type:
public enum MyType {
FIRST, SECOND
}
My custom sequence provider:
public class BeanSequenceProvider implements DefaultGroupSequenceProvider<MyBean> {
#Override
public List<Class<?>> getValidationGroups(MyBean object) {
final List<Class<?>> classes = new ArrayList<>();
classes.add(MyBean.class);
if (object != null && object.getType() == MyType.SECOND) {
classes.add(Special.class);
}
return classes;
}
}
Group annotation:
public interface Special {
}
When I execute the above code, I get the input MyBean object as null and cannot add the custom sequence. What am I missing? I am using hibernate-validator version as 5.4.1.Final

LazyInitializationException in AOP logger aspect

I have an app build with Spring, JSF JPA and Hibernate.
I have an aspect that "watches" every update*, create*,delete* method from my service layer of the application. This aspect logs the method params, apply toString to every param and them log them in the database. The problem is that I use domain objects in jsf and when i try to update* something i get a LazyInitializationException when toString() is applied to the method param.
One solution would be to remove from toString all params that represents other objects but then the operation has no sense as i do not log the details that interests me.
ie. I have an entity called Price which has a dependency PriceList:
public class Price extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "price")
private Double price;
//bi-directional many-to-one association to TelCoPriceList
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "price_list_id")
private PriceList priceList;
.....................
}
And the price is used into AddPriceForm.xhtml, which is the jsf form. The JSF AddPriceMB has a reference to PriceService which performs the actual update.
The PriceService is "monitored" by this aspect:
#Aspect
#Named("auditLogAspectImpl")
public class AuditLogAspectImpl implements AuditLogAspect {
#Inject
private UserService userService;
#Inject
private AuditLogService auditLogService;
#Override
#AfterReturning(pointcut = "execution(* com.videanuadrian.core.impl.services..*.save*(..)) or execution(* com.videanuadrian.core.impl.services..*.update*(..)) or execution(* com.videanuadrian.core.impl.services..*.delete*(..))", returning = "retVal")
public boolean afterLogEvent(JoinPoint joinPoint,Object retVal) {
String className = joinPoint.getTarget().getClass().getName();
String methondName = joinPoint.getSignature().getName();
...................................................................
StringBuffer logMessage = new StringBuffer();
Object[] args = joinPoint.getArgs();
//for login action we only get the username and hash the password
if (methondName.compareTo("login") == 0){
logMessage.append(args[0]);
}else {
int argsLen = args.length;
//else log all the parameters
for (int i = 0; i < argsLen; i++) {
// this is where the exception occurs !!!!!!!!!!!!!!!!!!!!!!!!!!!
logMessage.append(args[i]).append(",");
}
if (argsLen > 0) {
logMessage.deleteCharAt(logMessage.length() - 1);
}
}
//some save/update methods return Boolean
Boolean status = false;
if (retVal instanceof Boolean){
status = (Boolean) retVal;
}
//some return the ID of the object inserted,and if these methods return an integer the status is true, if they return null, the status si false
if (retVal instanceof Integer){
if (retVal!=null)
status = true;
}
auditLogService.addAuditLogEvent(uid, className+"."+methondName, status,logMessage.toString());
return true;
}
On a prior version I did not had this problem because I had used DTO objects and the conversion between DTO and Domain Objects was performed in the service layer. The error is very clear, as my session was closed long ago compared to the time that I perform this toString() operation.
Any idea how can i achieve this without using openSessionInView or extended persistence context? Or maybe my approach is not good....

Spring java syntax error

I have a self join class where I want to save a parent object along with the children. To implement that I have:
Model
public class UserEntity implements Serializable {
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="checker_id")
private UserEntity checker;
#OneToMany(mappedBy="checker", orphanRemoval=true, cascade = CascadeType.ALL)
private Set<UserEntity> setters = new HashSet<UserEntity>();
// getter and setter
public void addSetter(UserEntity setter) {
if(setters == null) {
setters = new HashSet<UserEntity>();
}
setter.setChecker(this);
this.setters.add(setter);
}
This is the DAO
public UserEntity create(UserEntity checker){
List<UserEntity> list = new ArrayList();
for(UserEntity setter : list)
checker.addSetter(setter);
if (checker != null)
sessionFactory.getCurrentSession().persist(checker);
return checker;
I get the error
Syntax error on token "(", Expression expected after this token
at this line
for(UserEntity setter : List<UserEntity>())
I would appreciate knowing what is missing in the DAO code.
It should be like that
List<UserEntity> list = ...
for(UserEntity setter : list)

Resources