Spring boot #Caching With Multiple Keys - java

public class Student {
public int studentName;
public String Addr1;
public String Addr2;
public String Addr2;
//getter setter
}
I have one repository class which contains following methods
class StudentRepoImpl{
#Cacheable(value = "Students")
public List<Students> findAllStudents() {
//fetching all cust and putting in the Students cache
}
#Cacheable(value = "Students")
public List<Students> findStudentsBystudentNameAndAddresses() {
//fetching all cust data by **Name/Address1/Address2/Address3** basis of field available studentName/Address1/Address2/Address3 and putting in Student table
}
}
Current Output :
Fetch All data from DB and adding in Students cache in findAllStudents() method
But while searching for data based on some criteria (Name/Address1/Address2/Address3) using findStudentsBystudentNameAndAddresses() method it is fetching data from DB instead of cache.
Note: Not added Key while caching because there are 4 fields in search criteria (Name/Address1/Address2/Address3) and these are conditional fields means some time only Address1 will be in search criteria or sometime Address1+Address2 or sometimes all Address1+Address2+Address3 fields and I want to fetch exact match on the basis of Name and available Addresses.

did you add #EnableCaching annotation in your configuration class

Try to add configuration like this
#Configuration
public class CachingConfig {
#Bean(name = "springCM")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("Students");
}
}
Hope useful

Related

How to centralize data encryption and decryption in Java?

I have two functions to encrypt and decrypt data.
My current code is as below. I have entity class, DTO class, repository and service class.
The name need to be encrypted before save to database and to be decrypted when retrieve from database.
Lets say I have 10 different entity classes need to do the encryption and decryption data, I need to add the encryption and decryption function to each service class as below codes.
Is there any way to do all the encryption and decryption data in one service class for all the entity? like overriding the Get and Set method in entity? Anyone can advice? Thanks a lot.
#Entity
#Getter
#Setter
public class Customer {
private Long id;
private String name;
private String contact;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class CustomerDTO {
private String name;
}
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>{
}
#Service
public class CustomerService {
#Autowired
private CustomerRepository customerRepository;
#Autowired
private EncrytionService encrytionService;
public void save(String name){
Customer customer = new Customer();
customer.setName(encrytionService.doEncrypt(name));
customerRepository.save(customer);
}
public CustomerDTO getCustomer(Long customerId) {
Customer customer = customerRepository.findById(customerId);
CustomerDTO dto = new CustomerDTO();
dto.setName(encrytionService.doDecrypt(customer.getName()));
return dto;
}
}
Not sure there is a way to do that easily (kinda out-of-the-box way), but maybe you could try to implement something yourself using JPA lifecycle events and EntityListener.
For example:
// this is going to be our EntityListener
public class SensitiveDataListener {
#PrePersist
void beforeAnyPersist(Customer customer) {
// encrypt what you need and set
// e.g. customer.setName(encrytionService.doEncrypt(customer.getName()));
}
// after an entity has been loaded
#PostLoad
void afterLoad(Customer customer) {
// decrypt what you need
}
}
// and this is how you add it to your entity
#EntityListeners(SensitiveDataListener.class)
#Entity
public class Customer {
//...
}
A good question here would be - ok I have multiple entities, what do I do - create multiple **Listener classes? In general, no. Your listener can "handle" multiple entities, but how to make it happen - depends on what you need - for instance, if you need to encrypt/decrypt different fields in different entity that's one case, and if you need encrypt/decrypt let's say name and you have this field in different entities, that would be another case and another solution. Also, you might want to encrypt everything and again that would be a different solution because it is yet another use case.
If it is the same field you could probably "unify" you entities (but keep in mind sometimes it is not a good idea when your entities implement some interfaces):
public interface Sensitive {
void setName(final Date date);
}
#EntityListeners(SensitiveDataListener.class)
#Entity
public class Entity1 implements Sensitive {
// override setName
}
#EntityListeners(SensitiveDataListener.class)
#Entity
public class Entity2 implements Sensitive {
// override setName
}
// but then your SensitiveDataListener will look like this
public class SensitiveDataListener {
#PrePersist
void beforeAnyPersist(Sensitive entity) {
// encrypt what you need and set
// e.g. entity.setName(encrytionService.doEncrypt(entity.getName()));
}
// after an entity has been loaded
#PostLoad
void afterLoad(Sensitive entity) {
// decrypt what you need
}
}
Maybe you could also use AttributeConverter, assuming your field is String and encoded value is also String you could create converter which will encode/decode your stuff, but then you need to add it to every field (in every entity) you want to encode.
Something like this:
#Convert(converter = MyAttributeConverter.class)
private String name; // this is entity field

Querydsl 4 StringExpression of a field inside a SimplePath

I have an Order entity which contains a OrderCustomer field and OrderCustomer entity has an Customer field.
#Document
public class Order {
private OrderCustomer orderCustomer;
}
public class OrderCustomer {
private Customer customer;
}
public class Customer {
private String referenceNumber;
}
And the query class generated by org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor looks like:
public QOrder {
public final QOrderCustomer orderCustomer;
}
public QOrderCustomer {
public final SimplePath<Customer> = createSimple("customer", Customer.class);
}
In this case, how can I create a predicate to check Customer.referenceNumber contains any string input from client?
For example:
QOrder.order.orderCustomer.customer.referenceNumber.contains("anystring")
Or maybe the code is not generated properly? Should generate QCustomer?
My dependency versions:
querydsl 4.1.4
+
spring-data-mongodb 1.9.5 (managed by spring-data-releasetrain:Hopper-SR5)
To generate QCustomer, I have to put #QueryEmbeddable on Customer type. Not sure this is a correct solution.

External object linked through foreign key in hibernate and MySql

I'm using Spring data with Hibernate and MySql and I have a doubt.
My entity is
#Entity
#Table(name = "car", catalog = "DEMO")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonBackReference
private CarType carType;
#JsonBackReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonManagedReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
with get and set method.
Sometimes, I need external object as carType, another entity.
If I use this webservice
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public String getCars(#PathVariable int idFleet, Model model){
try{
model.addAttribute("carsList",fleetAndCarService.findCarsByIdFleet(idFleet));
//Modal parameter
model.addAttribute("carTypeList",fleetAndCarService.getCarsType());
model.addAttribute("fleetApplication",fleetAndCarService.getFleetById(idFleet));
model.addAttribute("carForm", new CarForm());
model.addAttribute("error",false);
}catch (Exception e){
LOG.error("Threw exception in FleetAndCarControllerImpl::getCars : " + ErrorExceptionBuilder.buildErrorResponse(e));
model.addAttribute("error",true);
}
return "cars";
}
from my html page I can retrieve carType.idCarType,but if I use this
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
where TableUi has only a field data where I put the result to use it into datatables, I don't have carType and fleet. Why? Do I have to use Hibernate.initialize, and how so it is a list?Thansk,regards
Also this update doesn't work:
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList)
Hibernate.initialize(car.getCarType());
return carList;
}
You could call Hibernate.initialize on each element
Collection<Car> cars = fleetAndCarService.findCarsByIdFleet(idFleet);
for(Car car : cars) {
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
ajaxCall.setData();
return ajaxCall;
This would be a good starting point and would allow you to move forwards. At high scales however this could become a performance bottleneck as it will perform a query with each call to initialize so you will have 2*n queries to the database.
For maximum performance you will have several other options:
Iterate through the cars and build up a list of IDs and then query for the car types by ID in a single query with the list of IDs. Do the same for the fleets. Then call Hibernate.initialize. The first two queries will populate the persistence context and the call to initialize will not need to go to the database.
Create a special query for this call which fetch joins the properties you will need.
Setup batch fetching which will fetch the cards and fleets in batches instead of one car/fleet per query.
Use a second level cache so the initialization causes Hibernate to pull from the cache instead of the database.
Describing these options in details is beyond the scope of a single question but a good place to start would be Hibernate's documentation on performance.

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}

