spring data jpa query value in a Set - java

I have an entity class A which has a Set of entities of class B with a ManyToMany relationship (out of scope why I need this)
class A {
#ManyToMany(cascade = CascadeType.ALL)
Set<B> setOfB;
}
Now, given an object of class B, how can I retrieve the object(s) of class A which has the B object in its Set??
I have tried in my class A repository with this:
interface Arepository extends JpaRepository<A, Long> {
#Query("from A a where ?1 in a.setOfB")
List<A> findByB(B b)
}
But it gives me a SQLGrammarException, so which is the correct syntax?
Thank you for your help.

Try with #Query("SELECT a from A a where ?1 member of a.setOfB").

Metamodel class:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(A.class)
public class A_ {
public static volatile SetAttribute<A, B> bSet;
}
Specification utils:
public class ASpecs {
public static Specification<A> containsB(B b) {
return (root, query, cb) -> {
Expression<Set<B>> bSet = root.get(A_.bSet);
return cb.isMember(b, bSet);
};
}
}
Your repository:
public interface ARepository extends JpaRepository<A, Long>, JpaSpecificationExecutor<A> {
}
Usage:
#Service
public class YourService {
#Resource
private ARepository repository;
public List<A> getByB(B b) {
return repository.findAll(ASpecs.containsB(b));
}
}

Related

SpringBoot: create object from generic type in generic mapper

I have a lot of entity extend Catalog entity and as well as have a lot of dto that extent CatalogDto
And I have a generic repository, service, and mapper as follows
My repository:
#Repository
public interface CatalogRepository<T extends Catalog> extends JpaRepository<T, Integer>{
}
My service:
#Service
#Transactional
public class CatalogServiceImpl<T extends Catalog,Dto extends CatalogDto>{
private final Logger log = LoggerFactory.getLogger(CatalogServiceImpl.class);
private final CatalogRepository<T> repository;
private CatalogMapper<T,Dto> catalogMapper=new CatalogMapper<T,Dto>() {};
public CatalogServiceImpl(CatalogRepository<T> repository) {
this.repository = repository;
}
}
My Mapper:
public abstract class CatalogMapper<T extends Catalog,Dto extends CatalogDto> implements Rapper<T,Dto> {
#Override
public Dto entityToDto(T entity) {
return null;
}
#Override
public T dtoToEntity(Dto dto) {
return null;
}
}
I want to create an object from T in dtoToEntity method and an object from Dto in entityToDto method in CatalogMapper class
I think that these two methods should be abstract because every mapper probably works in different ways. Anyway you can provide a base implementation like this
public T dtoToEntity(Dto dto) throws InstantiationException, IllegalAccessException {
T entity = (T) ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
BeanUtils.copyProperties(dto, entity);
return entity;
}
public Dto entityToDto(T entity) throws InstantiationException, IllegalAccessException {
Dto dto = (Dto) ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
BeanUtils.copyProperties(entity, dto);
return dto;
}
Using ParameterizedType of the generic class you can create a new instance and then execute a simple copyProperties

JPA repository with single table inheritance (hibernate)

