Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection - jpa

I have an entity class as below:
#Entity
public class UserDemo implements Serializable {
#Id
private Long id;
private String username;
private String createdBy;
#Version
private int version;
/***
*
* Getters and setters
*/
}
Using Spring Data JPA and Querydsl how do I fetch a page of UserDemo with only id and username properties populated? I need to use paging as well as searching. In short I would like to achieve the same result as
Page<UserDemo> findAll(Predicate predicate, Pageable pageable);
but with limited field of UserDemo populated.

Looks like custom repository implementation is the way to go for now until something similar available in spring data.
I have gone through http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations
Here is my implementation which works. However it would be good to have this method available directly in Spring-Data-JPA
Step 1: Intermediate interface for shared behavior
public interface CustomQueryDslJpaRepository <T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
/**
* Returns a {#link org.springframework.data.domain.Page} of entities matching the given {#link com.mysema.query.types.Predicate}.
* This also uses provided projections ( can be JavaBean or constructor or anything supported by QueryDSL
* #param constructorExpression this constructor expression will be used for transforming query results
* #param predicate
* #param pageable
* #return
*/
Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable);
}
Step 2: Implementation of intermediate interface
public class CustomQueryDslJpaRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements CustomQueryDslJpaRepository<T, ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate));
Long total = countQuery.count();
List<T> content = total > pageable.getOffset() ? query.list(factoryExpression) : Collections.<T> emptyList();
return new PageImpl<T>(content, pageable, total);
}
}
Step 3: Create a custom repository factory to replace the default
public class CustomQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomQueryDslJpaRepositoryFactory(entityManager);
}
private static class CustomQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public CustomQueryDslJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQueryDslJpaRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQueryDslJpaRepository.class;
}
}
}
Step 4: Use the custom repository factory
Using annotation
#EnableJpaRepositories(repositoryFactoryBeanClass=CustomQueryDslJpaRepositoryFactoryBean.class)
OR using XML
<repositories base-package="com.acme.repository" factory-class="com.acme.CustomQueryDslJpaRepositoryFactoryBean" />
Note: Don't place custom repository interface and implementation in the same directory as base-package. If you are placing then exclude them from scanning otherwise spring will try to create beans for them
Sample usage
public interface UserDemoRepository extends CustomQueryDslJpaRepository<UserDemo, Long>{
}
public class UserDemoService {
#Inject
UserDemoRepository userDemoRepository;
public Page<User> findAll(UserSearchCriteria userSearchCriteria, Pageable pageable) {
QUserDemo user = QUserDemo.userDemo;
return userDemoRepository.findAll(Projections.bean(UserDemo.class, user.id, user.username), UserPredicate.defaultUserSearch(userSearchCriteria), pageable);
}
}

For more recent versions of Spring Data, I couldn't get the accepted answer to work without hitting issues, but found that going down the route from the Spring Data docs, does work by revising that answer as follows:
1. The repository interface
#NoRepositoryBean
public interface QueryDslPredicateAndProjectionExecutor<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable);
}
2. The repository implementation
public class QueryDslJpaEnhancedRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements QueryDslPredicateAndProjectionExecutor<T, ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public QueryDslJpaEnhancedRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public QueryDslJpaEnhancedRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public <PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate));
Long total = countQuery.count();
List<PROJ> content = total > pageable.getOffset() ? query.list(factoryExpression) : Collections.<PROJ>emptyList();
return new PageImpl<PROJ>(content, pageable, total);
}
}
3. Setting the default repository implementation
#EnableJpaRepositories(
repositoryBaseClass=QueryDslJpaEnhancedRepositoryImpl.class,
basePackageClasses=SomeRepository.class)

For the current versions of Spring Data (1.11.1) and QueryDSL (4), you have to change the customFindWithProjection method implementation like this:
#Override
public <PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery(predicate);
JPQLQuery<PROJ> query = querydsl.applyPagination(pageable, createQuery(predicate).select(factoryExpression));
long total = countQuery.fetchCount();
List<PROJ> content = pageable == null || total > pageable.getOffset() ? query.fetch() : Collections.<PROJ> emptyList();
return new PageImpl<PROJ>(content, pageable, total);
}
The rest of the code remains the same.

