How to retrieve blob from server using JPA repository in spring - java

I've created spring application for CRUD. I can easily write into server data like string,Long,blob but When I try to retrieve it from server. I've encountered with difficulty which byte array from server gives in BigInteger from server. How I could get data in byte array instead of BigInteger?When I write in insert byte array this data which column is BLOB. Here is my code
Repository
public interface ArriveRepository extends JpaRepository<ArriveEntity,Long>
{
#Query(value = "select arrive.time,air_lines.image,arrive.flight,arrive.destination_uzb," +
"arrive.destination_eng,arrive.destination_rus,arrive.status,arrive.status_time " +
"from arrive inner join air_lines on air_lines.id = arrive.airline_id where arrive.arrive_date = (:date1)",nativeQuery = true)
List<Object[]> getForArriveTerminal(#Param("date1") LocalDate date1);
}
When I retrieve data from server I'm using this class
ArriveTerminalDto
public class ArriveTerminalDto {
private String time;
private BigInteger logo;
private String flight;
private String destinationUzb;
private String destinationEng;
private String destinationRus;
private String status;
private String statusTime;
//getter setter}
Service class
public List<ArriveTerminalDto> getToShow(LocalDate date1)
{
List<ArriveTerminalDto> list = new ArrayList<>();
List<Object[]> list1 = arriveRepository.getForArriveTerminal(date1);
for(Object[] objects: list1)
{
ArriveTerminalDto arriveTerminalDto = new ArriveTerminalDto();
arriveTerminalDto.setTime((String)objects[0]);
arriveTerminalDto.setLogo((BigInteger) objects[1]);
arriveTerminalDto.setFlight((String) objects[2]);
arriveTerminalDto.setDestinationUzb((String) objects[3]);
arriveTerminalDto.setDestinationRus((String) objects[4]);
arriveTerminalDto.setDestinationEng((String) objects[5]);
arriveTerminalDto.setStatus((String) objects[6]);
list.add(arriveTerminalDto);
}
return list;
}
This code works but it didn't give me byte array from server.
When I try to change BigInteger into byt[] array it gives me following errors
from postman
{
"timestamp": "2019-01-28T09:33:52.038+0000",
"status": 500,
"error": "Internal Server Error",
"message": "java.math.BigInteger cannot be cast to [B",
"path": "/arrive/terminal/date=2019-01-27"
}
Changed Object into ArriveTerminalDto but still it give error my following repo
public interface ArriveRepository extends JpaRepository<ArriveEntity,Long>
{
#Query(value = "select arrive.time,air_lines.image,arrive.flight,arrive.destination_uzb," +
"arrive.destination_eng,arrive.destination_rus,arrive.status,arrive.status_time " +
"from arrive inner join air_lines on air_lines.id = arrive.airline_id where arrive.arrive_date = (:date1)",nativeQuery = true)
List<ArriveTerminalDto> getForArriveTerminal(#Param("date1") LocalDate date1);
}

Why don't you take a look at the Spring Content community project. This project allows you to associate content with Spring Data entities. Think Spring Data but for Content, or unstructured data. This can also give you REST endpoints for the content as well, like Spring Data REST.
This approach will give you a clear abstraction for your content with implementations for many different types of storage. It is stream-based, rather than byte-based. Using byte[] won't work if you want to transfer very large files. Also getting databases to stream properly is very idiosyncratic. You probably don't want to figure all that out yourself when Spring Content already has.
This is pretty easy to add to your existing projects. I am not sure if you are using Spring Boot, or not. I'll give a non-spring boot example:
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa</artifactId>
<version>0.5.0</version>
</dependency>
<!-- REST API (if you want it) -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.5.0</version>
</dependency>
Configuration
#Configuration
#EnableJpaStores
#Import("org.springframework.content.rest.config.RestConfiguration.class")
public class ContentConfig {
// schema management
//
#Value("/org/springframework/content/jpa/schema-drop-mysql.sql")
private Resource dropContentTables;
#Value("/org/springframework/content/jpa/schema-mysql.sql")
private Resource createContentTables;
#Bean
DataSourceInitializer datasourceInitializer() {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropContentTables);
databasePopulator.addScript(createContentTables);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource());
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
To associate content, add Spring Content annotations to your account entity.
ArriveEntity.java
#Entity
public class ArriveEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.. existing fields...
#ContentId
private String contentId;
#ContentLength
private long contentLength = 0L;
// if you have rest endpoints
#MimeType
private String mimeType = "text/plain";
}
Create a "store":
ArrivEntityContentStore.java
#StoreRestResource(path="arriveEntityContent)
public interface ArrivEntityContentStore extends ContentStore<ArriveEntity, String> {
}
This is all you need to create REST endpoints # /arriveEntityContent. When your application starts, Spring Content will look at your dependencies (seeing Spring Content JPA/REST), look at your ArrivEntityContentStore interface and inject an implementation of that interface for JPA. It will also inject a #Controller that forwards http requests to that implementation. This saves you having to implement any of this yourself which I think is what you are after.
So...
To access content with a Java API, auto-wire ArrivEntityContentStore and use it methods.
Or to access content with a REST API:
curl -X POST /arriveEntityContent/{arriveEntityId}
with a multipart/form-data request will store the image in the database and associate it with the account entity whose id is itemId.
curl /arriveEntityContent/{arriveEntityId}
will fetch it again and so on...supports full CRUD.
There are a couple of getting started guides here. The reference guide is here. And there is a tutorial video here. The coding bit starts about 1/2 way through.
HTH

