LazyInitializationException in AOP logger aspect - spring

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....

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.

How to generate cache CustomKey for Redis in Spring Boot

I have spring boot application which is integrated with Redis cache. Have to implement caching for one of the method call. That method argument is an object with multiple params which is external Request object. This object params will vary for each request also based on that param and its values output of the method is varies. I need to create a cache key using that Request object field/param values. How to achieve it.
We can use SimpleKeyGenerator only when method params are static?
UserService.java
#Cacheable(value = "usercache", keyGenerator="customKeyGenerator")
public UserResponse getUserResp(User user){
//Some backend calls
return user
}
User.java
public class User {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
}
In this method implementation User object is dynamic. I have to create a cache key based on User object fields which is having valid non null values. How to achieve it.
I have implemented as like below.
User.java
public class User implements Serializable {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
#Override
public int hashCode() {
final int prime = 31;
//Add necessary fields
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
//Add necessary fields
}
}
public class UserKeyGenerator implements KeyGenerator{
private static final String UNDERSCORE_DELIMITER = "_";
#Override
public Object generate(Object target, Method method, Object... params) {
String cacheKey = null;
if(params.length > 0) {
StringJoiner paramStrJoiner = new StringJoiner(UNDERSCORE_DELIMITER);
User userReq = (User) params[0];
paramStrJoiner.add(target.getClass().getSimpleName());
paramStrJoiner.add(method.getName());
paramStrJoiner.add(String.valueOf(userReq.hashCode()));
cacheKey = paramStrJoiner.toString();
}
return cacheKey;
}

JHipster - Insert in the database with the GET method

I have to create an application with Jhipster but i never use it before.
When a user send a GET request to the address http://localhost:8080/api/newmesure/{mac-address}/{value}
I want to insert a new mesure in my database.
First i created 3 entity "Plantes", "Capteurs" and "Mesures" with this format :
Image here : https://i.stack.imgur.com/zJqia.png (I'm not allowed to post)
I activated the JPA Filtering to create a #Query to insert data in my database but i read that was not possible.
In /src/main/java/com/mycompany/myapp/web/rest/MesuresRessources.java :
/**
* REST controller for managing {#link com.mycompany.myapp.domain.Mesures}.
*/
#RestController
#RequestMapping("/api")
public class MesuresResource {
private final Logger log = LoggerFactory.getLogger(MesuresResource.class);
private static final String ENTITY_NAME = "mesures";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final MesuresService mesuresService;
private final MesuresQueryService mesuresQueryService;
public MesuresResource(MesuresService mesuresService, MesuresQueryService mesuresQueryService) {
this.mesuresService = mesuresService;
this.mesuresQueryService = mesuresQueryService;
}
#GetMapping("/newMesure/{mac}/{value}")
public String newMesure(#PathVariable String mac,#PathVariable int value) {
log.debug("Adresse MAC : "+mac);
log.debug("Valeur : "+value);
#Query("SELECT valeur FROM Mesures WHERE id = 1") //not working
Mesures getValeur(); //not working
return "Mesure ajoutée";
}
}
In /src/main/java/com/mycompany/myapp/domain/Mesures.java :
/**
* A Mesures.
*/
#Entity
#Table(name = "mesures")
public class Mesures implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "valeur")
private Integer valeur;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties("macs")
private Capteurs mac;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getValeur() {
return valeur;
}
public Mesures valeur(Integer valeur) {
this.valeur = valeur;
return this;
}
public void setValeur(Integer valeur) {
this.valeur = valeur;
}
public Capteurs getMac() {
return mac;
}
public Mesures mac(Capteurs capteurs) {
this.mac = capteurs;
return this;
}
public void setMac(Capteurs capteurs) {
this.mac = capteurs;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Mesures)) {
return false;
}
return id != null && id.equals(((Mesures) o).id);
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
return "Mesures{" +
"id=" + getId() +
", valeur=" + getValeur() +
"}";
}
}
Louan
Learning java with JHipster is probably not a wise idea, it uses a very rich technology stack which might lose you unless you invest enough time to learn the basics.
There are many things wrong in your code and approach:
You can't use #Query annotation inside the body of method a of your REST controller, it must be used in your #Repository interface, this code can't compile. See https://www.baeldung.com/spring-data-jpa-query for a quick introduction
JPA filtering is not related to inserting into database
In HTTP/REST, GET method is supposed to be idempotent. For making changes in your database you should use POST or PUT methods. See What is idempotency in HTTP methods?
Your entity naming convention is not consistent: use singular for entity classes because each entity object represents one single instance of Mesure. Here you have Plantes (plural), Capteur (singular) and Mesures (plural). For table names, JHipster uses singular but plural is quite common too because a table holds many rows. Of course, this is just a convention and you or your team may decide to apply another (like a prefix for table names) but the key point is to be consistent.

Avoidy Lazy Initialization except in dozer mapping for null collections

#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.

Spring does not inject SessionFactory when calling CustomValidator second Time

i am developing a application using spring and hibernate. i have created a custom validator for for checking duplicate value in the database for that i need to inject hibernate SessionFactory in the CustomValidator. it works fine for on a single field but when i apply it on two or more field i get a NullPointerException it seems like spring does not inject the SessionFactory while calling validator second time.
here is code of entity class
public class CenterRegistration implements Serializable {
#Column(unique=true)
#UniqueKey(message="Email Id already exist please choose another Email Id ", table = "CenterRegistration", column = "email")
private String email;
#UniqueKey(message="UserName already exist please choose another username", table = "CenterRegistration", column = "userName")
#Column(unique = true)
private String userName;
And here is code of customvalidator implementing class
public class UserNameConstraintValidator implements
ConstraintValidator<UniqueKey, String> {
#Inject
private SessionFactory sessionFactory;
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("in is valid value=" + value + " " + sessionFactory);
if (sessionFactory == null) {
return true;
}
Session session = sessionFactory.openSession();
it works fine when i apply this validator on a single field on my entity but when applying on second spring injects null into the sessionfactory.
if (sessionFactory == null) {
return true;
}
i have used these just to avoid NullPointerException.

Resources