JPA Repository search by custom field - java

I am trying to get a list of the objects with the same field.
I have a model named Song, with the fields: name, genre, artist and id.
I read online about how to implement the method in the repository and i added it like this:
#Repository public interface ISongRepository extends JpaRepository<Song, Long>{
#Query("SELECT s.name FROM Song s WHERE s.genre = ?1")
Song findByGenre(String genre);
}
Now i want to retrieve the objects with an endpoint that i create in the controller. But i can't find online how i need to correctly write this method.
I tried to use the same format as my get by id but that doesn't work, is there a different format when it comes to custom methods?
Code of my get by id:
#GetMapping(value = "/{id}")
#ResponseBody
public ResponseEntity getSongById(#PathVariable("id") Long id) {
Optional<Song> song = songRepository.findById(id);
if(song.isEmpty()) {
return ResponseEntity.badRequest().body(song);
}
return ResponseEntity.ok(song.get());
}
Code of my method to retrieve the output of the custom method:
#GetMapping(value = "/genre/{genre}")
#ResponseBody
public ResponseEntity getSongByGenre(#PathVariable("genre") String songGenre){
List<Song> song = song.(songRepository.findByGenre(songGenre));
if(song.isEmpty()){
return ResponseEntity.badRequest().body(song);
}
return ResponseEntity.ok(song.get());
}
}

Try to add #Param at ISongRepository and change the return type findByGenre method to List<Song>
, Also make sure that you retrieve the id or genre from the rest controllers
#Repository public interface ISongRepository extends JpaRepository<Song, Long>{
#Query("SELECT s FROM Song s WHERE s.genre =:genre")
List<Song> findByGenre(#Param("genre") String genre);
}

I changed the method to this in the Controller:
#GetMapping("/genre/{genre}")
public ResponseEntity getAllSongsGenre(#PathVariable("genre") String genre){
List<Song> song;
if(genre != null){
song = songRepository.findByGenre(genre);
if(song.isEmpty()){
return ResponseEntity.badRequest().body(song);
}
return ResponseEntity.ok(song);
}
song = songRepository.findAll();
return ResponseEntity.ok(song);
}
And in the JPARepository to this:
List<Song> findByGenre(String genre);
The output is like this (In Postman):
{
"songName": "Someday",
"songArtist": "Kygo",
"songGenre": "House",
"songId": 1
}

Related

How to handle Ambiguous handler methods mapped in REST application with Spring

In my UserController, I have 3 #GetMapping methods:
getAllUsers() - It is used to get all users,
getUserById() - It is used to get specific user with his unique id and
getUserByName() - It is used to get specific user with his unique name.
The code from the method is as follows:
#RestController
#RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
#Autowired
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}
#GetMapping(value = "/{id}")
public ResponseEntity<User> getUserById(#PathVariable Long id) {
User user = userRepository.findById(id);
return ResponseEntity.ok(user);
}
#GetMapping(value = "/{name}")
public ResponseEntity<User> getUserByName(#PathVariable String name) {
User user = userRepository.findUserByName(name);
return ResponseEntity.ok(user);
}
The first problem is that my app doesn't know if the URL is String or Integer, so I solved the problem between the getUserById() and getUserByName() methods like this:
#GetMapping(value = "{id}")
public ResponseEntity<User> getUserById(#PathVariable Long id) {
User user = userRepository.findById(id);
return ResponseEntity.ok(user);
}
#GetMapping
public ResponseEntity<User> getUserByName(#RequestParam(value = "name") String name) {
User user = userRepository.findUserByName(name);
return ResponseEntity.ok(user);
}
So now I can access the methods with:
http://localhost:8080/users/1 and
http://localhost:8080/users?name=john
When I run the application, I get this error:
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method
com.db.userapp.controller.UserController#getUserByName(String)
to {GET [/users]}: There is already 'userController' bean method
I believe this is because my getAllUsers() and getUserByName() methods are on the same URL format. Does anyone have an idea how I can solve this?
You are right because both getAllUsers() and getUserByName() are mapped to the /users/. So for the request /users/ , it does not know which method should be used to process it.
You can configure the name query parameter for /users/ as optional and check if its value is null. Null means user does not want have any filtering on the users and want to get all users :
#RestController
#RequestMapping("/users")
public class UserController {
#GetMapping
public List<User> getUser(#RequestParam(value = "name",required = false) String name) {
if (Strings.isNullOrEmpty(name)) {
return userRepository.findAll();
} else {
return userRepository.findUserByName(name);
}
}
}
You're right. You have defined two GET Methods on /users and therefore its basically the same as your first problem.
You can merge these methods and set the RequestParam for name as not required and just use it when it's not null.
The problem here is that you would return one User as List even though you know that there is only one User for this name. Which I think is fine because its not the Users main identifier and with the RequestParam it's more like a filter anyway.

