Environment:
spring-data-mongo: 1.7.0.RC1
mongo-java-driver: 3.2.2
Document:
#Document(collection = "products")
public class Product {
#Id
private String sid;
private String name;
private Long vendor;
(...)
}
Repository:
public interface ProductRepository extends MongoRepository<Product, String> {
Product findByName(String productName);
}
My goal is to intercept any query performed on the Product collection and add a predicate or a specification without modifying the repository or the need to implement the method findByNameAndBelongsToVendorList.
I need this interceptor or aspectJ because I have multiple methods like:
Page<Product> findAll(Pageable page);
List<Product> findByCategory(String category, Pageable pageRequest);
(...)
Goal
findByName // perform a filter by name (explicit)
// and a filter by vendor (injected via inteceptor or aspecJ)
Avoid doing this
#Repository
public class ProductRepositoryCustomImpl implements ProductRepositoryCustom {
#Autowired
private MongoTemplate template;
public Product findByNameAndBelongsToVendorList(String name, List<Long> vendors, Pageable pageRequest) {
Criteria criteriaVendor = Criteria.where("vendors").in(vendors);
Query query = new Query(criteriaVendor);
query.with(pageRequest);
return template.findOne(query, Product.class);
}
}
Aspects should do the trick.
#Aspect
public class YourAspect {
#Autowired
private MongoTemplate template;
#Pointcut("execution(public * findByName(..))")
private void findByName() {
}
#Pointcut("within(com.leonel.repository.ProductRepository)")
private void repository() {
}
#Around("repository() && findByName()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name = (String) args[0];
Criteria newCriteria = YOUR NEW LOGIC HERE;
Query query = new Query(newCriteria);
return template.find(query, Your.class);
}
I would recommend against it though, as it introduces a bit of magic to your code and manipulating queries should not be a concern of aspects.
What is the reason you want to avoid having multiple find methods in your repository?
Related
I'm using jquery Datatables together with Spring JPA.
I want to create a custom Query so that my Datatable will show a list of items based on the id of a ManyToOne related object.
PS. I have obviously declared Repositories, Mapper and Entities for these DTOs, I'm just avoiding to write all the classes because I find it useless.
public class SezioniDTO {
private static final long serialVersionUID = 1L;
private long id;
private LocalDate sezDtaggiornamento;
private Comune Comune;
}
public class Comune {
private static final long serialVersionUID = 1L;
private long id;
private String comCap;
private String comCodbelfiore;
private String comCodcomune;
}
These are my classes (i use mapstruct to map the dtos from the entities).
How can i use criteria builder inside my repository and services to search for Sezionis based on Comunes id?
I'm new to QueryDSL and Specifications, i just would like to obtain something like this:
#Query("Select * from Sezioni s WHERE s.id_Comune = :id", native="true")
public DataTablesOutput <Object> findByField (#Param(value="id", input);
This is the current Service Implementation
#Service
public class SezioniServiceImpl implements SezioniService{
#Autowired
SezioniRepository repo;
#Autowired
SezioniMapper mapper;
#Autowired
SezioniSpecifications sezSpec;
#Override
public List<SezioniDTO> findAll() {
return repo.findAll().stream().map(x -> mapper.entityToDto(x, new CycleAvoidingMappingContext()))
.collect(Collectors.toList());
}
#Override
public List<SezioniDTO> findByIdComune(Long idcom){
return repo.findSezionibyIdComune(idcom).stream().map(x -> mapper.entityToDto(x, new CycleAvoidingMappingContext()))
.collect(Collectors.toList());
}
#Override
public SezioniDTO save(SezioniDTO entity) {
return null;
}
#Override
public Optional<SezioniDTO> findById(Long id) {
// TODO Auto-generated method stub
return null;
}
#Override
public void delete(SezioniDTO entity) {
// TODO Auto-generated method stub
}
#Override
public void deleteById(Long id) {
// TODO Auto-generated method stub
}
#Override
public long count() {
// TODO Auto-generated method stub
return 0;
}
#Override
public DataTablesOutput<SezioniDTO> getSezioniTable(#Valid DataTablesInput input) {
return repo.findAll(input, null, null, a -> mapper.entityToDto(a, new CycleAvoidingMappingContext()) );
}
}
and the current Repository for SezioniDTO
#Repository
public interface SezioniRepository extends JpaRepository<Sezione,Long>, JpaSpecificationExecutor<Sezione>, DataTablesRepository<Sezione,Long> {
#Query(value = "SELECT * FROM db.sezione WHERE sez_com_prg = :id ORDER BY sez_numsezione", nativeQuery = true)
public List <Sezione> findSezionibyIdCom(#Param(value = "id") Long id);
}
Where Sezione is the current Entity. As you can see, it extends , and DataTablesOutput work only with Specifications, which I haven't understood at all.
I simply would like to create a method similar to the public List I have in the repo, but with a DataTablesOutput return instead.
Define Entities:
#Entity
public class Sezioni {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDate sezDtaggiornamento;
#OneToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "comune_id")
private Comune Comune;
// getters & setter are omitted
}
and
#Entity
public class Comune {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String comCap;
private String comCodbelfiore;
private String comCodcomune;
// getters & setter are omitted
}
Define repository
#Repository
public interface SezioniRepository extends JpaRepository<Sezioni, Long> {
#Query("select s from Sezioni s where s.Comune.id = :id")
List<Sezioni> findByComuneId(Long id);
}
Use (here in test)
#DataJpaTest
class SezioniRepositoryTest {
#Autowired
SezioniRepository sezioniRepository;
#BeforeEach
void setUp() {
Comune comune = new Comune();
comune.setComCap("cap42");
comune.setComCodcomune("cod43");
Sezioni sezioni = new Sezioni();
sezioni.setComune(comune);
sezioni.setSezDtaggiornamento(LocalDate.of(1970, 1, 1));
sezioniRepository.save(sezioni);
}
#Test
void test() {
List<Sezioni> sezionis = sezioniRepository.findByComuneId(1L);
assertEquals(1, sezionis.size());
assertEquals("cap42",sezionis.get(0).getComune().getComCap());
}
}
Next you can use MapStruct to map entities into DTO (if you prefer to expose DTO on your API)
Criteria Builder's advantage is to build queries dynamically upon your business login needs:
Consider next example:
#Service
public class SezioniQuery {
#PersistenceContext
private EntityManager entityManager;
List<Sezioni> select(TriFunction<CriteriaBuilder, Root<Sezioni>, CriteriaQuery<Sezioni>, CriteriaQuery<Sezioni>> builder) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Sezioni> query = criteriaBuilder.createQuery(Sezioni.class);
// SQL FROM clause
Root<Sezioni> from = query.from(Sezioni.class);
// SQL SELECT clause
CriteriaQuery<Sezioni> select = query.select(from);
// build WHERE somewhere later
CriteriaQuery<Sezioni> apply = builder.apply(criteriaBuilder, from, query);
// execute
TypedQuery<Sezioni> typedQuery = entityManager.createQuery(apply);
return typedQuery.getResultList();
}
}
^^ here we define boilerplate.
Next we can reuse it to build different queires:
// #BeforeEach void setUp() {...} omitted see prev. answer
#Test
void testEqual() {
Long id = 1L;
List<Sezioni> sezionis = sezioniQuery.select((cb, from, query) ->
// WHERE id=1
query.where(cb.equal(from.get("id"), id)));
assertEquals(1, sezionis.size());
assertEquals("cap42",sezionis.get(0).getComune().getComCap());
}
#Test
void testGreater() {
List<Sezioni> sezionis = sezioniQuery.select((cb, from, query) ->
// WHERE id > 0
query.where(cb.gt(from.get("id"), 0)));
assertEquals(1, sezionis.size());
assertEquals("cap42",sezionis.get(0).getComune().getComCap());
}
So, using CriteriaBuilder you can build queries dynamically but this requires a bit more code, non-type-safe code.
Whereas JpaRepository extension is type-safe but non-dynamiŃ
I am new to spring.
I just tried successfully using an entity class without #Id in Spring Data JDBC
Custom query was added in my repository for retrieving data from 2 mysql tables and returning an entity having the joined table data.
If I plan to use only custom queries, am I missing anything here?
Here's my entity class without #Id or #Entity:
public class Item
{
private long id;
private String code;
private String itemName;
private String groupName;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
Repository layer:
#Repository
public interface ItemRepository extends PagingAndSortingRepository<Item, Long>
{
#Query("SELECT a.id, a.code, a.name AS item_name,
b.name as group_name from item a, item_group b
WHERE a.group_id = b.id AND a.id=:id")
Item findItemById(#Param("id") Long id);
}
Service layer:
#Service
public class ItemServiceImpl implements ItemService
{
private final ItemRepository itemRepository;
public ItemServiceImpl(ItemRepository itemRepository)
{
this.itemRepository = itemRepository;
}
#Override
#Transactional(readOnly=true)
public Item findItemById(Long id)
{
return itemRepository.findItemById(id);
}
}
My updated main Configuration class in response to answer of Jens:
#SpringBootApplication
#EnableJdbcRepositories
public class SpringDataJdbcApplication extends AbstractJdbcConfiguration
{
public static void main(String[] args)
{
SpringApplication.run(SpringDataJdbcApplication.class, args);
}
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
return dataSourceBuilder.build();
}
#Bean
NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource)
{
return new NamedParameterJdbcTemplate(dataSource);
}
#Bean
PlatformTransactionManager transactionManager()
{
return new DataSourceTransactionManager(dataSource());
}
}
If you don't get any exceptions you should be fine. There shouldn't be anything in Spring Data JDBC that silently breaks when the id is not specified.
The problem is though: I don't consider it a feature that this works, but just accidental behaviour. This means it might break with any version, although replacing these methods with custom implementations based on a NamedParameterJdbcTemplate shouldn't be to hard, so the risk is limited.
The question though is: Why don't you add the #Id annotation, after all your entity does have an id. And the whole idea of a repository conceptually requires an id.
If it's working and you really don't want to use the annotations, you can do it. But I think that it's unnecessary complication. You can expect errors that would not be there if you had used the annotations and code will be harder to debug. If you are new in Spring I recommend to use annotations. But after all it depend on you how will you design your applications. For sure advantage of approach without annotations is higher control about database.
I'm trying to make sortable/pageable/filterable repository with multiple filter methods. This is how my relevant code looks right now:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", length = 50, nullable = false)
private String name;
The repository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> ,
QuerydslPredicateExecutor<User> {
}
And the controller:
#RequestMapping(path="/test")
public #ResponseBody ResponseEntity<Object> foo
( #QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
return userRepository.findAll(predicate,pageable);
}
It is working perfectly fine, like this:
/users/?page=0&limit=1&sort=name,ASC&name=testuser
But i can't use any other filter method except equals like "name=testuser"
I was searching around and i keep finding guides like this but i'd have to write a PathBuilder for every entity and the controller looks way uglier too.
Is there a way to work around this and keep everything simplified like now? I need the basic operators like eq,neq,gte,lte,like, etc...
Generally I use the CriteriaBuilder API. And it gives me a small solution, all you need to do is subscribe the repository to your custom spec.
public class CustomerSpecification implements Specification<CustomerDetail> {
private C2Criteria criteria;
public static CustomerSpecification of(C2Criteria criteria) {
return new CustomerSpecification(criteria);
}
private CustomerSpecification(C2Criteria criteria) {
this.criteria = criteria;
}
#Override
public Predicate toPredicate
(Root<CustomerDetail> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return getPredicate(root, builder, criteria);
}
}
public <T> Predicate getPredicate(Root<T> root, CriteriaBuilder builder, C2Criteria criteria) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
return builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase("<")) {
return builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType().equals(String.class)) {
return builder.like(
root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
And my criteria class is:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class C2Criteria {
private String key;
private String operation = ":";
private Object value;
}
And my JpaRepository looks like:
public interface CustomerDetailRepository extends JpaRepository<CustomerDetail, Long>, JpaSpecificationExecutor<CustomerDetail> {
}
In your controller you can use it by getting the object from the queryString.
#GetMapping(value = "renew")
public ResponseEntity renew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Page<InsuranceRenew> renews = this.insuranceService.getRenew(page, criteria);
return ResponseEntity.ok(renews);
}
and the insuranceservice method looks like:
#Override
public Page<InsuranceRenew> getRenew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Pageable pageable = PageRequest.of(page.getPage(), page.getSize(), new Sort(page.getSort(), page.getOrderBy()));
InsuranceRenewSpecification specification = InsuranceRenewSpecification.of(criteria);
return this.renewRepository.findAll(specification, pageable);
}
You can see that I used a PageDto class, which is just a POJO with some fields for pagination purposes and it is defined as:
#Data
public class PageDto {
private int page;
private int size = 10;
private Sort.Direction sort = Sort.Direction.DESC;
private String orderBy = "id";
}
As you can see, I used to use the id as the default order by to prevent no wanted exceptions and de order DESC as default.
Hope it helps.
I have simple procedure which lists out users. I am using #NamedStoredProcedureQueries for procedure declaration and used EntityManager.createNamedStoredProcedureQuery for StoredProcedureQuery.
It returns the result properly but I need column name so that I will know which value is for which column.
My code goes something like this
Entity Class
#Entity
#NamedStoredProcedureQueries({ #NamedStoredProcedureQuery(name =
"sGetUserList", procedureName = "sGetUserList", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "user_id", type =
Integer.class) })
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String email;
//getters and setters
}
Custom Repositoty
public interface UserRepositoryCustom {
List<?> testProc() ;
}
Repository
public interface UserRepository extends JpaRepository<User, Long>,
UserRepositoryCustom{
}
Repository Implementation
public class UserRepositoryImpl implements UserRepositoryCustom{
#PersistenceContext
EntityManager em;
public List<Object> testProc() {
StoredProcedureQuery q = em.createNamedStoredProcedureQuery("sGetUserList");
q.setParameter("user_id", 1);
List<Object> res = q.getResultList();
return res;
}
}
I need result with column names.
You can get the column names along with their values in a Map. i.e Map<'column-name', value>.
Query query = entityManager.createNativeQuery("{call <<Your procedure>>}");
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
This will be very helpful in a places where you want to use column names as a placeholder in HTML where value will replace it in runtime.
Here , I have written a method from you can get JSON from you can get key value pair of column and value
#Transactional
#Component
public class CustomRepository<T> {
#Autowired
private EntityManager em;
private ObjectMapper mapper = new ObjectMapper();
public List<T> getResultOfQuery(String argQueryString,Class<T> valueType) {
try {
Query query = em.createNativeQuery(argQueryString);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
List<T> resultList = result.stream()
.map(o -> {
try {
return mapper.readValue(mapper.writeValueAsString(o),valueType);
} catch (Exception e) {
ApplicationLogger.logger.error(e.getMessage(),e);
}
return null;
}).collect(Collectors.toList());
return resultList;
} catch (Exception ex) {
ApplicationLogger.logger.error(ex.getMessage(),ex);
throw ex;
}
}
}
The Only condition is that your query and pojo attribute name should be same
I'm not sure I understand what you are trying to do here. If you want to get all the users using Spring data you shouldn't be implementing your UserRepository. Spring Data does this for you.
In fact the JpaRepository already has the method you need.
List<User> findAll();
You can just call this to get a list of all your users and won't need to worry about the column names.
Just inject your repository where you need it and call the method to get all users:
#Autowire
UserRepository userRepository;
List<Users> allUsers = userRepository.findAll();
EDIT: If there is a particular reason you want to use stored procedures though there is a Spring Data way of doing this without implementing UserRepository yourself. You can do this by defining the following method:
public interface UserRepository extends JpaRepository<User, Long>{
#Procedure(name = "sGetUserList")
List<User> sGetUserList(#Param("user_id") Integer userId);
}
Again there shouldn't be any issue with resolving column names with this method.
I am new to setting up embedded Elasticsearch into my Spring Boot application where Spring Data JPA is setup with Postgres database.
Now I've also added support for Elastic Search Spring Data repositories (or so I thought). The problem is that ES searches do not return anything (JSON array is empty), whilst JPA ones work correctly.
I read that people need an index tool that runs every now and then, but I couldn't find anything related to this in the Spring Data Elastic Search documents.
Do I understand correctly that you need to index the searches from the database constantly? Is the answer provided in this topic Batch indexing Spring Data JPA entries to Elastic through Spring Data ElasticSearch the only solution:
Here is the Application class:
#SpringBootApplication
#EnableJpaRepositories(basePackages = "eu.deniss.repository")
#ComponentScan
public class SpringDataElasticsearchDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataElasticsearchDemoApplication.class, args);
}
}
Person Entity class:
#Entity
#Document(indexName = "person", type = "person")
public class Person {
private Long id;
private String firstName;
private String lastName;
private String email;
private String gender;
private String ipAddress;
#Id
#org.springframework.data.annotation.Id
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
...
}
PersonSearchRepository class:
public interface PersonSearchRepository extends ElasticsearchRepository<Person, Long> {
}
PersonServiceImpl class:
#Service
public class PersonServiceImpl implements PersonService {
private final PersonRepository personRepository;
private final PersonSearchRepository personSearchRepository;
private final PersonMapper personMapper;
private static final Logger log = Logger.getLogger(PersonServiceImpl.class);
public PersonServiceImpl(PersonRepository personRepository, PersonSearchRepository personSearchRepository, PersonMapper personMapper) {
this.personRepository = personRepository;
this.personSearchRepository = personSearchRepository;
this.personMapper = personMapper;
}
...
#Override
#Transactional(readOnly = true)
public Page<PersonDTO> search(String query, Pageable pageable) {
log.info("Request to search for a page of People with a query " + query);
Page<Person> result = personSearchRepository.search(queryStringQuery(query), pageable);
return result.map(person -> personMapper.personToPersonDTO(person));
}
}
PersonController class:
#RestController()
#RequestMapping("/api")
public class PersonController {
private final PersonService personService;
private final Logger log = LoggerFactory.getLogger(PersonController.class);
private static final String ENTITY_NAME = "person";
public PersonController(PersonService personService) {
this.personService = personService;
}
#GetMapping("/_search/people")
public ResponseEntity<List<PersonDTO>> searchPeople(#RequestParam String query, Pageable pageable) throws URISyntaxException {
log.info("REST request to search for a page of Appointments for query {} ", query);
Page<PersonDTO> page = personService.search(query, pageable);
HttpHeaders headers = PaginationUtil.generateSearchPaginationHttpHeaders(query, page, "/api/_search/people");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
}
The answer was quite simple.
I had to do a batch job of querying current entities from a database and saving them to Elastic Search by using
personSearchRepository.save(person);