Getting data from multiple tables in spring jpa - java

So far I am using it like below to get data from multiple tables:
#RequestMapping(path = "/get_data_on_login", method = RequestMethod.GET)
public ResponseEntity get_data_on_login(#RequestParam(value="username") String username) throws Exception {
List<TopicBean> topics = topicService.findAllTopics();
UserInfo role = userInfoService.findRoleByUsername(username);
return new ResponseEntity(new LoginDataBean( topics, role.getRole(), role.getUserImage()), HttpStatus.OK);
}
but as the requirement to get data from more number of table increases, its getting complicated.
What I am looking for is to get data like this but I am not sure if this is correct:
public interface LoginRepository extends JpaRepository<LoginDataBean, Long>{
List<LoginDataBean> SearchByTopicBeanAndCommentBeanAndCommentLikes(TopicBean topicBean, CommentBean commentBean, CommentLikes commentLikes);
}
I have LoginDataBean like this:
public class LoginDataBean {
#Autowired
private final List<TopicBean> topics;
private String userImage;
private String role;
public LoginDataBean(List<TopicBean> topics, String role, String userImage){
this.topics = topics;
this.role = role;
this.userImage = userImage;
}
public String getRole(){
return role;
}
public String userImage(){
return userImage;
}
public List<TopicBean> getTopics() {
return topics;
}
}

Related

Cloudinary image upload not persisting image and returning null value in spring boot app

