How do you save files for entity in Spring Data Rest? - spring

In a regular entity for SDR, it takes care of all properties of an entity for you saving it to the database. But how do you handle files?
#Entity
public class User {
String name;
Set<File> myfiles; //how can I make this work?
}
#RepositoryRestResource
public interface UserRepository extends JpaRepository<User, Long> {}
How can I make it so that a User owns a list of files, can upload and download them?

This is not really possible with Spring Data/REST as it focusses on structured data; i.e. tables and associations, for the most part.
#Lob is problematic as it forces you to store your content in the database which isn't necessarily where you want to store it. The file-system or S3 might be better for example.
byte[] is also problematic if you have very large files as you will likely cause OutOfMemoryExceptions.
Instead, there is a community project called Spring Content that addresses exactly the problem you are trying to solve.
Spring Content provides the same programming paradigms as Spring Data/REST for unstructured data; i.e. images, documents, movies, etc. So, using this project you can associate one, or in your case, many "content" objects with your Spring Data entities and manage them over HTTP just like you do with your Spring Data Entities too.
Its pretty simple to add to your project, as follows:
pom.xml (boot starters also available)
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs</artifactId>
<version>1.0.0.M4</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>1.0.0.M4</version>
</dependency>
Configuration
#Configuration
#EnableFilesystemStores
#Import("org.springframework.content.rest.config.RestConfiguration.class")
public class ContentConfig {
#Bean
FileSystemResourceLoader fileSystemResourceLoader() throws IOException {
return new FileSystemResourceLoader(new File("/path/to/uploaded/files").getAbsolutePath());
}
}
To associate content, modify your User entity as follows:
#Entity
public class User {
String name;
List<Image> images;
}
Add an Image entity:
#Entity
public class Image {
#ContentId
private String contentId;
#ContentLength
private long contentLength = 0L;
#MimeType
private String mimeType = "text/plain";
}
And to this add a "store" (the equivalent of a Repository but for content):
ImageStore.java
#StoreRestResource
public interface ImageStore extends FilesystemContentStore<Image, String> {}
This is all you need to create REST endpoints # /users/{userId}/images. When your application starts, Spring Content will look at your dependencies seeing Spring Content Filesystem, look at your ImageStore interface and inject a filesystem-based implementation of that interface. It will also see the Spring Content REST dependency and inject an #Controller implementation that forwards HTTP requests to your ImageStore. Just like Spring Data does for your UserRepository. This saves you having to implement any of this yourself which I think is what you are after.
So...
To manage content with the injected REST API:
curl -X POST /users/{userId}/images -F file=#/path/to/image.jpg
will store the image on the filesystem at `` and associate it with the user entity whose id is userId.
curl /users/{userId}/images/{contentId} -H "Accept: image/jpeg"
will fetch it again and so on...supports all CRUD methods and video streaming as well BTW!
There are a couple of getting started guides here. The reference guide for Spring Content Filesystem is here. And there is a tutorial video here. The coding bit starts about 1/2 way through.
A couple of additional points:
- if you use the Spring Boot Starters then you don't need the #Configuration for the most part.
- Just like Spring Data is an abstraction, so is Spring Content so you aren't limited to storing your images on the filesystem. You could store them as BLOBs in the database, or in cloud storage like S3.
HTH

I suggest you can use #Lob instead to save file data (fileData variable below)
#Entity
public class File {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String fileName;
private String fileType;
#Lob
private byte[] fileData;
}

Related

How to access Spring properties from an entity?