How to create route that search by value?

I have a recipe app with several routes (get, getById, post, delete, etc).
So far it hasn't been difficult to implement them, since, for example, a get route would be like this:
Controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Recipe> getAllRecipe() {
return recipesRepository.findAll();
}
Or by id:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
}
Controller:
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Recipe getRecipeById(#PathVariable("id") String id) {
return recipesRepository.findBy_id(id);
}
I want to make a search screen in my app, so when you write something (like "straw") I get all results that contain the word "straw" in my database (like strawberry ice cream, strawberry milkshake...) in the name field (my model has a field called name).
My knowledge in Java isnt by any means good, so I'm lost here, so far I have a route that gets all results using "name", but I need it to search anything that contains that value, not only if you put the exact value (like if I put "Stew", it gives me "Stew" result if it exists, but I need to get every result that have the word "Stew" in their name). If it helps, this is the code for that:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
Recipe findByName(String name);
}
Controller:
#RequestMapping(value = "/recipe/{name}", method = RequestMethod.GET)
public Recipe getRecipeByName(#PathVariable("name") String name) {
return recipesRepository.findByName(name);
}
About the case sensitive problem, you can add keywords, like this:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLikeIgnoreCase(String name);
}
I think you can use smth like this one :
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLike(String name);
}

cannot perform edit operation in spring boot

