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.
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 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?
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> {
}
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);
}
}
So I have such classes
BaseDao
public interface BaseDao<T> {
T save(T entity);
}
UserDao
public interface UserDao extends BaseDao<User> {
User getUserByUserName(String name);
}
GenericDao
public abstract class GenericDao implements BaseDao {
#Autowired
protected SessionFactory sessionFactory;
private Class<?> getEntityClass() {
return ((Class) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]);
}
public T save(T entity) {
Integer id = (Integer) sessionFactory.getCurrentSession().save(entity);
return (T) sessionFactory.getCurrentSession().get(getEntityClass(), id);
}
}
UserDaoImpl
#Repository
public class PgUserDaoImpl extends GenericDao<User> implements UserDao {
public User getUserByUserName(String name) {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(
User.class);
criteria.add(Restrictions.eq("name", name));
return (User) criteria.uniqueResult();
}
}
GenericService
public class GenericService<T extends GenericDao<E>, E> {
protected T dao;
public GenericService(T dao) {
setDao(dao);
}
#Transactional
public E save(E entity) {
return dao.save(entity);
}
}
UserServiceImpl
#Service("userServiceImpl")
public class UserServiceImpl extends GenericService<PgUserDaoImpl, User>
implements UserService {
#Autowired
public UserServiceImpl(PgUserDaoImpl dao) {
super(dao);
}
#Autowired
private UserAssebmler assebmler;
#Transactional
#Override
public UserDetails loadUserByUsername(String name)
throws UsernameNotFoundException {
.....
}
}
So I write test contoller to save user, but I always get Exception
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
The contoller
#
Controller
#RequestMapping(value = "/test")
public class TestController {
#Autowired
private UserServiceImpl userService;
#RequestMapping(value = "save", method = RequestMethod.GET)
public String test() {
User user = new User();
user.setName("admin");
user.setPassword("21232f297a57a5a743894a0e4a801fc3");
user.setRole(UserRole.ADMIN);
userService.save(user);
return "home";
}
}
Anybody know hot to fix that? Thanks :)
I've solved my question by extending GenericDao class from HibernateDaoSupport class.