I have created two entites (RegularEmployee and ContactEntity) that extends the Employee entity.
#Entity
#Table(name="employees")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value="employee")
public class Employee {
#Id
#GeneratedValue
private Long id;
private String name;
...
Im using SINGLE_TABLE inheritance for this implementations, and created a generic JpaRepository for manipulating data:
#Repository
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
}
I've created also the Service class that autowire three instance of these generic repositories, and specific methods for each class.
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository<Employee> employeeRepo;
#Autowired
private EmployeeRepository<RegularEmployee> regularRepo;
#Autowired
private EmployeeRepository<ContractEmployee> contractRepo;
public List<Employee> getAllEmployee() {
return employeeRepo.findAll();
}
public List<RegularEmployee> getAllRegularEmployee(){
return regularRepo.findAll();
}
public List<ContractEmployee> getAllContractEmployee() {
return contractRepo.findAll();
}
...
My problem is, that when I try to find all regular employees or contract employees, I always get all type of employees (employees, regular employees and contract employees all together).
I do not know why it behaves like this, even though the method's signature says it returns the appropriate type.
One option is to use #Query in EmployeeRepository:
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
#Query("from RegularEmployee")
List<RegularEmployee> findAllRegularEmployees();
}
A second option is to create an additional repository for each subclass of Employee. For RegularEmployee would be:
public interface RegularEmployeeRepository extends EmployeeRepository<RegularEmployee>{}
This is how to use both options in EmployeeService:
#Service
public class EmployeeService {
#Autowired EmployeeRepository<Employee> employeeRepo;
#Autowired EmployeeRepository<RegularEmployee> regularRepoT;
#Autowired RegularEmployeeRepository regularRepo;
#PostConstruct
public void init(){
employeeRepo.save(new ContractEmployee("Mark"));
employeeRepo.save(new RegularEmployee("Luke"));
employeeRepo.findAll().forEach(System.out::println); // prints Mark and Luke
regularRepo.findAll().forEach(System.out::println); // prints only Luke
regularRepoT.findAllRegularEmployees().forEach(System.out::println); // prints only Luke
}
//...
}
Also you can omit #Repository on top of EmployeeRepository. Spring already knows that is a Repository because it extends JpaRepository.
Side note: if you don't need EmployeeRepository to be created by Spring add #NoRepositoryBean on top of its class.
I've been able to replicate what you've encountered using your generic EmployeeRepository. As an alternative I created two separate repositories: ContractualEmployeeRepository and RegularEmployeeRepository.
public interface ContractualEmployeeRepository extends JpaRepository<ContractualEmployee, String> {
}
public interface RegularEmployeeRepository extends JpaRepository<RegularEmployee, String> {
}
Then, I created an integration test.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Main.class})
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
#TestPropertySource(locations="classpath:application-test.properties")
#DatabaseSetup("classpath:SingleTableDataSet.xml")
public class IntegrationTest {
#Autowired
private RegularEmployeeRepository regularEmployeeRepository;
#Autowired
private ContractualEmployeeRepository contractualEmployeeRepository;
#Test
public void test() {
Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
}
}
and it works.
As for the usage and limitations of Generics in Spring Data JPA repositories: https://stackoverflow.com/a/19443031/14180014 He had done a great job explaining it.

Using JPA with multiple AND operations