Try to change entity definition to handle byte[] directly, but suggest JPA to interpret it as Lob. You can do it with #Lob annotation:
public class ArriveTerminalDto {
private String time;
#Lob
private byte[] logo;
private String flight;
private String destinationUzb;
private String destinationEng;
private String destinationRus;
private String status;
private String statusTime;
}
Laster, as #Clijsters suggested, you can change your repo to return List<ArriveTerminalDto>.

Related

Spring boot MongoDB working with encrypted fields

I have a spring boot project (version 2.5.5) and I'm using the spring-boot-starter-data-mongodb dependency to work with MongoDB.
I have a bean with these fields:
#Document(collection = "user_data")
public class UserData {
#Id
private String id;
#Field("is_active")
private Boolean isActive;
#Field("organization_id")
private String organizationId;
#Field("system_mode")
private SystemMode systemMode;
#Field("first_name")
private String firstName;
#Field("last_name")
private String lastName;
}
*Also with constructors and getters and setters but I omitted them for simplicity.
I also have a matching repository:
#Repository
public interface UsersDataRepository extends MongoRepository<UserData, String> {
}
Now the fields firstName and lastName are in fact encrypted and stored in the database as Binary type.
When I try to do say
Optional<UserData> optionalUserData = usersDataRepository.findById(userId);
I get an error stating that failed to convert from Binary to String, which makes sense because the fields are encrypted.
In the database I have a key_vault collection that contains the keys to decrypt.
So how can I add MongoDB client side field level decryption using the above setup so that I can get the fields decrypted and use them in my project?
I followed this guide and got a working solution for my case:
https://blog.contactsunny.com/tech/encrypting-and-decrypting-data-in-mongodb-with-a-springboot-project
In a nutshell, create a component that will handle encrypting and decrypting fields.
Create two event listener classes that will listen to mongo save and get from database events.
My MongoDBAfterLoadEventListener ended up like this,
note that it currently only works for strings:
public class MongoDBAfterLoadEventListener extends AbstractMongoEventListener<Object> {
#Autowired
private EncryptionUtil encryptionUtil;
#Override
public void onAfterLoad(AfterLoadEvent<Object> event) {
Document eventObject = event.getDocument();
List<String> keysToDecrypt = encryptionUtil.ENCRYPTED_FIELDS_MAP.get(event.getCollectionName());
if (keysToDecrypt == null || keysToDecrypt.isEmpty()) {
return;
}
for (String key : eventObject.keySet()) {
if (keysToDecrypt.contains(key)) {
Binary encrypted = (Binary) eventObject.get(key);
BsonBinary bsonBinary = new BsonBinary(encrypted.getData());
BsonValue decrypted = this.encryptionUtil.decryptText(bsonBinary);
eventObject.put(key, decrypted.asString().getValue());
}
}
super.onAfterLoad(event);
}
}
Use MongoDB GridFsTemplate to save, retrieve and delete the binary files. https://www.youtube.com/watch?v=7ciWYVx3ZrA&t=1267s

