Accesing data from different collections of mongodb via springboot - mongodb

I have been trying to join two different collections of MongoDB within a Spring boot application in order to fetch the data within a single #GetMapping call.
These are my application.properties:
spring.data.mongodb.uri=mongodb://localhost:27017/alpha1
#spring.data.mongodb.uri=mongodb://username:password#host:port/database
server.port = 4000
And this is what my repository looks like:
package com.example.demo.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.Person;
#Repository
public interface PersonRepository extends MongoRepository<Person, String>{
public Person findByContId(String firstName);
public Person findByUid(String uid);
}
The problem is I need to make another collection regarding user feeds and its data should be fetched within the same method.

You need to create another repository for the same like and fetch it where ever you need it
#Repository
public interface UserFeedsRepository extends MongoRepository<UserFeeds, String>{
public List<UserFeeds> findAll();
}

Related

Add custom database session variable to Spring Boot JPA queries?

I am trying to set SET SESSION encrypt.key='some_key' to database queries or connection.
Thing is I have following column definition in my model class
#ColumnTransformer(forColumn = "first_name",
read = "pgp_sym_decrypt(first_name, current_setting('encrypt.key'))",
write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))")
#Column(name = "first_name", columnDefinition = "bytea")
private String firstName;
Above works when we set encrypt.key in postgres.conf file directly but out requirement is to have encrypt.key configurable from our spring properties file.
Things I tried.
AttributeConverter annotation with custom Converter class which only works with JPA, and LIKE operations are not supported.
I tried ContextEventListener where I executed SET SESSION query at application startup but that only works for few requests
Next I tried CustomTransactionManager extends JpaTransactionManager where I was doing following
#Override
protected void prepareSynchronization(DefaultTransactionStatus status,TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
log.info("Encrypt Key : {}", entityManager.createNativeQuery("SElECT current_setting('encrypt.key')").getSingleResult());
}
}
Above does not work when I call normal JPA Repository methods and encrypt.key is not set as the CustomTransactionManager class in not called.
Any guidance in right direction would help me a lot
Since I created CustomTransactionManager extends JpaTransactionManager
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import javax.persistence.EntityManager;
#Component
#Slf4j
#Primary
#Qualifier(value = "transactionManager")
public class CustomTransactionManager extends JpaTransactionManager {
#Autowired
private EntityManager entityManager;
#Value("${database.encryption.key}")
private String encryptKey;
#Override
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET SESSION encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
}
}
Above was not getting called when I used normal JPA Repository methods.
For example,
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByFirstName(String firstName);
}
Adding #Transactional on Repository class did override framework logic where a shared transaction was getting created behind-the-scenes for all repository beans. This resulted in my CustomTransactionManager to be called even with repository methods.
I initially thought that adding Transactional annotation was overkill but found out that it gets created automatically at framework level as well so manually adding it had no additional footprint on its own but code/query you write inside CustomTransactionManager class will add required request footprint.
So I ended up adding #Transactional annotation on all repository classes whose domain(table) had encrypted columns.
For my use-case, this was the most flexible solution to have column level encryption on Azure postgres datbase service with Spring boot because we can not add custom environment variables there from Azure Portal, and directly adding to postgres.conf file also not possible due it being a SAAS service.

Mixing Spring Data Envers and QueryDSL

I'm using a global custom repository in my project which extends QueryDslJpaRepository:
public class CustomPagingAndSortingRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements CustomPagingAndSortingRepository<T, ID> {
And the interface:
public interface CustomPagingAndSortingRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
And then on my configuration I annotate it with:
#EnableJpaRepositories(repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
All is working fine, but now I was trying to add auditing support to my entities by using spring-data-envers and according to the docs I should use a specific repository factory bean class :
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class, repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
Now obviously if I do this things won't work because my repositories will now be created through the EnversRevisionRepositoryFactoryBean class and will no longer be of CustomPagingAndSortingRepositoryImpl type.
How can I support something like this? I'm not seeing how since my custom repository need to extend from QueryDslJpaRepository already.
I think the relevant part for you is this method of EnversRevisionRepositoryFactoryBean:
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return EnversRevisionRepositoryImpl.class;
}
Here you really want your CustomPagingAndSortingRepositoryImpl returned. So I would try the following:
extend EnversRevisionRepositoryFactoryBean and overwrite getRepositoryBaseClass to return your CustomPagingAndSortingRepositoryImpl.
Make CustomPagingAndSortingRepositoryImpl extend EnversRevisionRepositoryImpl.

