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?
Related
I need to add a query endpoint to all of my Spring Data REST repositories. Something like this:
/api/users/query?query=...
/api/issues/query?query=...
/api/projects/query?query=...
...
or
/api/users/search/query?query=...
/api/issues/search/query?query=...
/api/projects/search/query?query=...
...
The URL format doesn't matter.
I implemented a custom base repository:
#NoRepositoryBean
public interface QueryableRepository<T, ID> extends JpaRepository<T, ID> {
Page<T> findAllByQuery(String query, Pageable pageable);
}
public class CustomRepository<T, ID> extends SimpleJpaRepository<T, ID> implements QueryableRepository<T, ID> {
public CustomRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
#Override
public Page<T> findAllByQuery(String query, Pageable pageable) {
return findAll(pageable); // Some omitted implementation here
}
}
#EnableJpaRepositories(repositoryBaseClass = CustomRepository.class)
For sure findAllByQuery method is not exposed by Spring Data REST:
https://stackoverflow.com/a/21502510/632199
https://stackoverflow.com/a/25217113/632199
I can implement a controller for each entity type exposing such a method:
#RepositoryRestController
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#GetMapping(path = "/users/search/query")
public ResponseEntity<CollectionModel<PersistentEntityResource>> findAllByQuery(
String query,
Pageable pageable,
PagedResourcesAssembler<Object> pagedAssembler,
PersistentEntityResourceAssembler resourceAssembler) {
return ResponseEntity.ok(pagedAssembler.toModel(
userRepository.findAllByQuery(value, pageable).map(Object.class::cast),
resourceAssembler));
}
}
But is it possible to add this method to all entities once, without creation of dosens of same controllers?
The following works for me:
#RepositoryRestController
public class QueryController {
private final ApplicationContext context;
public QueryController(ApplicationContext context) {
this.context = context;
}
#GetMapping(value = "/{repository}/query")
public ResponseEntity<Object> findAllByQuery(
RootResourceInformation resourceInformation,
String query, Pageable pageable,
PagedResourcesAssembler<Object> pagedAssembler,
PersistentEntityResourceAssembler resourceAssembler) {
Repositories repositories = new Repositories(context);
Optional<QueryableRepository<?, ?>> repository = repositories
.getRepositoryFor(resourceInformation.getDomainType())
.filter(QueryableRepository.class::isInstance)
.map(QueryableRepository.class::cast);
if (repository.isPresent()) {
return ResponseEntity.ok(pagedAssembler.toModel(
repository.get()
.findAllByQuery(query, pageable)
.map(Object.class::cast),
resourceAssembler));
} else {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
}
}
}
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
I followed the reference guide for creating and customizing Repositories and came up with the following:
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
}
#Transactional(readOnly = true)
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
#Override
public User findByToken(UUID token) {
return new User();
}
}
public interface UserRepositoryCustom {
User findByToken(UUID token);
}
In my case userRepository.findByToken(token);returns null.
#Edit
The test below fails
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = NONE)
public class UserRepositoryTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository userRepository;
#Test
public void test() throws Exception{
assertNotNull(userRepository.findByToken(UUID.randomUUID()));
}
}
Your custom implementation is named wrong. It should be named after the class name of the Repository, not after the interface declaring the custom method.
Just renamed UserRepositoryCustomImpl to UserRepositoryImpl
The reason the method currently returns null is because Spring Data creates a query from the name and doesn't find a User with the specified token.
I'm trying to create an abstract class that performs the common REST operations that are required, but can't work out if what I'm trying to do is possible. I've tried a number of approaches, but have stripped the code below right back to how it should work in my head
Classes updated as per suggestions below. Problem now is that the constructor in the concrete class isn't valid, as CustomerRepository isn't assignable to JpaRepository, though it extends that interface.
AbstractRestController
public abstract class AbstractRestController<T> {
private final JpaRepository<T, Serializable> repository;
public AbstractRestController(JpaRepository<T, Serializable> repository) {
this.repository = repository;
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<JsonResponseBody<T>> getOne(#PathVariable Long id) {
T restObj = repository.findOne(id);
JsonResponseBody<T> response = new JsonResponseBody<>(ResponseStatus.SUCCESS, restObj);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(response);
}
protected JpaRepository<T, Serializable> getRepository() {
return repository;
}
}
CustomerController
#RestController
#RequestMapping(value = "/api/v1/customer")
public class CustomerController extends AbstractRestController<Customer> {
#Autowired
public CustomerController(CustomerRepository repository){
super(repository);
}
}
CustomerRepository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
Indeed, as #dino-tw mentions, you are trying to instantiate an abstract class with an undefined dependency. You can absolutely have an abstract controller class, and even define request handling methods that will be inherited by all subclasses. Try this instead:
public abstract class AbstractRestController<T, ID extends Serializable> {
private final JpaRepository<T, ID> repository;
public AbstractRestController(JpaRepository<T, ID> repository){
this.repository = repository;
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<JsonResponseBody<T>> getOne(#PathVariable ID id) {
T restObj = repository.findOne(id);
JsonResponseBody<T> response = new JsonResponseBody<>(ResponseStatus.SUCCESS, restObj);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(response);
}
protected JpaRepository<T, ID> getRepository(){ return repository; }
}
#RestController
#RequestMapping(value = "/api/v1/customer")
public class CustomerController extends AbstractRestController<Customer, Long> {
#Autowired
public CustomerController(CustomerRepository repository){
super(repository);
}
}
I would like to override the default CrudRepository save method that is also exported to Rest api:
#RepositoryRestResource(path = "users")
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#RestResource(exported=false)
User save(User user);
}
In my ApiController I have set up a requestmapping like this:
#RequestMapping(value = "/", produces = "application/json", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Resource<User>> registerUser(
#RequestParam("name") String name,
#RequestParam("alias") String alias,
#RequestParam("email") String email,
#RequestParam("password") String password,
#RequestParam("dateOfBirth") String dateOfBirth,
#RequestParam("imageIdentifier") String imageIdentifier) {
User user = new User();
//try {
// userReposiotry.save(user);
//} catch (Exception e) {
//}
Resource<User> resource = toResource(user);
return new ResponseEntity<Resource<User>>(resource, HttpStatus.OK);
}
The problem is when I try to POST to localhost:8080/api/users it returns a "Method Not allowed" which is good because it was set "exported=false"
But how can I implement my own POST for localhost:8080/api/users ?
Thanks
Another way to do it is to create a custom repository implementation like so:
#RepositoryRestResource(path = "users")
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
#Override
#RestResource(exported=false)
User save(User user);
}
public interface UserRepositoryCustom {
<S extends User> S save(T entity);
}
public UserRepositoryImpl implements UserRepositoryCustom {
<S extends User> S save(T entity) {
// implementation code...
}
}
If you look at the CrudRepository you will find a method <S extends T> S save(S entity);, that's where I got the save(..) from, just changed the extends T to extends User.
The other thing that I would pay attention to is the naming of the classes/interfaces, try to be consistent. The way I named them should work for you, the UserRepositoryImpl must have that name in order for this to work.
Doing this you won't have to set exported=false and you can just use the save() method as you would do normal.
Found a solution:
#BasePathAwareController
#RequestMapping("/users")
public class RestApiController implements ResourceProcessor<Resource<User>>{
#Autowired
private EntityLinks entityLinks;
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<Resource<User>> saveUser(#Param("name") String name) {
// Testing
System.out.println(name);
Resource<User> resource = new Resource<>(new User());
return new ResponseEntity<>(resource , HttpStatus.OK);
}
#Override
public Resource<User> process(Resource<User> resource) {
LinkBuilder lb = entityLinks.linkFor(User.class);
resource.add(new Link(lb.toString()));
return resource;
}
}
The CrudRepository save is still set as exported=false as in my question.