I want to edit my User Class while passing the id and while returning user object to controller it is getting error such as "There was an unexpected error (type=Internal Server Error, status=500)".It is telling me to typecast to Optional.I don't know what to do.
UserService Class
public User editMyUser(int id) {
return userRepository.findById(id);
}
Controller Class
#RequestMapping("/edit-user")
public String editUser(#RequestParam int id, HttpServletRequest request) {
userService.deleteMyUser(id);
request.setAttribute("user", userService.editMyUser(id));
request.setAttribute("mode", "MODE_UPDATE");
return "welcome";
}
This is how findById looks like in the new version of Spring (according to docs):
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
Optional<T> findById(ID primaryKey);
// .... other methods ...
}
So, the first thing I would change in your code is :
public Optional<User> editMyUser(int id) {
return userRepository.findById(id);
}
Make your method return Optional<User>, maybe this will help.
Also, be careful when using user returned by that new method, e.g. here
request.setAttribute("user", userService.editMyUser(id));
With Optional you need to use get() to obtain the actual user instance:
userService.editMyUser(id).get()
but first, you should check if that Optional actually contains the user:
Optional<User> optionalUser = userService.editMyUser(id);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
// do whatever you need
} else {
// means user is null - nothing found for ID
// act accordingly
}
There is also a good documentation that Spring provides. Could be useful.
Happy Coding :)
I let u here the cofiguration of the interface for a rest service that is working rihg now
#Api(value = "empleados", description = "the empleados API")
public interface EmpleadosApi {
#ApiOperation(value = "Buscar empleados", notes = "", response = ResultadoBusquedaEmpleado.class, tags = {})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Búsqueda de empleados", response = ResultadoBusquedaEmpleado.class) })
#RequestMapping(value = "/empleados", method = RequestMethod.GET)
ResponseEntity<ResultadoBusquedaEmpleado> empleadosGet(
#ApiParam(value = "Nombre de empleado") #RequestParam(value = "nombre", required = false) String nombre)
}

project how to redesign to use unique dao

I have two projects, but they are not using the same DAO layer, and one project use an URL request to invoke the methods from the other project.
I encountered a problem, when I create an entity, I have to create separated DAO, service and so on. This DAO is use different fragement.
I want to find one method or a sweet design pattern to use the unique abstract DAO, and change the URL invoke way to a clearer and more changeable way.
My code is like:
public static String apiDeviceEdit(Device device, HttpSession session,
Long operate) {
String userId = (String) session
.getAttribute(CommonAttributes.API_USERID_SESSION);
String token = (String) session
.getAttribute(CommonAttributes.API_TOKEN_SESSION);
Assert.notNull(userId);
Assert.notNull(token);
String param = "user_id=" + userId + "&device_sn=" + device.getfSn()
+ "&operate=" + operate + "&token=" + token;
String string = sendPost(setting.getApiDeviceEdit(), param,
"DeviceEdit");
return string;
}
the different Dao like that:
#Component
#Scope("singleton")
public class ConductorDao extends BaseHBDao <Conductor, Long> {
#Autowired
public ConductorDao (#Qualifier("sessionFactory") SessionFactory session) {
super ();
this.setSessionFactory (session);
}
}
public interface ConductorDao extends BaseDao<Conductor, Long> {
final String SELECT_BY_SN = "from Conductor c where c.sn =:sn";
Conductor findBySn(String sn) throws Exception;
Page<Conductor> findByUserId(Long getfId, Pageable pageable);
}
#Component
#Scope("singleton")
public class ConductorDaoImpl extends BaseDaoImpl<Conductor, Long> implements ConductorDao {
#Override
public Conductor findBySn(String sn) throws Exception {
List<Conductor> conductors = entityManager.createQuery(SELECT_BY_SN).setParameter("sn", sn).getResultList();
if (conductors == null || conductors.size()== 0) {
return new Conductor();
}
else {
return conductors.get(0);
}
}
#Override
public Page<Conductor> findByUserId(Long getfId, Pageable pageable) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Conductor> criteriaQuery = criteriaBuilder.createQuery(Conductor.class);
Root<Conductor> root = criteriaQuery.from(Conductor.class);
criteriaQuery.select(root);
Predicate restrictions = criteriaBuilder.conjunction();
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(root.get("userId"), getfId));
criteriaQuery.where(restrictions);
return super.findPage(criteriaQuery, pageable);
}
}
ok add third layer of abstraction, call it business and make all your call to the dao layer from this new project, cut all the call that goes directly to the dao to use your business tier. then you can use your two jars of the dao in that project, and by doing so you can expose it as rest, or web services if you want.

Implementing custom methods of Spring Data repository and exposing them through REST