I was able to achieve the same result with just a bit of code.
public class Queryable<T> extends QuerydslJpaPredicateExecutor<T> {
private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public Queryable(Class<T> domainClass, EntityManager entityManager) {
this(JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager),
entityManager);
}
private Queryable(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager, resolver, null);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<?> builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public Page<T> findAll(Expression<T> expression, Predicate predicate, Pageable pageable) {
JPQLQuery<?> countQuery = createQuery(predicate);
JPQLQuery<T> query = querydsl.applyPagination(pageable,
createQuery(predicate).select(expression));
List<T> dtos = query.fetch();
return PageableExecutionUtils.getPage(dtos, pageable, countQuery::fetchCount);
}
}
Usage:
#Repository
#Transactional
class UserDemoRepository
private static final QUserDemo q = QUserDemo.userDemo;
private static final QBean<UserDemo> PROJECTION = Projections.bean(UserDemo.class,
q.id, q.username);
#PersistenceContext
private EntityManager entityManager;
public Page<UserDemo> findAll(Predicate predicate, Pageable pageable) {
return new Queryable<UserDemo>(UserDemo.class, entityManager)
.findAll(PROJECTION, predicate, pageable);
}
}
(inspired by https://stackoverflow.com/a/53960209/1833472)

As a (albeit very ugly and inefficient) workaround, I simply retrieved a regular Page containing the entities from my repository and manually mapped them to a projection in the controller like so:
#GetMapping(value = "/columns")
public Page<ColumnProjection> getColumns(#QuerydslPredicate(root = Column.class) final Predicate predicate,
final Pageable pageable) {
Page<Column> filteredColumns = columnRepository.findAll(predicate, pageable);
List<ColumnProjection> filteredColumnProjections = new ArrayList<>();
filteredColumns.forEach(c -> filteredColumnProjections.add(new ColumnProjectionImpl(c)));
return new PageImpl<>(filteredColumnProjections, pageable, filteredColumnProjections.size());
}
Where ColumnProjectionImpl is a class implementing my ColumnProjection interface.
This was the easiest solution I could come up with while not having to adapt my existing ColumnRepository.

I've just run into the same issue myself.
In short - we have to specify custom repository factory bean, that tell to use our custom repo as another "fragment".
So i overrided factory.getRepositoryFragments to include a custom projection predicate implementation(IMHO it resolves the issue in the question No property found for type… custom Spring Data repository).
updated code, based on all previous answers :
1.QuerydslPredicateProjectionRepositoryFactory
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
import javax.persistence.EntityManager;
import java.io.Serializable;
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
public class QuerydslPredicateProjectionRepositoryFactory extends JpaRepositoryFactory {
private final EntityManager entityManager;
private EntityPathResolver entityPathResolver;
public QuerydslPredicateProjectionRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
}
#Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
boolean isQueryDslRepository = QUERY_DSL_PRESENT
&& QuerydslPredicateProjectionRepository.class.isAssignableFrom(metadata.getRepositoryInterface());
if (isQueryDslRepository) {
JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
Object querydslFragment = getTargetRepositoryViaReflection(QuerydslPredicateProjectionRepositoryImpl.class, entityInformation,
entityManager, entityPathResolver, null);
fragments = fragments.append(RepositoryFragment.implemented(querydslFragment));
}
return fragments;
}
}
2.QuerydslPredicateProjectionRepositoryFactoryBean
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class QuerydslPredicateProjectionRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {
public QuerydslPredicateProjectionRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new QuerydslPredicateProjectionRepositoryFactory(entityManager);
}
}
3.QuerydslPredicateProjectionRepository here we add new methods, that use projections etc...
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.List;
public interface QuerydslPredicateProjectionRepository<T> {
<Projection> Page<Projection> findAll(Predicate predicate, Pageable pageable, FactoryExpression<Projection> factoryExpression);
<Projection> List<Projection> findAll(Predicate predicate, Sort sort, FactoryExpression<Projection> factoryExpression);
<Projection> List<Projection> findAll(Predicate predicate, FactoryExpression<Projection> factoryExpression);
}
4.QuerydslPredicateProjectionRepositoryImpl here we implement the repository interface methods
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.CrudMethodMetadata;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.jpa.repository.support.QuerydslJpaPredicateExecutor;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import javax.persistence.EntityManager;
import java.util.List;
public class QuerydslPredicateProjectionRepositoryImpl<T> extends QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateProjectionRepository<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver, null);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager, EntityPathResolver resolver, CrudMethodMetadata metadata) {
super(entityInformation, entityManager, resolver, metadata);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public <Projection> List<Projection> findAll(Predicate predicate, FactoryExpression<Projection> factoryExpression) {
return createQuery(predicate).select(factoryExpression).fetch();
}
#Override
public <Projection> List<Projection> findAll(Predicate predicate, Sort sort, FactoryExpression<Projection> factoryExpression) {
JPQLQuery<Projection> query = createQuery(predicate).select(factoryExpression);
querydsl.applySorting(sort, query);
return query.fetch();
}
#Override
public <Projection> Page<Projection> findAll(Predicate predicate, Pageable pageable, FactoryExpression<Projection> factoryExpression) {
JPQLQuery<Projection> query = createQuery(predicate).select(factoryExpression);
querydsl.applyPagination(pageable, query);
querydsl.applySorting(pageable.getSort(), query);
QueryResults<Projection> queryResults = query.fetchResults();
return new PageImpl<>(queryResults.getResults(), pageable, queryResults.getTotal());
}
}
5.Example entity
#Entity
public class Example extends Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#Column
private String name;
#Column
private String surname;
#Column
private Integer year;
public Example() {
}
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getSurname() {return surname;}
public void setSurname(String surname) {this.surname= surname;}
public Integer getYear() {return year;}
public void setSurname(Integer year) {this.year= year;}
}
6.Example repository
#Repository
public interface ExampleRepository extends JpaRepository<Example, Long>, QuerydslPredicateProjectionRepository<Example> { }
7.Example usage
configuration :
#EnableJpaRepositories(repositoryFactoryBeanClass = QuerydslPredicateProjectionRepositoryFactoryBean.class)
typical usage :
//get list of entities only with year field value set - memory consuming
List<Example> years = repository.findAll(predicate, Projections.fields(Example.class, QExample.example.year));
//get list of tuples - looks nicer - less memory consuming
List<Tuple> years = repository.findAll(predicate, Projections.tuple(QExample.example.year));
//get list of integers - nice :)
List<Integer> years = repository.findAll(predicate, Projections.constructor(Integer.class, QExample.example.year));