I have a spring app, that pushes data in an s3 bucket.
public class Ebook implements Serializable {
#Column(name= "cover_path", unique = true, nullable = true)
private String coverPath;
private String coverDownloadUrl;
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
public void init(){
// I want to access the property here
System.out.println("PostConstruct");
String coverDownloadUrl = "https://"+awsCloudFrontDns+"/"+coverPath;
}
When a data is pushed, let's say my cover here, I get the key 1/test-folder/mycover.jpg which is the important part of the future http URL of the data.
When I read the data from database, I enter inside #PostLoad method and I want construct the complete URL using the cloudfront value. This value changes frequently so we don't want to save hardly in the database.
How could I do to construct my full path just after reading the data in database?
The only way to do this is to use a service that update the data after using repository to read it? For readbyId it can be a good solution, but for reading list or using other jpa methods, this solutions won't work because I have each time to create a dedicated service for the update.
It doesn't look good for Entity to depend on property.
How about EntityListener.
#Component
public class EbookEntityListener {
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
void postload(Ebook entity) { entity.updateDns(awsCloudFrontDns); }
}
I recommend trying this way :)

Marshalling a Spring Data Jpa Projection in a rest endpoint to xml produces strange tags

When marshalling a spring data jpa projection, the xml structure is invalid. The proxy is displayed as a root xml tag, not the projection interface class name. The JSON output however is fine, but the rest client on the external system does not understand json.
The entity looks like this example - the real entity is more complicated:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String city;
private int age;
// getters, setters, ...
}
The projection interface looks like:
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name = "user-short")
public interface UserShort {
String getName();
String getCity();
}
The response from the rest endpoint shows (we need application/xml):
<.Proxy253>
<name>Alexander</name>
<city>Alexandria</city>
</.Proxy253>
The number in the proxy tag is changing. I would expect a root tag with the name of the interface or the #XmlRootElement(name = "user-short") annotation like this:
<user-short>
<name>Alexander</name>
<city>Alexandria</city>
</user-short>
We are using a interface-based Spring Data JPA projection in our project. Now we want to use it in a REST endpoint, without exposed repositories. So we can't use Spring Data Rest projections. Due to nested projections, we can't use class-based projections either. The JSON output looks like expected, but the xml output writes the proxy class as the root tag. And all XML-Annotations are ignored ( enums don't get converted to ordinals etc. ).
In a debugger when I open the variable of the interface type UserShort I see a instance of type com.sun.proxy.Proxy, some reflection stuff. There is no implementation of UserShort.
What is the prefered way to tell the marshaller to use the information from the interface and not the reflection stuff in spring boot?
Thanks for any help
What you try is not supported by the JAXB spec:
The mapping of existing Java interfaces to schema
constructs is not supported. Since an existing class
can implement multiple interfaces, there is no obvious mapping of existing interfaces to XML schema constructs.
Sou you should use Class-based Projections (DTOs) on Spring Data

Spring Data Redis Repository support does not read back embedded complex objects

I have a spring-boot application (1.4RC1, I know it's RC, but Spring Data Redis 1.7.2 is not) where I'm using spring-boot-starter-redis.
The application uses a Spring Data Repository (CrudRepository) which should save an object (using #RedisHash annotation) with String and Boolean properties and one custom class property, which also has only Strings and Longs as properties.
When I save an object (via the repository), everything went fine and I can see all the properties in the database as I would expect.
When I want to read the data from the database (via the repository) I only get the properties from the parent object. The custom class property is null.
I would expect to get the property loaded from the database as well. As the documentation states you can write a custom converter, but since I don't need to do that, when I want to write the data, I shouldn't need to write a reading converter as well.
I wonder if I need to annotate the custom class property, but I couldn't find anything in the documentation. Can you point me in the right direction?
The classes are as follows:
Class sample:
#Data
#EqualsAndHashCode(exclude = {"isActive", "sampleCreated", "sampleConfiguration"})
#RedisHash
public class Sample {
#Id
private String sampleIdentifier;
private Boolean isActive;
private Date sampleCreated;
private SampleConfiguration sampleConfiguration;
public Sample(String sampleIdentifier, SampleConfiguration sampleConfiguration){
this.sampleIdentifier = sampleIdentifier;
this.sampleConfiguration = sampleConfiguration;
}
}
Class SampleConfiguration:
#Data
public class SampleConfiguration {
private String surveyURL;
private Long blockingTime;
private String invitationTitle;
private String invitationText;
private String participateButtonText;
private String doNotParticipateButtonText;
private String optOutButtonText;
private Long frequencyCappingThreshold;
private Long optOutBlockingTime;
}
I added #NoArgsConstructor to my Sample class as Christoph Strobl suggested. Then the repository reads the SampleConfiguration correctly. Thanks, Christoph!

Multiple Repositories for the Same Entity in Spring Data Rest

Is it possible to publish two different repositories for the same JPA entity with Spring Data Rest?
I gave the two repositories different paths and rel-names, but only one of the two is available as REST endpoint.
The point why I'm having two repositories is, that one of them is an excerpt, showing only the basic fields of an entity.
The terrible part is not only that you can only have 1 spring data rest repository (#RepositoryRestResource) per Entity but also that if you have a regular JPA #Repository (like CrudRepository or PagingAndSorting) it will also interact with the spring data rest one (as the key in the map is the Entity itself).
Lost quite a few hours debugging random load of one or the other. I guess that if this is a hard limitation of spring data rest at least an Exception could be thrown if the key of the map is already there when trying to override the value.
The answer seems to be: There is only one repository possible per entity.
I ended up using the #Subselect to create a second immutable entity and bound that to the second JpaRepsotory and setting it to #RestResource(exported = false), that also encourages a separation of concerns.
Employee Example
#Entity
#Table(name = "employee")
public class Employee {
#Id
Long id
String name
...
}
#RestResource
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}
#Entity
#Immutable
#Subselect(value = 'select id, name, salary from employee')
public class VEmployeeSummary {
#Id
Long id
...
}
#RestResource(exported = false)
public interface VEmployeeRepository extends JpaRepository<VEmployeeSummary, Long> {
}
Context
Two packages in the monolithic application had different requirements. One needed to expose the entities for the UI in a PagingAndSortingRepository including CRUD functions. The other was for an aggregating backend report component without paging but with sorting.
I know I could have filtered the results from the PagingAndSorting Repository after requesting Pageable.unpaged() but I just wanted a Basic JPA repository which returned List for some filters.
So, this does not directly answer the question, but may help solve the underlying issue.
You can only have one repository per entity... however, you can have multiple entities per table; thus, having multiple repositories per table.
In a bit of code I wrote, I had to create two entities... one with an auto-generated id and another with a preset id, but both pointing to the same table:
#Entity
#Table("line_item")
public class LineItemWithAutoId {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
...
}
#Entity
#Table("line_item")
public class LineItemWithPredefinedId {
#Id
private String id;
...
}
Then, I had a repository for each:
public interface LineItemWithoutId extends Repository<LineItemWithAutoId,String> {
...
}
public interface LineItemWithId extends Repository<LineItemWithPredefinedId,String> {
...
}
For the posted issue, you could have two entities. One would be the full entity, with getters and setters for everything. The other, would be the entity, where there are setters for everything, but only getters for the fields you want to make public. Does this make sense?

Spring Data JPA querying with transitive sorting

I got a problem with simple Spring Data issue. Let's assume we got two entities.
public class Request {
// all normal stuff
#ManyToOne
private Document doc;
}
public class Document {
private Long id;
private String name;
}
Simple relation. My question is - is it possible to retrieve Request entities using Spring Data Method-DSL and sorting by Document? So what I want to achieve is to create repository method like:
public List<Request> findAllOrderByDoc()
or similar:
public List<Request> findAllOrderByDocId()
Unfortunately when I try that I am given error message saying that there is no Doc field or it cannot be mapped to long. I assume it is possible to be done using QueryDSL and predicates but I am wondering if this pretty obvious and simple thing can be done by plain Spring Data?
Yes, sure.
you need to provide the direction:
public List<Request> findAllOrderByDocAsc()
public List<Request> findAllOrderByDocDesc()

Resources