I'm working on a Spring app and defining various find methods on a repository:
#Repository
public interface TicketRepository extends JpaRepository<TicketEntity, Long> {
List<TicketEntity> findByTicketId(#Param("ticketId") Long ticketId);
List<TicketEntity> findByTicketIdAndState(#Param("ticketId") Long ticketId, #Param("state") String state);
List<TicketEntity> findByTicketIdAndStateAndFlagged(#Param("ticketId") Long ticketId, #Param("state") String state, #Param("flagged") String Flagged);
}
The problem is that I have 30 columns which can be optionally filtered on. This is will result in the repository methods becoming unwieldy:
List<TicketEntity> findByTicketIdAndStateAndFlaggedAndCol4AndCol5AndCol6AndCol7AndCol8AndCol9AndCol10AndCol11AndCol12AndCol13AndCol14AndCol15AndCol16AndCol17AndCol18AndCol19AndCol120....);
How should the JPA layer be designed to cater for this scenario ?
If I create an object with attributes:
public class SearchObject {
private String attribute1;
//Getter and Setters
.
.
.
.
}
Can I pass SearchObject into a a find method and Spring JPA will determine which attributes to insert AND statements for depending on which attributes are Null - if the attribute is not null a corresponding AND is generated for that attribute.
Create filter object that will contain all optional columns e.g.:
#AllArgsConstructor
public class TicketFilter {
private final String col1;
private final Integer col2;
public Optional<String> getCol1() {
return Optional.ofNullable(col1);
}
public Optional<Integer> getCol2() {
return Optional.ofNullable(col2);
}
}
Extend your Respoitory with JpaSpecificationExecutor
Create specification class:
public class TicketSpecification implements Specification {
private final TicketFilter ticketFilter;
public TicketSpecification(TicketFilter ticketFilter) {
this.ticketFilter = ticketFilter;
}
#Override
public Predicate toPredicate(Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
ticketFilter.getTitle().ifPresent(col1 -> predicates.add(getCol1Predicate(root, col1)));
ticketFilter.getDescription().ifPresent(col2 -> predicates.add(getCol2Predicate(root, col2)));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private Predicate getCol1Predicate(Root root, String title) {
return root.get("col1").in(col1);
}
}
Use your repository: ticketRepository.findAll(specification);
Use Spring Data JPA Specification
Detail Solution be patient
First create a SpecificationCriteria class to define your criterias means filtering column as key and filtering value as value
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class SpecificationCriteria {
private String key;
private Object value;
}
Then create SpecificationCriteriaBuilder to build your Criteria
#Service
public class SpecificationCriteriaBuilder {
public List<SpecificationCriteria> buildCriterias(String name) {
List<SpecificationCriteria> specificationCriterias = new ArrayList<SpecificationCriteria>();
if (!StringUtils.isEmpty(name)) {
specificationCriterias
.add(SpecificationCriteria.builder().key("name")
.value(name).build());
}
// Here you can add other filter one by one
return specificationCriterias;
}
}
Then create a SpecificationBuilder class to build your specifications.
You can build from the list of filter options(Criteria) to List of specification
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
#Service
public class SpecificationBuilder<T> {
public Specification<T> buildSpecification(List<SpecificationCriteria> specificationCriterias) {
if (ObjectUtils.isEmpty(specificationCriterias)) {
return null;
}
Specification<T> specification = getSpecification(specificationCriterias.get(0));
for (int index = 1; index < specificationCriterias.size(); index++) {
SpecificationCriteria specificationCriteria = specificationCriterias.get(index);
specification =
Specification.where(specification).and(getSpecification(specificationCriteria));
}
return specification;
}
public Specification<T> getSpecification(SpecificationCriteria specificationCriteria) {
Specification<T> specification = new Specification<T>() {
private static final long serialVersionUID = 2089704018494438143L;
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.equal(root.get(specificationCriteria.getKey()),
specificationCriteria.getValue());
}
};
return specification;
}
}
In service first build criteria and then build specification using them. Then use specifications in repository call
#Service
#Transactional
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class UserService {
private final SpecificationCriteriaBuilder criteriaBuilder;
private final SpecificationBuilder<User> specificationBuilder;
private final UserRepository userRepository;
public List<User> getAll(String name) {
List<SpecificationCriteria> specificationCriterias =
criteriaBuilder.buildCriterias(name); // here you can pass other parameter as function argument
Specification<User> specification =
specificationBuilder.buildSpecification(specificationCriterias);
List<User> users = userRepository.findAll(specification);// pass the specifications
return users;
}
Repository extend JpaSpecificationExecutor
#Repository
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
}

how inject implementation of JpaRepository

I want to use method a from UserRepository in UserService, but I'm getting jpaRepository instead my custom implementation, how should I write classes to get it?
Repository:
#Repository
public interface UserRepository<UserEntity extends EntityInterface,Long> extends JpaRepository<UserEntity,Long> {
Optional<UserEntity> findUserByLogin(String login);
}
CrudAbstractService with generics method:
public abstract class CrudAbstractService<ENTITY extends EntityInterface, DTO extends DTOInterface> {
protected final JpaRepository<ENTITY, Long> jpaRepository;
protected final Validator<DTO> validator;
protected final MapperInterface<ENTITY, DTO> mapper;
private Class<ENTITY> entityClazz;
public CrudAbstractService(JpaRepository<ENTITY, Long> jpaRepository,
Validator<DTO> validator, MapperInterface<ENTITY, DTO> mapper) {
this.jpaRepository = jpaRepository;
this.validator = validator;
this.mapper = mapper;
}
public Iterable<DTO> findAll() {
List<ENTITY> allEntities = jpaRepository.findAll();
if (allEntities == null) {
throw new EntityNotFound(entityClazz);
}
List<DTO> mappedDTOs = mapper.toDTOs(allEntities);
return mappedDTOs;
}
public void delete(DTO dto) {
validator.validate(dto);
ENTITY entity = mapper.toEntity(dto);
jpaRepository.delete(entity);
}
public DTO save(DTO dto) {
validator.validate(dto);
ENTITY entity = mapper.toEntity(dto);
ENTITY save = jpaRepository.save(entity);
if (save == null) {
throw new EntityNotFound(entityClazz);
}
DTO mappedDTO = mapper.toDTO(save);
return mappedDTO;
}
}
Implementation of CrudUserService, there I want to inject UserRepository instead of JpaRepository:
#Service
public class UserService extends CrudAbstractService<UserEntity,UserDTO> {
private MapperInterface<LectureEntity,LectureDTO> lectureMapper;
public UserService(UserRepository<UserEntity, Long> jpaRepository,
Validator<UserDTO> validator, MapperInterface<UserEntity, UserDTO> mapper,
MapperInterface<LectureEntity,LectureDTO> lectureMapper) {
super(jpaRepository, validator, mapper);
this.lectureMapper = lectureMapper;
}
public UserDTO findUserByLogin(String login) {
if (login == null) {
throw new UserNotFoundException();
}
//Here i want use UserRepository method instead of JpaRepository.
Optional<UserEntity> userByLogin = jpaRepository.findUserByLogin(login);
UserEntity userEntity = userByLogin.orElseThrow(UserNotFoundException::new);
List<LectureEntity> reservations = userEntity.getReservations();
List<LectureDTO> lectureDTOS = lectureMapper.toDTOs(reservations);
UserDTO userDTO = mapper.toDTO(userEntity);
userDTO.setLectures(lectureDTOS);
return userDTO;
}
}
I think you don't need to make you repository interface generic.
So, replace this:
#Repository
public interface UserRepository<UserEntity extends EntityInterface,Long> extends JpaRepository<UserEntity,Long> {
Optional<UserEntity> findUserByLogin(String login);
}
with this:
#Repository
public interface UserRepository extends JpaRepository<UserEntity,Long> {
Optional<UserEntity> findUserByLogin(String login);
}
And use it in your service:
#Service
public class UserService extends CrudAbstractService<UserEntity,UserDTO> {
private MapperInterface<LectureEntity,LectureDTO> lectureMapper;
public UserService(UserRepository jpaRepository,
Validator<UserDTO> validator, MapperInterface<UserEntity, UserDTO> mapper,
MapperInterface<LectureEntity,LectureDTO> lectureMapper) {
super(jpaRepository, validator, mapper);
this.lectureMapper = lectureMapper;
}
}
If you need to map your entities to DTOs then you can try to use JPA projections
Regarding throwing an exception in findAll() - in my opinion, it's not a good idea. You should probably return just empty list and let the clients of your class decide what to do in case of missing entities.
Also in your case I would try to avoid using abstract classes and inheritance and use composition instead.
Inheritance versus composition: How to choose and Why should I prefer composition over inheritance?

How to assign a #Projection to a #GetMapping spring servlet endpoint?

I have an #Entity Person class, and want to expose that via a webservice. There should be a method just exposing all details, and and endpoint only exposing a view excerpt.
Can I use Spring #Projection for that purpose without having to manually extract the fields I want to expose? I'd prefer just returning a List<Person> but render only certain details for certain endpoints.
#RestController
public class BookingInfoServlet {
#Autowired
private PersonRepository dao;
#GetMapping("/persons")
public List<Person> persons() {
return dao.findAll();
}
//TODO how can I assign the Projection here?
#GetMapping("/personsView")
public List<Person> persons() {
return dao.findAll();
}
//only expose certain properties
#Projection(types = Person.class)
public interface PersonView {
String getLastname();
}
}
#Entity
public class Person {
#id
long id;
String firstname, lastname, age, etc;
}
interface PersonRepository extends CrudRepository<Person, Long> {
}
Note that #Projection only works with spring data rest. I believe you could try this:
#Projection(name = "personView", types = Person.class)
public interface PersonView {
String getLastname();
}
And on your repo, you need something like this:
#RepositoryRestResource(excerptProjection = PersonView.class)
interface PersonRepository extends CrudRepository<Person, Long> {
}

Categories

Resources