Spring boot elastic search repository

I have several indices where I save products:
product-example1
product-example2
product-example3
product-example4
product-example5
I have the a document in elastic search that has the same structure and it can used for different indices:
#Data
#org.springframework.data.elasticsearch.annotations.Document(indexName = "", type = "", createIndex = false)
public class ProductDocument {
#Id
private String id;
private String title;
private String seller;
private String releaseDate;
....
}
So basically I want to use same settings, same mappings same search service.
So I made indexName and type parametric in spring boot java, instead of creating 5 classes extending ProductDocument ?
#Autowired
private final ElasticsearchTemplate elasticsearchTemplate;
this.elasticsearchTemplate.createIndex("product-example1", loadFile("/files/settings.json"));
this.elasticsearchTemplate.putMapping("product-example1", "product-type1", loadFile("/files/mapping.json"));
this.elasticsearchTemplate.createIndex("product-example2", loadFile("/files/settings.json"));
this.elasticsearchTemplate.putMapping("product-example2", "product-type2", loadFile("/files/mapping.json"));
......
Now I want to create a ProductRepository but I don't have a class with defined index name. If I use generic class:
public interface DocumentRepository extends ElasticsearchRepository<ProductDocument, String> {
}
I get the error which is totally understable cause I created the index names in dynamic way:
lang.IllegalArgumentException: Unknown indexName. Make sure the indexName is defined. e.g #ProductDocument(indexName="foo")
So is it possible somehow to create repository for indexes that I created in dynamic way as described above, and pass the index name and type as parameter ?
Please help! I'm stuck.

spring data elasticsearch dynamic multi tenant index mismatch?