Hibernate one to many cascade all Not saving child id in parent

This might be duplicate. But I don't find a proper link to my problem. May be my searching is too bad. So hoping for a good answer or link to any useful solution. Here is my Configuration.java
public class Configuration implements java.io.Serializable {
private Integer configurationId;
private String configurationName;
private Set<ConfigFields> ConfigFieldses = new HashSet<ConfigFields>(0);
//getter and setter
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="configuration")
public Set<ConfigFields> getConfigFieldses() {
return this.ConfigFieldses;
}
public void setConfigFieldses(Set<ConfigFields> ConfigFieldses) {
this.ConfigFieldses = ConfigFieldses;
}
}
ConfigFields.java
public class ConfigFields implements java.io.Serializable {
private Integer configFieldId;
private Configuration configuration;
private String configFieldName;
//getter and setter
#XmlTransient
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ConfigurationId")
public Configuration getConfiguration() {
return this.configuration;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
}
Here is my code to insert the configuration
Configuration configuration=new Configuration();
configuration.setConfigurationName(configName);
List<ConfigFields> configFieldsList=new ArrayList<ConfigFields>();
for(int i=0;i<fieldLength;i++) {
ConfigFields configField=new ConfigFields();
// configField.setConfiguration(configuration);
String stringI=Integer.toString(i);
if(i<fieldLength-1) {
configField.setConfigFieldName(ENGINE_FORM+stringI);
} else {
configField.setConfigFieldName(ENGINE_TABLE+stringI);
}
configFieldsList.add(configField);
}
Set<ConfigFields> configFields-new HashSet<ConfigFields>(configFieldsList);
configuration.setConfigFieldses(configFields);
configurationService.insert(configuration);
I want to insert one configuration in Configuration table and a set of configFields in ConfigFields table. ConfigFields table has configuration id that should come form confguration table. For a set of config fields, configuration id will be same (As you can guess from code).
The above code works and saving to both tables. But when I check in ConfigFields table, the configurationId is null. I is not setting the Id. Why? As you can see I am not setting the configuration object in each list of ConfigFields object. Do I need to set that?
How to solve this?
Yes what you have persisted is a uni directional relationship. But since you expect hibernate to maintain a bi directional relationship you need to set the configuration object to configField

Categories

Resources