Spring Data Rest CrudRepository vs ReadOnlyRepository

I noticed an anomaly in the way Spring Data Rest repositories are behaving. I have two types of entities. in my application - readonly entities (for reference data like statecodes, country codes, zip codes etc.). I don't want to let the end user change these. So I implemented the following ReadOnly repository.
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
Iterable<T> findAll();
}
#Repository
public interface StateRepository extends ReadOnlyRepository<State, Long> {
}
Now, all other entities have CrudRepositories associated with them because they are editable entities like addresses which reference the states and zip codes.
Here's an example.
#Repository
public interface CustomerRepository extends CrudRepository<Address, Long> {
}
I have a controller for both readonly and editable entities, with a pass-through call to the repositories.
#RestController
#RequestMapping(value = "/addresses", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#Autowired
private AddressRepository addressRepository;
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Iterable<Address> getAllAddresses() {
return addressRepository.findAll();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Address getAddress(#PathVariable("id") Long id) {
return addressRepository.findOne(id);
}
}
I have an identical Controller corresponding to the State entity.
Funnily enough, the request to StateController gives me a HATEOAS json response, while the request to Address gives me a non HATEOAS json response. What gives?
My bad. My application server did not redeploy certain repositories. This is a non-issue.
So for those running into these issues, you are likely using hot-code replace feature of your IDE. Consider restarting your app and it should be a non-issue.

Wrapping Spring Data JPA with ApsectJ

Is it possible?
Currently I am using some aspects for my MVC controllers, what works really fine. I'm wrapping their responses and I have desired effect.
I also want to do this with Spring Data JPA repositories. But since they're generated based on the interface e.g:
public interface SomeRepository<T extends Some, ID extends Serializable> extends
BaseRepository<T, ID>, JpaSpecificationExecutor<T> {
public List<T> findById(Long id)
}
It generates me controller which is ready to use:
http://localhost:8080/findById?id=1234
I also want to wrap this controller. Is it possible?
This should work:
#Component
#Aspect
public class MyAdvice {
#Before("execution(* com.company.jpa.SomeRepository+.findById(..))")
public void intercept() { ... }
}
Basically, we are telling the framework to intercept the call to the findById method on any sub-class of SomeRepository.
Here is a sample application demonstrating this in action.

Using Spring Security ACL with Spring Data REST

I am trying to authorize apis exposed by Spring Data REST. So far I am able to do role-based authorization i.e:
#RepositoryRestResource(path = "book")
public interface BookRepository extends JpaRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN')")
<S extends Book> Book save(Book book);
}
Also in the same project i have a service layer with ACL mechanism, which is working.
I am unable to use PostFilter expression with Spring Data REST i.e:
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
List<Book> findAll();
It would be of great help, if anyone using ACL with Spring Data REST.
Note: I am aware of below open issues:
https://jira.spring.io/browse/DATAREST-236
https://jira.spring.io/browse/SEC-2409
using JpaRepository was shadowing List<Book> findAll() method. Then I used CrudRepository, and PostFilter got applied.
For more details, a sample project is available on GitHub:
https://github.com/charybr/spring-data-rest-acl
ACL-based authorization is working for below entity exposed by Spring Data REST.
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.security.access.method.P;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
#RepositoryRestResource(path = "book")
public interface BookRepository extends CrudRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#book, 'write')")
<S extends Book> Book save(#P("book") Book book);
#Override
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
Iterable<Book> findAll();
}