I'm trying to add custom methods to my Spring Data repository PersonRepository as described in 1.3 Custom implementations for Spring Data repositories and exposing these method through REST. The initial code is from Accessing JPA Data with REST sample, here is the code for added/modified classes:
interface PersonRepositoryCustom {
List<Person> findByFistName(String name);
}
class PersonRepositoryImpl implements PersonRepositoryCustom, InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
// initialization here
}
#Override
public List<Person> findByFistName(String name) {
// find the list of persons with the given firstname
}
}
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
When I run the application and visit http://localhost:8080/portfolio/search/, I get the following response body:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Why findByFirstName is not exposed even if it is available in the PersonRepository interface?
Also, is there a way to dynamically/programmatically add respositories to be exposed via REST?
After two days, I have solved in this way.
Custom Repository Interface:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Custom Repository Implementation
public class PersonRepositoryImpl implements PersonRepositoryCustom{
#Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Spring Data Repository:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(#Param("name") String name, Pageable pageable);
}
Bean Resource representation
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Resource Assembler
#Component
public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
#Autowired
RepositoryEntityLinks repositoryEntityLinks;
public PersonResourceAssembler() {
super(PersonCustomSearchController.class, PersonResource.class);
}
#Override
public PersonResource toResource(Person person) {
Link personLink = repositoryEntityLinks.linkToSingleResource(Person.class, person.getId());
Link selfLink = new Link(personLink.getHref(), Link.REL_SELF);
return new PersonResource(person, Arrays.asList(selfLink, personLink));
}
}
Custom Spring MVC Controller
#BasePathAwareController
#RequestMapping("person/search")
public class PersonCustomSearchController implements ResourceProcessor<RepositorySearchesResource> {
#Autowired
PersonRepository personRepository;
#Autowired
PersonResourceAssembler personResourceAssembler;
#Autowired
private PagedResourcesAssembler<Person> pagedResourcesAssembler;
#RequestMapping(value="customFind", method=RequestMethod.GET)
public ResponseEntity<PagedResources> customFind(#RequestParam String param1, #RequestParam String param2, #PageableDefault Pageable pageable) {
Page personPage = personRepository.customFind(param1, param2, pageable);
PagedResources adminPagedResources = pagedResourcesAssembler.toResource(personPage, personResourceAssembler);
if (personPage.getContent()==null || personPage.getContent().isEmpty()){
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Person.class);
List<EmbeddedWrapper> embedded = Collections.singletonList(wrapper);
adminPagedResources = new PagedResources(embedded, adminPagedResources.getMetadata(), adminPagedResources.getLinks());
}
return new ResponseEntity<PagedResources>(adminPagedResources, HttpStatus.OK);
}
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link customLink = new Link(search + "/customFind{?param1,param2,page,size,sort}").withRel("customFind");
repositorySearchesResource.add(customLink);
return repositorySearchesResource;
}
}
The reason these methods are not exposed is that you're basically free to implement whatever you want in custom repository methods and thus it's impossible to reason about the correct HTTP method to support for that particular resource.
In your case it might be fine to use a plain GET, in other cases it might have to be a POST as the execution of the method has side effects.
The current solution for this is to craft a custom controller to invoke the repository method.
For GET methods I have used the following approach:
create a dummy #Query method in the Repository (LogRepository.java)
create a custom interface with the same method declared (LogRepositoryCustom.java)
create an implementation of the custom interface (LogRepositoryImpl.java)
Using this approach I don't have to manage projections and resource assembling.
#RepositoryRestResource(collectionResourceRel = "log", path = "log")
public interface LogRepository extends PagingAndSortingRepository<Log, Long>,
LogRepositoryCustom {
//NOTE: This query is just a dummy query
#Query("select l from Log l where l.id=-1")
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
public interface LogRepositoryCustom {
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
In the implementation you are free to use the repository methods or going directly to the persistence layer:
public class LogRepositoryImpl implements LogRepositoryCustom{
#Autowired
EntityManager entityManager;
#Autowired
LogRepository logRepository;
#Override
public Page<Log> findAllFilter(
#Param("options") String options,
#Param( "eid") Long[] entityIds,
#Param( "class" ) String cls,
Pageable pageable) {
//Transform kendoui json options to java object
DataSourceRequest dataSourceRequest=null;
try {
dataSourceRequest = new ObjectMapper().readValue(options, DataSourceRequest.class);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Session s = entityManager.unwrap(Session.class);
Junction junction = null;
if (entityIds != null || cls != null) {
junction = Restrictions.conjunction();
if (entityIds != null && entityIds.length > 0) {
junction.add(Restrictions.in("entityId", entityIds));
}
if (cls != null) {
junction.add(Restrictions.eq("cls", cls));
}
}
return dataSourceRequest.toDataSourceResult(s, Log.class, junction);
}
The answer is that you haven't followed instructions. Your PersonRepository has to extend both PagingAndSortingRepository<Person, Long> AND PersonRepositoryCustomin order to achieve what you're after. See https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Another option we used as well is to implement a custom repository factory for your specific storage type.
You can extend from RepositoryFactoryBeanSupport, build your own PersistentEntityInformation and take care of CRUD ops in a default repo impl for your custom data storage type. See JpaRepositoryFactoryBean for example. You maybe need to implement about 10 classes in total but then it gets reusable.
Try using
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
The implementing class name should be PersonRepositoryCustomImpl instead of PersonRepositoryImpl.

Categories

Resources