I am experimenting with spring data elasticsearch by implementing a cluster which will host multi-tenant indexes, one index per tenant.
I am able to create and set settings dynamically for each needed index, like
public class SpringDataES {
#Autowired
private ElasticsearchTemplate es;
#Autowired
private TenantIndexNamingService tenantIndexNamingService;
private void createIndex(String indexName) {
Settings indexSettings = Settings.builder()
.put("number_of_shards", 1)
.build();
CreateIndexRequest indexRequest = new CreateIndexRequest(indexName, indexSettings);
es.getClient().admin().indices().create(indexRequest).actionGet();
es.refresh(indexName);
}
private void preapareIndex(String indexName){
if (!es.indexExists(indexName)) {
createIndex(indexName);
}
updateMappings(indexName);
}
The model is created like this
#Document(indexName = "#{tenantIndexNamingService.getIndexName()}", type = "movies")
public class Movie {
#Id
#JsonIgnore
private String id;
private String movieTitle;
#CompletionField(maxInputLength = 100)
private Completion movieTitleSuggest;
private String director;
private Date releaseDate;
where the index name is passed dynamically via the SpEl
#{tenantIndexNamingService.getIndexName()}
that is served by
#Service
public class TenantIndexNamingService {
private static final String INDEX_PREFIX = "test_index_";
private String indexName = INDEX_PREFIX;
public TenantIndexNamingService() {
}
public String getIndexName() {
return indexName;
}
public void setIndexName(int tenantId) {
this.indexName = INDEX_PREFIX + tenantId;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
}
So, whenever I have to execute a CRUD action, first I am pointing to the right index and then to execute the desired action
tenantIndexNamingService.setIndexName(tenantId);
movieService.save(new Movie("Dead Poets Society", getCompletion("Dead Poets Society"), "Peter Weir", new Date()));
My assumption is that the following dynamically index assignment, will not work correctly in a multi-threaded web application:
#Document(indexName = "#{tenantIndexNamingService.getIndexName()}"
This is because TenantIndexNamingService is singleton.
So my question is how achieve the right behavior in a thread save manner?
I would probably go with an approach similar to the following one proposed for Cassandra:
https://dzone.com/articles/multi-tenant-cassandra-cluster-with-spring-data-ca
You can have a look at the related GitHub repository here:
https://github.com/gitaroktato/spring-boot-cassandra-multitenant-example
Now, since Elastic has differences in how you define a Document, you should mainly focus in defining a request-scoped bean that will encapsulate your tenant-id and bind it to your incoming requests.
Here is my solution. I create a RequestScope bean to hold the indexes per HttpRequest
how does singleton bean handle dynamic index

IllegalArgumentException using Spring Data and MongoDB

We're using MongoDB and Spring Data in our Spring Boot app. Another developer has earlier written a service, using MongoTemplate's findAndModify method, and some additional code that uses that service. It worked fine, even in production.
I've branched and went on adding some new features (completely new code) and needed to call that service. Service doesn't work for me, even though I didn't even touch the service code. Moreover, even the code the other guy wrote before (it's a REST controller calling the service) doesn't work now. On the master branch, everything works like it should.
Here's the service:
#Autowired
private MongoTemplate mongo;
public void addRetention(Date when, String userId, Platform platform) {
LocalDateTime dateTime = LocalDateTime.ofEpochSecond(when.getTime()/1000, 0, ZoneOffset.UTC);
int yyyymm = 100 * dateTime.getYear() + dateTime.getMonthValue();
Retention r = mongo.findAndModify( // the exception is coming from this line
new Query(Criteria.where("userId").is(userId).and("yyyymm").is(yyyymm)),
new Update().inc("count", 1).set("platform", platform),
new FindAndModifyOptions().upsert(true).returnNew(true), Retention.class
);
addRealTimeRetention(userId, r, yyyymm);
}
How I call the service:
retentionService.addRetention(timeService.timeToUtil(usage2.getDate()),
usage2.getUserId(), platform);
And the stack trace on PasteBin. The exception message is:
IllegalArgumentException: Target bean is not of type of the persistent entity!
EDIT: Here's the Retention.java class:
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
#CompoundIndexes({
#CompoundIndex(name = "userid_yyyymm_idx", def = "{ userId: 1, yyyymm: 1 }")
})
public class Retention {
#Id
private String id;
#Indexed
private String userId;
#Indexed
private int yyyymm;
private int count;
private Platform platform;
// setters and getters...
}
FINAL EDIT: I solved this by deleting spring devtools dependency in pom.xml. I'm not sure how that dependency has anything to do with this exception. Found the solution here. Thank you to everyone who helped out.

#JsonIgnore field in frontend UI and include it in Jsonb json serialization to store in Postgres

I need a field to be ignored in front end UI, whereas the same field will be calculated in backend and needs to get stored in Postgres DB as a Jsonb object. Other than transforming the value object into a newer one, do we have any feature in Jackon for this use case.
Test.java
public class Test {
private Integer score;
private Date dateValidated = null;
private Boolean consent = false;
private Date dateConsented;
public void setConsent(Boolean consent) {
this.consent = consent;
this.dateConsented = consent ? new Date() : null;
}
}
Based on consent, dateConsented will be set and i don't want this to be set while calling my service. I can use #JsonIgnore for this
Problem
I will store this Test as json object in postgres (Jsonb). So if i use #JsonIgnore dateConsented will be ignored in DB as well. I don't want that to happen. Any suggestions/solution for this?
Just create a resource class for yourself, and convert this class to it. finally return this resource class to frontend UI.Take a look ConverterFactory from spring.

Categories

Resources