Querydsl 4 StringExpression of a field inside a SimplePath - java

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.

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

Spring boot #Caching With Multiple Keys

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

How get Cassandra, Spring Boot to call field setters for an entity?

I have an entity that is grabbed from Cassandra by a repository. In it are some custom fields that I want set when certain managed fields are set by Spring/Cassandra.
But when I try to put the primary key signifier on the getter method (similar to JPA) it doesn't use the methods. How do I get it to call them when setting the fields?
#Table(name="entity")
public class MyEntity {
private String calculatedField;
private CompoundKey pk;
...elided...
#PrimaryKey
public void setPk(CompoundKey pk) {
this.pk = pk;
//do some calculations...
this.calculatedField = pk.getField() + val;
}
}
This always leaves calculatedField as null.
The AccessType annotation is exactly for that purpose.
Your entity should look like this:
#AccessType(Type.PROPERTY)
public class MyEntity {
//...
}

Saving/updating data with spring

I get an javax.persistence.EntityNotFoundException from the following code:
#Transactional
public ManagementEmailConfig save(ManagementEmailConfig managementEmailConfig)
{
logger.info("Save Management Email Config");
try
{
managementEmailConfig = entityManager.merge(managementEmailConfig);
entityManager.flush();
} catch (Exception e)
{
//ERROR: com.xxx.app.dao.kpi.ManagementEmailConfigDAO -
Not able to save Management Email Config
//javax.persistence.EntityNotFoundException: Unable to find com.xxx.app.model.configuration.AlertCommunicationAddress with id 1260
logger.error("Not able to save Management Email Config", e);
return null;
}
return managementEmailConfig;
}
where the model looks like this (shortened version):
import java.io.Serializable;
import javax.persistence.*;
import java.util.List;
/**
* The persistent class for the MANAGEMENT_EMAIL_CONFIG database table.
*
*/
#Entity
#Table(name="MANAGEMENT_EMAIL_CONFIG")
#NamedQuery(name="ManagementEmailConfig.findAll", query="SELECT m FROM ManagementEmailConfig m")
public class ManagementEmailConfig implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="MANAGEMENT_EMAIL_CONFIG_ID")
private long managementEmailConfigId;
//bi-directional many-to-one association to AlertCommunicationAddress
#OneToMany(mappedBy="managementEmailConfig")
private List<AlertCommunicationAddress> alertCommunicationAddresses;
public ManagementEmailConfig() {
}
public long getManagementEmailConfigId() {
return this.managementEmailConfigId;
}
public void setManagementEmailConfigId(long managementEmailConfigId) {
this.managementEmailConfigId = managementEmailConfigId;
}
public List<AlertCommunicationAddress> getAlertCommunicationAddresses() {
return this.alertCommunicationAddresses;
}
public void setAlertCommunicationAddresses(List<AlertCommunicationAddress> alertCommunicationAddresses) {
this.alertCommunicationAddresses = alertCommunicationAddresses;
}
public AlertCommunicationAddress addAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().add(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(this);
return alertCommunicationAddress;
}
public AlertCommunicationAddress removeAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().remove(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(null);
return alertCommunicationAddress;
}
}
The use case is that the user provides a new alertCommunicationAddress to an existing ManagementEmailConfig and I want create the alertCommunicationAddress then update the ManagementEmailConfig.
If you are using Spring you've made life really difficult for yourself by not using Spring features
I suggest you do the following:
Using Spring Data JPA, write a repository interface to allow
you to easily persist your entity:
public interface ManagementEmailConfigRepository extends JpaRepository { }
use it to persist your entity (save is insert if it's not there,
update if it is)
#Inject
private ManagementEmailConfigRepository managementEmailConfigRepository;
....
managementEmailConfigRepository.save(managementEmailConfig);
This gets rid of the following from your code:
needing to write a save method at all
needing to do a flush
no need for try catch type code
no need for that named query on your entity
(you get that for free on your repository)
I'll leave it up to you to decide where you want the #Transactional annotation; it really shouldn't be on your DAO layer but higher up, e.g. your service layer.

Spring Data Elasticsearch: Multiple Index with same Document

I'm using spring-data-elasticsearch and for the beginning everything works fine.
#Document( type = "products", indexName = "empty" )
public class Product
{
...
}
public interface ProductRepository extends ElasticsearchRepository<Product, String>
{
...
}
In my model i can search for products.
#Autowired
private ProductRepository repository;
...
repository.findByIdentifier( "xxx" ).getCategory() );
So, my problem is - I've the same Elasticsearch type in different indices and I want to use the same document for all queries. I can handle more connections via a pool - but I don't have any idea how I can implement this.
I would like to have, something like that:
ProductRepository customerRepo = ElasticsearchPool.getRepoByCustomer("abc", ProductRepository.class);
repository.findByIdentifier( "xxx" ).getCategory();
Is it possible to create a repository at runtime, with an different index ?
Thanks a lot
Marcel
Yes. It's possible with Spring. But you should use ElasticsearchTemplate instead of Repository.
For example. I have two products. They are stored in different indices.
#Document(indexName = "product-a", type = "product")
public class ProductA {
#Id
private String id;
private String name;
private int value;
//Getters and setters
}
#Document(indexName = "product-b", type = "product")
public class ProductB {
#Id
private String id;
private String name;
//Getters and setters
}
Suppose if they have the same type, so they have the same fields. But it's not necessary. Two products can have totally different fields.
I have two repositories:
public interface ProductARepository extends ElasticsearchRepository<ProductA, String> {
}
public interface ProductBRepository
extends ElasticsearchRepository<ProductB, String> {
}
It's not necessary too. Only for testing. The fact that ProductA is stored in "product-a" index and ProductB is stored in "product-b" index.
How to query two(ten, dozen) indices with the same type?
Just build custom repository like this
#Repository
public class CustomProductRepositoryImpl {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public List<ProductA> findProductByName(String name) {
MatchQueryBuilder queryBuilder = QueryBuilders.matchPhrasePrefixQuery("name", name);
//You can query as many indices as you want
IndicesQueryBuilder builder = QueryBuilders.indicesQuery(queryBuilder, "product-a", "product-b");
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build();
return elasticsearchTemplate.query(searchQuery, response -> {
SearchHits hits = response.getHits();
List<ProductA> result = new ArrayList<>();
Arrays.stream(hits.getHits()).forEach(h -> {
Map<String, Object> source = h.getSource();
//get only id just for test
ProductA productA = new ProductA()
.setId(String.valueOf(source.getOrDefault("id", null)));
result.add(productA);
});
return result;
});
}
}
You can search as many indices as you want and you can transparently inject this behavior into ProductARepository adding custom behavior to single repositories
Second solution is to use indices aliases, but you had to create custom model or custom repository too.
We can use the withIndices method to switch the index if needed:
NativeSearchQueryBuilder nativeSearchQueryBuilder = nativeSearchQueryBuilderConfig.getNativeSearchQueryBuilder();
// Assign the index explicitly.
nativeSearchQueryBuilder.withIndices("product-a");
// Then add query as usual.
nativeSearchQueryBuilder.withQuery(allQueries)
The #Document annotation in entity will only clarify the mapping, to query against a specific index, we still need to use above method.
#Document(indexName="product-a", type="_doc")

Categories

Resources