I'm working on a spring boot ecommerce app that requires cloudinary to persist image and get using the url.
However, all effort to get this done has been proved abortive. The code is not throwing any error but its not persisting in the cloudinary page and the database. And the response is null.
This is a response for example. Meanwhile i expect a link in the form of String
{
"productName": "Track suit",
"price": 300,
"productDescription": "XXL",
"productImage1": "",
"productImage2": "",
"productImage3": ""
}
This is my code
ENTITY
#Entity
public class Product {
#Id
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#Column
private String productName;
#Column
private double price;
#Column
private String productDescription;
#Column(nullable = false)
private String productImage1;
#Column(nullable = true)
private String productImage2;
#Column(nullable = true)
private String productImage3;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
#ManyToOne
#JoinColumn(name = "admin_id")
private Admin admin;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
#ManyToOne
#JoinColumn(name = "users_entity_id")
private UsersEntity usersEntity;
}
REQUEST DTO
#Data
public class UploadProductRequestDto {
private String productName;
private double price;
private String productDescription;
private MultipartFile productImage1;
private MultipartFile productImage2;
private MultipartFile productImage3;
}
RESPONSE DTO
#Data
public class UploadProductResponseDto {
private String productName;
private double price;
private String productDescription;
private String productImage1;
private String productImage2;
private String productImage3;
}
REPOSITORY
public interface ProductRepository extends JpaRepository<Product,String> {
Optional<Product> findByProductName(String productName);
}
SERVICE
public interface ProductService {
UploadProductResponseDto uploadProducts(UploadProductRequestDto uploadProductRequestDto, String categoryName) throws AuthorizationException, GeneralServiceException, ImageUploadException;
}
SERVICEIMPL
#Slf4j
#Service
public class ProductServiceImpl implements ProductService {
#Autowired
CloudStorageService cloudStorageService;
#Autowired
AdminRepository adminRepository;
#Autowired
CategoryRepository categoryRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
ModelMapper modelMapper;
#Autowired
UserPrincipalService userPrincipalService;
#Autowired
UserRepository userRepository;
#Autowired
ProductRepository productRepository;
#Override
public UploadProductResponseDto uploadProducts(UploadProductRequestDto uploadProductRequestDto, StringcategoryName) throws AuthorizationException, GeneralServiceException, ImageUploadException {
Optional<Category> checkCategory = categoryRepository.findByCategoryName(categoryName);
if (checkCategory.isEmpty()){
throw new AuthorizationException(CATEGORY_NOT_RECOGNIZED);
}
Product product = new Product();
product=mapAdminRequestDtoToProduct(uploadProductRequestDto,product);
productRepository.save(product);
UploadProductResponseDto adminUploadProductResponseDto = packageAdminProductUploadResponseDTO(product);
return adminUploadProductResponseDto;
}
private UploadProductResponseDto packageAdminProductUploadResponseDTO(Product product){
UploadProductResponseDto uploadProductResponseDto=new UploadProductResponseDto();
modelMapper.map(product,uploadProductResponseDto);
return uploadProductResponseDto;
}
private Product mapAdminRequestDtoToProduct(UploadProductRequestDto uploadProductRequestDto,Product product) throws ImageUploadException {
modelMapper.map(uploadProductRequestDto,product);
product=uploadProductImagesToCloudinaryAndSaveUrl(uploadProductRequestDto,product);
product.setId("Product "+ IdGenerator.generateId());
return product;
}
private Product uploadProductImagesToCloudinaryAndSaveUrl(UploadProductRequestDto uploadProductRequestDto,Product product) throws ImageUploadException {
product.setProductImage1(imageUrlFromCloudinary(uploadProductRequestDto.getProductImage1()));
product.setProductImage2(imageUrlFromCloudinary(uploadProductRequestDto.getProductImage2()));
product.setProductImage3(imageUrlFromCloudinary(uploadProductRequestDto.getProductImage3()));
return product;
}
private String imageUrlFromCloudinary(MultipartFile image) throws ImageUploadException {
String imageUrl="";
if(image!=null && !image.isEmpty()){
Map<Object,Object> params=new HashMap<>();
params.put("public_id","E&L/"+extractFileName(image.getName()));
params.put("overwrite",true);
try{
Map<?,?> uploadResult = cloudStorageService.uploadImage(image,params);
imageUrl= String.valueOf(uploadResult.get("url"));
}catch (IOException e){
e.printStackTrace();
throw new ImageUploadException("Error uploading images,vehicle upload failed");
}
}
return imageUrl;
}
private String extractFileName(String fileName){
return fileName.split("\\.")[0];
}
}
Controller
#Slf4j
#RestController
#RequestMapping(ApiRoutes.ENMASSE)
public class ProductController {
#Autowired
ProductService productService;
#PostMapping("/upload-product/categoryName")
public ResponseEntity<?> UploadProduct(#ModelAttribute UploadProductRequestDto UploadProductRequestDto,#RequestParam String categoryName){
try{
return new ResponseEntity<>
(productService.uploadProducts(UploadProductRequestDto,categoryName), HttpStatus.OK);
}catch (Exception exception){
return new ResponseEntity<>(exception.getMessage(),HttpStatus.BAD_REQUEST);
}
}
}
CLOUD
cloudConfig
#Component
#Data
public class CloudinaryConfig {
#Value("${CLOUD_NAME}")
private String cloudName;
#Value("${API_KEY}")
private String apikey;
#Value("${API_SECRET}")
private String secretKey;
}
CloudConfiguration
#Component
public class CloudinaryConfiguration {
#Autowired
CloudinaryConfig cloudinaryConfig;
#Bean
public Cloudinary getCloudinaryConfig(){
return new Cloudinary(ObjectUtils.asMap("cloud_name",cloudinaryConfig.getCloudName(),
"api_key",cloudinaryConfig.getApikey(),"api_secret",cloudinaryConfig.getSecretKey()));
}
}
CloudinaryStorageServiceImpl
#Service
public class CloudinaryStorageServiceImpl implements CloudStorageService{
#Autowired
Cloudinary cloudinary;
#Override
public Map<?, ?> uploadImage(File file, Map<?, ?> imageProperties) throws IOException {
return cloudinary.uploader().upload(file,imageProperties);
}
#Override
public Map<?, ?> uploadImage(MultipartFile multipartFile, Map<?, ?> imageProperties) throws IOException {
return cloudinary.uploader().upload(multipartFile.getBytes(),imageProperties);
}
}
CloudStorageService
public interface CloudStorageService {
Map<?,?> uploadImage(File file, Map<?,?> imageProperties) throws IOException;
Map<?,?> uploadImage(MultipartFile multipartFile, Map<?, ?> imageProperties) throws IOException;
}
You didn't include the implementation of cloudStorageService.uploadImage(?,?) in the code you pasted here.
Cloudinary's Java implementation requires you pass in the multipart file in bytes. I do not know if you have that since your upload method isn't here.
Maybe refer to the simple implementation here
PS: You can clone the repo to see the implementation of the upload method in the CloudinaryServiceImpl.
It happens that there is nothing wrong with my code. The issue is that it has to be coupled with the frontend so the image tag can render it. Thank you.

How to turn a String into ArrayList<Details>?