1. CustomJpaRepositoryFactoryBean
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class CustomJpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {
public CustomJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomJpaRepositoryFactory(entityManager);
}
}
2. CustomJpaRepositoryFactory
import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {
public CustomJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
if(QUERY_DSL_PRESENT) {
Class<?> repositoryInterface = metadata.getRepositoryInterface();
if(CustomQueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface)) {
return CustomQueryDslJpaRepository.class;
} else if(QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface)) {
return QueryDslJpaRepository.class;
}
}
return SimpleJpaRepository.class;
}
}
3. CustomQueryDslJpaRepository
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
public class CustomQueryDslJpaRepository<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID> implements CustomQueryDslPredicateExecutor<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public CustomQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public <DTO> Page<DTO> findAll(Predicate predicate, Pageable pageable, FactoryExpression<DTO> factoryExpression) {
JPQLQuery<DTO> query = createQuery(predicate).select(factoryExpression);
querydsl.applyPagination(pageable, query);
querydsl.applySorting(pageable.getSort(), query);
QueryResults<DTO> queryResults = query.fetchResults();
return new PageImpl<>(queryResults.getResults(), pageable, queryResults.getTotal());
}
}
4. CustomQueryDslPredicateExecutor
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
public interface CustomQueryDslPredicateExecutor<T> extends QueryDslPredicateExecutor<T> {
<DTO> Page<DTO> findAll(Predicate predicate, Pageable pageable, FactoryExpression<DTO> factoryExpression);
}
5. example
#EnableJpaRepositories(
...
repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class
)
public interface ProductRepository extends JpaRepository<Product, Long> implements CustomQueryDslPredicateExecutor<Product> {
}