The browser sends the following object to the backend:
Now I would like to store the data in my database. So I have an entity that looks like this:
#Entity
public class NewQuote {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String description;
#ElementCollection(targetClass = Details.class)
private List<Details> details = new ArrayList<>();
public NewQuote(String description, ArrayList<Details> details) {
this.description = description;
this.details = details;
}
#Embeddable
public class Details {
private String description;
private String label;
public Details(String description, String label) {
this.description = description;
this.label = label;
}
public Details() {}
}
Controller
#PostMapping(value = "/save-quote", produces = MediaType.APPLICATION_JSON_VALUE)
public void saveQuote(#RequestBody Map<String, String> data) {
newQuoteService.saveQuote(data);
}
Service
public void saveQuote(Map<String, String> data) {
JSONObject json = new JSONObject(data);
NewQuote newQuote = new NewQuote(
json.getAsString("description"),
json.getAsString("details")
);
newQuoteRepository.save(newQuote);
}
I am getting an error because json.getAsString("details") should not be a string of course. So how can I turn it to ArrayList<Details>?
Add a DTO to manage your json response. You don't need to explicitly use JSONObject because spring already manage the process of mapping under the wood with Jackson.
Also, it is not a good practice to pass your Entities directly into the controller methods.
NewQuoteDto.java
public class NewQuoteDto {
private Long id;
private String description;
private List<Details> details = new ArrayList<>();
public NewQuoteDto() {
}
// getter and setter or you can use lombok
}
DetailDto.java
public class DetailDto {
private String description;
private String label;
public DetailDto() {}
// getter and setter or you can use lombok
}
Controller
#PostMapping(value = "/save-quote", produces = MediaType.APPLICATION_JSON_VALUE)
public void saveQuote(#RequestBody NewQuoteDto dataDto) {
// here you map dataDto to your model before you call saveQuote
NewQuote data = mapper.map(dataDto, NewQuote.class); // if you use ModelMapper library.
newQuoteService.saveQuote(data);
}
For custom mapping take look here.

Spring Boot server error while POST with Retrofit2, only happens when there are relationships in the database

I POST some data with Retrofit2 to a Spring Boot REST service and there are a lot of exceptions occurring in the server. This happens when I have relations in my database.
I have a REST service over a Spring Boot application that runs over the Heroku services, I was doing a login and a register tasks with an Android application, I am using Retrofit2 in Android to POST the data to the REST service, everything was working well until for some other reasons I create a relationship between users in my database, this relationship is a "follow", this is, create a relationship in a follow table where I have the ID of the user that is following and an ID of the user that is followed. When I create this relationship into the database and I try to login with the method that I created, I got a bunch of errors into the REST service that I do not know why is this happening.
So in Android I have the call of my Retrofit2 client and a method that creates the service passing as a parameter the UserService.class with the HTTP methods. I also pass as a parameter the user of the class User where have the information that I want to POST, then I call the enqueue method.
RetrofitClient.createService(UserService.class).login(user).enqueue(new CallBack<User>(){
#Override
public void onResponse(Call<User> call, Response<User> response) {
//Some logic here
}
});
Into my UserService.java I have the method that POST the user object information.
public interface UserService {
#POST("login")
public Call<User> login(#Body User user);
}
Now in the backend side I have a REST controller where I have the login endpoint that will be consumed for Retrofit2.
#PostMapping(path = "login", consumes = "application/json", produces = "application/json")
#CrossOrigin(origins = "*", methods= {RequestMethod.GET,RequestMethod.POST})
public Object login(#RequestBody String json) {
//Some logic here
}
As I said this endpoint runs fine when there are no relationships over a user into the DB, but when a user follow another one, this is, when there is a new row into the follow table, lets say:
follow table:
id_follow id_user_follower id_user_following
1 1 2
At the example above the user 1 follows the user 2, and when I try to login, this is, use the login method in the UserService class it throws me a bunch of errors.
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
And this repeat over 300 lines of errors.
The thing here is that between the bunch of error the server return a 200 HTTP response, I managed the exceptions in Spring Boot and I catch that, when I catch I send a code for an error to my Android Retrofit2 client, but the login does not work.
Expected result:
After sending the POST from Retrofit2 to Spring Boot the response have to be a HTTP 200 response but no exceptions have to happen into the server.
Actual result:
There is a 200 HTTP response from the server but there are a lot of exceptions into the server that return an error code to the Android application and the login does not work.
This is the entity that I want to return as JSON from the RestController in Spring Boot.
#Entity
#Table(name = "users")
public class User extends AuditModel{
private static final long serialVersionUID = 1700575815607801150L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idUser;
private String name;
private String lastName;
#Column(name = "nick_name", unique = true)
private String nickName;
private String avatarResource;
#Column(unique=true)
private String email;
private String password;
private String birthDate;
private String gender;
private String postalCode;
private int active;
public Long getIdUser() {
return idUser;
}
public void setIdUser(Long idUser) {
this.idUser = idUser;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getAvatarResource() {
return avatarResource;
}
public void setAvatarResource(String avatarResource) {
this.avatarResource = avatarResource;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthDate() {
return birthDate;
}
public void setBirthDate(String birthDate) {
this.birthDate = birthDate;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public Long getId() {
return idUser;
}
public void setId(Long idUser) {
this.idUser = idUser;
}
public int getActive() {
return active;
}
public void setActive(int active) {
this.active = active;
}
/* Relations */
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> comments;
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserMemory> userMemories;
public List<UserMemory> getUserMemories() {
return userMemories;
}
public void setUserMemories(List<UserMemory> userMemories) {
this.userMemories = userMemories;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Vote> votes;
public List<Vote> getVotes() {
return votes;
}
public void setVotes(List<Vote> votes) {
this.votes = votes;
}
#OneToMany(mappedBy = "userFollower", fetch = FetchType.LAZY)
private List<Follow> usersFollowers;
public List<Follow> getUsersFollowers() {
return usersFollowers;
}
public void setUsersFollowers(List<Follow> usersFollowers) {
this.usersFollowers = usersFollowers;
}
#OneToMany(mappedBy = "userFollowing", fetch = FetchType.LAZY)
private List<Follow> usersFollowing;
public List<Follow> getUsersFollowing() {
return usersFollowing;
}
public void setUsersFollowing(List<Follow> usersFollowing) {
this.usersFollowing = usersFollowing;
}
}
By having fetchtype.LAZY, some of the values won't exist during serialization. This will make the ObjectMapper to try to fetch these and it all will end up in some kind of infinite loop.
It is never recommended to serialize #Entity annotated classes because database tables can change and that in turn will change the API for the calling clients.
Best way is to have specific ResponseDTOs that you transfer your data to before serialization so that the API and the database tables can change without breaking anything.
So, the problem was that in the backend I was using the writeValueAsString of the ObjectMapper class like this.
public Object register(#RequestBody String json){
User user = new User();
user.set...
...
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(user);
}
For some reason the writeValueAsString method gives a StackOverFlowException for a recursive behavior when there is a relationship into the database, this is a problem related with JPA or Hibernate, I am not sure about which one of those.
The solution was to write my own method to build a JSONObject with the information of my POJO:
public String buildUserJSON(User user) {
JSONObject userJson = new JSONObject();
userJson.put("idUser", user.getIdUser());
...
return userJson.toString();
}
And then call this method in my RestController to build the JSON that I want to return. As I said I do not know what was the problem but at least this was the solution for me.
Note: I found the solution to this be cause in the past I was with a problem similar like this one but when I was trying to return a List<T> as a JSON, so, I though that was related with the same thing.
Regards.
The problem was with Jackson, so to get out of this problem you most use two annotations in the relations of your entities.
More information, please see the next link: Here is the answer
Hope it can help for anyone.

How to make relational mapping between entities using mongoDB in sprinboot?

I am trying to create a springboot application using MongoDB and a Rest controller and connect objects together using DBRef instead of classic Jpa annotations like OneToMany etc. The purpose is to print all the bookmarks for a specific account. The list of bookmarks is found by the username but it seems that it doesn't work.
These are my classes:
#Document
public class Account {
#DBRef
private Set<Bookmark> bookmarkSet = new HashSet<>();
#Id
private String id;
#JsonIgnore
private String username;
private String password;
public Account(String username, String password) {
this.username = username;
this.password = password;
}
public void setBookmarkSet(Set<Bookmark> bookmarkSet) {
this.bookmarkSet = bookmarkSet;
}
public String getId() {
return id;
}
}
#Document
public class Bookmark {
#DBRef
#JsonIgnore
private Account account;
#Id
private String id;
private String uri;
private String description;
public Bookmark(Account account, String uri, String description) {
this.account = account;
this.uri = uri;
this.description = description;
}
public Account getAccount() {
return account;
}
public String getId() {
return id;
}
public String getUri() {
return uri;
}
public String getDescription() {
return description;
}
}
repositories:
public interface AccountRepository extends MongoRepository<Account, Long> {
Optional<Account> findOneByUsername(String username);
}
public interface BookmarkRepository extends MongoRepository<Bookmark, Long> {
Collection<Bookmark> findByAccountUsername(String username);
}
And RestController:
#RestController
#RequestMapping("/{userId}/bookmarks")
public class BookmarkRestController {
private final AccountRepository accountRepository;
private final BookmarkRepository bookmarkRepository;
#Autowired
public BookmarkRestController(AccountRepository accountRepository, BookmarkRepository bookmarkRepository) {
this.accountRepository = accountRepository;
this.bookmarkRepository = bookmarkRepository;
}
#RequestMapping(value = "/{bookmarkId}", method = RequestMethod.GET)
Bookmark readBookmark(#PathVariable String userId, #PathVariable Long bookmarkId) {
this.validateUser(userId);
return bookmarkRepository.findOne(bookmarkId);
}
#RequestMapping(method = RequestMethod.GET)
Collection<Bookmark> readBookmarks(#PathVariable String userId) {
this.validateUser(userId);
return this.bookmarkRepository.findByAccountUsername(userId);
}
private void validateUser(String userId) {
this.accountRepository.findOneByUsername(userId).orElseThrow(() -> new UserNotFoundException(userId));
}
}
After I run the application I get this error:
Invalid path reference account.username! Associations can only be pointed to directly or via their id property!
I'm not sure you have the right schema design. I assume you've modeled you objects based on a relational database type model, where the data is normalised and data is split across multiple tables, with relationships captured using Ids. With MongoDB you can structure and store your data with the heirarchy simply contained in within the one document.
So in your example the Bookmark would not be a Document itself, but would be a sub document of the Account. Remove the #Document annotation from the Bookmark object, and the #DBRef annotations, and simply store the Bookmarks within the Account document.
This would give you a schema more like this:
{
"_id": 1,
"bookmarkSet": [
{
"uri": "http://www.foo.com",
"description": "foo"
},
{
"uri": "http://www.bar.com",
"description": "bar"
}
],
"username": "John",
"password": "password"
}
*Note: if you make the bookmarks sub documents you can remove the _id member from the Bookmark object
The best design will depend on how many bookmarks you expect each account to have. If its only a few bookmarks then what I suggested would work well. If you have thousands then you might want to structure it differently. There are lots of articles about schema design in NoSQL database. This one covers the options for embedding subdocuments quite well:
http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1

How to delete record in MongoDB using Spring Data

I want to delete an record based on Id in Spring.
but in database id value is object
EX:-
id: Object(34562341112313)
How to delete this record in Spring?
You do like this:
public void deleteRecord() {
MongoOperations mongoOperation = (MongoOperations) ctx.getBean("mongoTemplate");
Query searchQuery = new Query(Criteria.where("id").is(34562341112313));
mongoOperation.remove(searchQuery, Your_entity_class.class);
logger.info("Delete success");
}
This is my realistic example:
/**
* Delete by condition(s).
*/
public void deleteJob() {
MongoOperations mongoOperation = (MongoOperations) ctx.getBean("mongoTemplate");
Query searchQuery = new Query(Criteria.where("company").is("DCV"));
mongoOperation.remove(searchQuery, Job.class);
logger.info("Đã xóa các công việc đăng bởi DCV.");
}
Source: https://github.com/SmartJobVN/MongoDB_SpringDataMongo/blob/master/src/main/java/vn/smartJob/jobs/MongoSpringJavaConfigApplication.java#L132
Reference: http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/
You should delete it like this:
#Repository
public class AppDaoClass{
#Autowired
MongoTemplate mongoTemplate;
#Override
public void deleteSomething(String somethingId) {
mongoTemplate.remove(Query.query(Criteria.where("somethingId").is(somethingId)), Ticket.class);
}
}
The first "somethingId" is the name you gave it in your model, and the second somethingId is for the Parametar you are giving in you method.
And your Domain Model:
#Document
public class Model {
#Id
private String somethingId;
private String someName;
private String someOtherName;
}
Be sure to user proper annotations for your classes #Document and #Repository. And add an #Id annotation to your ID field.
Hope this helps.
This is the way you can delete records in spring data mongoDB using MongoTemplate
WriteResult writeResult=mongoTemplate.remove(query,"collection_name");
OR
WriteResult writeResult=mongoTemplate.remove(query,EntityClassName.class);
You can also use repository Pattern
#Document(collection = "user")
public class User {
#Id
private String id;
private String username;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
#Repository
public interface UserRepository extend MongoRepository<User, String>{
public void delete(String id);
public void delete(User user);
public void deleteByUsername(String username);
}
you can use these method anywhere to delete records also u can write your custom methods
#Query(value = "{'_id' : ?0}", delete = true)
void deleteById(String id);

Categories