Related

How can I add a subselect to result in jpa query

I have an Spring boot app with an entity Person with a list of that persons events on.
Like this.
public class Person....
private Long id;
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "person_events", joinColumns = #JoinColumn(name = "person_id"))
private List<String> personEvents = new ArrayList<>();
Now when I select I want to select the result into a dto like this.
#Query("""
select new com.....dtos.PersonDto(p.name, p.events) from Person p
where p.name = (:name)
""")
Set<PersonDto> findPersonsByName(String name);
My repository fails because it cant generate the sql. How can I achieve this?
Few things you are doing can be improved like:
Instead of constructor select you can use interface projection
Set<PersonDto> findPersonsByName(String name);
Since you are doing a select on single item the result will be single object.
so please change it to
PersonDTO findPersonsByName(String name);
Please find my complete answer below:
package com.example.demo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
#RestController
#RequestMapping("/person")
public class PersonController {
private final PersonService personService;
#Autowired
public PersonController(PersonService personService) {
this.personService = personService;
}
#GetMapping
public Iterable<Person> list() {
return personService.list();
}
#PostMapping
public Person create(#RequestBody Person car) {
return personService.save(car);
}
#GetMapping(path = "/by")
public PersonDTO getPersonByName(#RequestParam(value = "name") String name) {
return personService.findPersonByName(name);
}
}
#Getter
#Setter
#ToString
#Entity
class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "person_events", joinColumns = #JoinColumn(name = "person_id"))
private List<String> personEvents = new ArrayList<>();
}
#Service
class PersonService {
private final PersonRepository personRepository;
#Autowired
PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Transactional
public Person save(Person person) {
return personRepository.save(person);
}
#Transactional(readOnly = true)
public Iterable<Person> list() {
return personRepository.findAll();
}
#Transactional(readOnly = true)
public PersonDTO findPersonByName(String name) {
return personRepository.findPersonsByName(name);
}
}
interface PersonDTO {
String getName();
Collection<String> getPersonEvents();
}
#Repository
interface PersonRepository extends JpaRepository<Person, Integer> {
PersonDTO findPersonsByName(String name);
}

Mapstruct: how to map multiple fields from DTO to an object in Entity?

i have this DTO:
#NoArgsConstructor
public class DataDTO implements DTO {
private static final long serialVersionUID = -5105904799152965475L;
private Long deviceId;
private OffsetDateTime generatedOn;
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
public void setGeneratedOn(OffsetDateTime generatedOn) {
this.generatedOn = generatedOn;
}
}
i have this MongoDB document:
#Document(collection = "data")
#EqualsAndHashCode
public class DataDocument {
private static final long serialVersionUID = 1772572723546311500L;
#Id
private IdByDeviceIdAndGeneratedOn id;
public DataDocument() {
}
public IdByDeviceIdAndGeneratedOn getId() {
return id;
}
public void setId(IdByDeviceIdAndGeneratedOn id) {
this.id = id;
}
}
and this is the #Id class for MongoDB Document:
#EqualsAndHashCode
#ToString
public class IdByDeviceIdAndGeneratedOn {
#Id
private final Long deviceId;
#Id
#Field("generated_on")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private final OffsetDateTime generatedOn;
public IdByDeviceIdAndGeneratedOn(final Long deviceId, final OffsetDateTime generatedOn) {
this.deviceId = Objects.requireNonNull(deviceId);
this.generatedOn = Objects.requireNonNull(generatedOn);
}
public Long getDeviceId() {
return deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
}
this is the mapper for this Key class:
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, componentModel = "spring")
public interface IdByDeviceIdAndGeneratedOnMapper {
default IdByDeviceIdAndGeneratedOn toId(final Long deviceId, final OffsetDateTime generatedOn) {
return new IdByDeviceIdAndGeneratedOn(deviceId, generatedOn);
}
default Long getDeviceId(final IdByDeviceIdAndGeneratedOn id) {
return id.getDeviceId();
}
default OffsetDateTime getGeneratedOn(final IdByDeviceIdAndGeneratedOn id) {
return id.getGeneratedOn();
}
and this is the #Mapper for DataDTO and DataDocument:
#Mapper( unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {IdByDeviceIdAndGeneratedOnMapper.class,
AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class
})
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
}
this is the generic mapper:
/**
* Contract for a generic dto to entity mapper.
*
* #param <DTO> - DTO source type parameter.
* #param <DOCUMENT> - MongoDB Document destination type parameter.
*/
public interface DocumentMapper<DTO, DOCUMENT> {
DOCUMENT toDocument(DTO dto);
DTO toDto(DOCUMENT document);
}
Currently i'm receiving this errors:
for MongoDB Data docment:
Unmapped target property: "id".
for DTO:
Unmapped target properties: "deviceId, generatedOn".
How to solve this errors without loosing immutability of Id class?
What you are trying to do is to use (using constructors to construct objects) is not yet supported. There is an open issue for it #73.
However, you can achieve what you are looking for by using Object factories, this is for the toDocument mapping, for the toDto mapping you can use nested source mappings.
Your mapper would look like:
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Mapping(target = "id", source = "dto")
#Override
DataDocument toDocument(DataDTO dto);
#ObjectFactory
default IdByDeviceIdAndGeneratedOn createId(DataDTO dto) {
return dto == null ? null : new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn());
}
#Mapping(target = "deviceId", source = "id.deviceId")
#Mapping(target = "generatedOn", source = "id.generatedOn")
#Override
DataDTO toDto(DataDocument document);
}
NB: You can also make DataDocumentMapper abstract class and make the createId method protected, in case you don't want to expose it in the interface
this is solved my problem, but this doesnt look elegant.
Maybe there is more elegant way?
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
imports = {IdByDeviceIdAndGeneratedOn.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Override
#Mapping(target = "id", expression = "java( new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn()) )")
DataDocument toDocument(DataDTO dto);
#Override
#Mapping(target = "deviceId", expression = "java( document.getId().getDeviceId() )")
#Mapping(target = "generatedOn", expression = "java( document.getId().getGeneratedOn() )")
DataDTO toDto(DataDocument document);
}

Spring Boot, Spring Data & MongoDB Long Sequence not getting generated

Spring Boot Version: 1.5.7.RELEASE
Spring Data
MongoDB
My attempt is to add a simple TODO in TODO Document in MongoDB. I was able to add it if I go by String id.But I want my id to be Long. And my requirement is to generate my own sequence. I am having issues generating sequence with the following code. Appreciate if you point where I am going wrong. I was able to add if I create my own id instead of using a sequence.
package com.srisris.tapasya.springjersey;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import com.srisris.tapasya.springjersey.domain.ToDo;
import com.srisris.tapasya.springjersey.domain.ToDoSequence;
import com.srisris.tapasya.springjersey.repository.ToDoRepository;
import com.srisris.tapasya.springjersey.repository.ToDoSequenceRepository;
#SpringBootApplication(scanBasePackages = { "com.srisris.tapasya.springjersey" })
public class SpringJerseyApplication extends SpringBootServletInitializer implements CommandLineRunner {
#Autowired
private ToDoRepository todoRepository;
/**
* The repository for the sequence {#link ToDoSequence}
*/
#Autowired
private ToDoSequenceRepository sequenceRepo;
// The sequence key name for the books
private static final String ToDo_SEQ_KEY = ToDo.COLLECTION_NAME;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringJerseyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringJerseyApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
todoRepository.deleteAll();
bootStrapToDo();
}
private void bootStrapToDo() throws Exception{
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
Long sequenceId = sequenceRepo.getNextSequenceId(ToDo_SEQ_KEY);
System.out.println(" Sequence ID is " + sequenceId);
ToDo td1 = new ToDo(sequenceId,"Work on Java", "Learn Lambdas, Modules & clojures", dateFormat.format(date));
todoRepository.save(td1);
//ToDo td2 = new ToDo(sequenceRepo.getNextSequenceId(ToDo_SEQ_KEY),"Work on Swift", "Learn Optionals", dateFormat.format(date));
//todoRepository.save(td2);
}
}
ToDo Domain
package com.srisris.tapasya.springjersey.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = ToDo.COLLECTION_NAME)
public class ToDo {
public static final String COLLECTION_NAME = "todos";
#Id
private Long id;
private String summary;
private String description;
private String dateCreated;
public ToDo() {
}
public ToDo(Long todoID, String summary, String description, String dateCreated) {
this.id = todoID;
this.summary = summary;
this.description = description;
this.dateCreated = dateCreated;
}
public Long getId() {
return id;
}
public void setId(Long todoID) {
this.id = todoID;
}
public String getSummary() {
return summary;
}
public String getDescription() {
return description;
}
public String getDateCreated() {
return dateCreated;
}
#Override
public String toString() {
return String.format("ToDo[id=%s, summary='%s', description='%s' , dateCreated='%s']", id, summary, description,
dateCreated);
}
}
Custom Sequence Domain
ToDoSequence
package com.srisris.tapasya.springjersey.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = ToDoSequence.COLLECTION_NAME)
public class ToDoSequence {
public static final String COLLECTION_NAME = "todoSequence";
#Id
private String id;
private Long sequence;
public ToDoSequence() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getSequence() {
return sequence;
}
public void setSequence(Long seq) {
this.sequence = seq;
}
}
ToDoRepository
package com.srisris.tapasya.springjersey.repository;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.srisris.tapasya.springjersey.domain.ToDo;
public interface ToDoRepository extends MongoRepository<ToDo, Long> {
public ToDo findBySummary(String summary);
public List<ToDo> findByDateCreated(String dateCreated);
}
ToDoSequenceRepository
package com.srisris.tapasya.springjersey.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import com.srisris.tapasya.springjersey.domain.ToDoSequence;
#Repository
public class ToDoSequenceRepository {
#Autowired
private MongoOperations mongoOperation;
public Long getNextSequenceId(String key) throws Exception {
Query query = new Query(Criteria.where("_id").is(key));
// increase sequence id by 1
Update update = new Update();
update.inc("sequence", 1);
// return new increased id
FindAndModifyOptions options = new FindAndModifyOptions();
options.returnNew(true);
// this is the magic happened.
ToDoSequence seqId = mongoOperation.findAndModify(query, update, options, ToDoSequence.class);
// if no id, throws SequenceException
// optional, just a way to tell user when the sequence id is failed to
// generate.
if (seqId == null) {
throw new Exception(seqId + " Unable to get sequence id for key : " + key);
}
return seqId.getSequence();
}
}

JPA #ManyToOne does not working

I can't understand where I'm going wrong when saving a List within and JPA Entity.
I have a super class Person. Client class extends Person. Client class has a list of Phone entities as #OneToMany (Bidirection) as code shown below. Whenever a Client entity is persisted with that phone list, all phones in list are saved as well. However, in Phone Table there are no client id recorded.
#Entity#Inheritance(strategy=InheritanceType.SINGLE_TABLE)#DiscriminatorColum(name="type")
public abstract class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy=GenerationType.SEQUENCE,generator="PERSON_SEQ")
#SequenceGenerator(name="PERSON_SEQ",sequenceName="PERSON_SEQ", allocationSize=1,initialValue=1000)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}}
Client.class
public class Client extends Person implements Serializable {
private static final long serialVersionUID = 1L;
private String foo;
#OneToMany(cascade=CascadeType.ALL,mappedBy="owner")
private List<Phone> phones;
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
Phone class
public class Phone implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idPhone;
private String number;
#ManyToOne(fetch=FetchType.EAGER) #JoinColumn(name="id")
private Person owner;
public Long getIdPhone() {
return idPhone;
}
public void setIdPhone(Long idPhone) {
this.idPhone = idPhone;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
ClientController class
#Named(value = "clientController")
#ViewScoped
public class ClientController extends BaseController implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject
private ClientService service;
#Inject
private Client client;
#Inject
private Employee employee;
#Inject
private Phone phone;
public void save(ActionEvent event) {
System.out.println(" Saving in Controller");
try {
client = new Client();
employee = new Employee();
Phone p1 = new Phone();
p1.setNumber("99998888");
Phone p2 = new Phone();
p2.setNumber("88887777");
List<Phone> phones = new ArrayList<Phone>();
phones.add(p1);
phones.add(p2);
client.setName("Novembro" );
client.setPhones(phones);
employee.setPhones(phones);
client.setFoo("foo value" );
employee.setBar("bar value");
service.saveOrUpdate(client);
//client = new Client();
addMessage(FacesMessage.SEVERITY_INFO, "Cliente registrado com sucesso");
} catch (Exception e) {
addMessage(FacesMessage.SEVERITY_ERROR, "Tente mais tarde");
e.printStackTrace();
}
}
}
Client Service class
public class ClientService implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject
private ClientDAO dao;
public Client saveOrUpdate(Client client) {
System.out.println(" Saving in Service");
return dao.save(client);
}
}
DAO save method
public T save(T entity) {
beginTransaction();
em.persist(entity);
em.flush();
commitAndCloseTransaction();
return entity;
}
I can't understand why it is not working as expected. I mean, save phones entities with ID from Client who owns the phones.
Database Postgresql 9.6
EclipseLink 2.6.0
JPA 2.1
All classes have #Entity and #Discrimator annotations
#Entity
#DiscriminatorValue(value="C")
#Entity
#Table(name="PHONE")

Why entityManager.contains returns different results?

This is in JPA2 (EclipseLink) and JSF2.
I have an entity class Student:
#Entity
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
private int age;
public Student(String firstname, String lastname, int age) {
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
public Student() {
}
// accessors and mutators here
}
Session bean StudentFacade that inherits AbstractFacade:
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
public T edit(T entity) {
return getEntityManager().merge(entity);
}
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
// other methods: findAll, findRange, count
}
#Stateless
public class StudentFacade extends AbstractFacade<Student> {
#PersistenceContext(unitName = "jpa2testsPU")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public StudentFacade() {
super(Student.class);
}
public boolean contains(Student s) {
return getEntityManager().contains(s);
}
public void testContains() {
Student s = find(1L);
boolean isContains = getEntityManager().contains(s);
}
}
This is my JSF Managed Bean:
#ManagedBean
#RequestScoped
public class IndexController {
#EJB
private StudentFacade studentFacade;
/**
* Creates a new instance of IndexController
*/
public IndexController() {
}
public String test() {
Student s = new Student("John", "Doe", 20);
studentFacade.create(s);
Student s1 = studentFacade.find(1L); // This works because table only has 1 record
boolean isContains = studentFacade.contains(s);
return null;
}
}
When I run test() from managed bean, isContains is false. But when testContains() in StudentFacade is called, isContains is true. Why is this?
StudentFacade is a Stateless Session Bean (SSB). The contents of its instance variables are not guaranteed to be preserved across method calls (reference). It's like having a different instance of EntityManager created for each method invocation.
When you run your test from the managed bean, you invoke two different methods on the SSB, therefore a different EntityManager instance is created for each call, and the second one does not contain the Student instance because it has not been loaded yet.
But when you run your test inside a method of the SSB itself, the same EntityManager is used for the scope of the entire method, therefore the call to contains() returns true.