Hibernate and trying to build simple feature where we can search Product by Id. Hibernate has inbuit function to search an entity by its id. I tried the same but i am getting "java.lang.NoSuchMethodException" .
MyController.java :
#GetMapping(value = "/getProducts/{id}" , produces ="application/json")
public ResponseEntity<Product> display(#PathVariable int id) {
Product products = productServiceImp.getAllProducts(id);
return ResponseEntity.ok(products);
MyProductServiceImp:
#Override
public Product getAllProducts(int product_id ) {
return productRepository.getById(product_id );
}
MyProductRepository:
#Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Schema of Product table : (product_id, desciption,display_name, qty, amount)
When i try to invoke API by postman
curl --location --request GET 'http://localhost:8080/admin/getProducts/1.
I see it is Caused by: java.lang.NoSuchMethodException: com.Project.OrderProcessing.OrderProcessing.Entity.Product$HibernateProxy$zAdAYVvM.<init>().I am unable to understand reason behind it
Try findById since getById is deprecated. Untested, but something like:
MyProductServiceImp:
#Override
public Optional<Product> findById(Integer productId) {
return productRepository.findById(productId);
}
Product.java
#Entity //make sure this is present
#Getter //from Lombok
#Setter //from Lombok
public class Product {
#Id //need this
#GeneratedValue //need this to auto-generate
private Integer id;
private String description;
//the rest: displayName, quantity, amount...
}
Your #Repository interface looks fine. There are different variations for your controller depending on what you need to do. But for now, just try calling your service method so you know you get the result back from the DB and work from there.
Camel-case your variables in general for consistency. Then you can use the Spring conventions in interfaces for repositories so your method could look like findAllByDisplayName() instead of findAllByDisplay_Name() and Spring will handle the query for you.
Also note that presumably, you're not getting all products with one product ID, right? So it should just be called getProduct or findProduct or getProductById or findProductById.
MyControllerClass:
#RequestMapping("/admin")
#RestController
public class ProductController {
#GetMapping(value = "/getProducts/{id}" , produces ="application/json")
public Optional<Product> display(#PathVariable int id) {
Optional<Product> products = productServiceImp.getProductDetailsbyID(id);
return products;
}
}
MyProductServiceImp :
#Override
public Optional<Product> getProductDetailsbyID(int product_id ) {
Optional<Product> prodresult=productRepository.findById(product_id);
return prodresult;
}
I have used FindbyID in place of GetById and it worked !!
Related
I am experimenting with the library OmniPersistence.
I have a problem using the class org.omnifaces.persistence.model.VersionedEntity. In my code there is a simple entity class City.
#Entity
public class City extends VersionedEntity<Long> {
#Id
#GeneratedValue
private Long id;
private String postalCode;
private String name;
... (getter + setter)
}
There is a REST-Service that exposes the Entity for some client-applications. But every time I want to update an object a javax.persistence.OptimisticLockException is thrown. The problem is that the version attribute is always null. A look in the code of VersionedEntity revealed that there is no setter method, but a comment
// No setter! JPA takes care of this.
I do understand the intention of the absence of the setter method but that is the reason for the exception.
Question
Is my architecture so poor (exposing the entity class in a web-service) or is it maybe reasonable to add a setter method although JPA should handle the value/manipulation of the #Versioned attribute?
Edit (as requested by the comment)
My update method is basically the one in OmniPersistence' BaseEntityService. My service class looks like the following.
#Stateless
public class CityService extends BaseEntityService<Long, City> {
public Long count() {
return super.createLongQuery("select count(c) from City c").getSingleResult();
}
}
My controller is the REST endpoint.
#Path("city")
public class CityEndpoint {
#Inject
private CityService cityService;
#GET #Produces(MediaType.APPLICATION_JSON)
public Response getAll() {
List<City> cities = cityService.list();
return Response.ok(cities).build();
}
#GET #Produces(MediaType.APPLICATION_JSON)
#Path("{id}")
public Response get(#PathParam("id") Long id) {
return Response.ok(cityService.getById(id)).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
public Response create(City city) {
cityService.persist(city);
return Response.created(URI.create(String.format("city/%s", Objects.toString(city.getId())))).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
#Path("update")
public Response update(City city) {
System.out.println(city);
City updated = cityService.update(city);
return Response.ok(updated).build();
}
#GET
#Path("count")
public Response count() {
return Response.ok(cityService.count()).build();
}
}
The JPA specification document provides an important hint that you must not manipulate the #Version attribute, see section 3.4.2, on page 90
An entity may access the state of its version field or property or
export a method for use by the application to access the version,
but must not modify the version value.
and
The version attribute is updated by the persistence provider runtime
when the object is written to the database.
So the comment (”No setter! JPA takes care of this.“) you find in VersionedEntity is absolutely reasonable. In essence, you should not change (or null) the #Version attribute from higher application levels.
In your case, it seems, you must compensate the ”lost“ (=nulled) version effect, eg by introducing a DTO for City. Otherwise, you will always run into an OptimisticLockException.
I have an application that I am working on, I have it connecting to my mongoDB database and everything, but doing either a findAll or findById method always returns an empty brackets {}
I think its "working" because I have a total of 5731 records in my mongodb database, and when doing a "findAll()" it returns 5731 open brackets.
I did some research and found some similar posts but most said to make cure my collection is correct (which it is).
here is my custom variable class "stockIncome.java"
#Document(collection = "IncomeStatement")
public class stockIncome {
#Id
String id;
spring.data.mongodb.uri=mongodb+srv://XXX_XXX_XXX(Hiding my username/password/hostname)?retryWrites=true&w=majority
my controller file
#RestController
public class stockController {
public StockRepository stockRepository;
public stockController(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
#GetMapping("/all")
public List<stockIncome> findStocks(){
return stockRepository.findAll();
}
#GetMapping("/stocks/{id}")
public Optional<stockIncome> findStock(#PathVariable final String id){
return stockRepository.findById(id);
}
}
and my repository
public interface StockRepository extends MongoRepository<stockIncome, String> {
}
any ideas to help me debug this?
Fixed!
the solution was to add public to the id in my variable constructor.
changed
string id
to
public string id
and now its no longer empty!
I try declare and use deleteBy method with spring-data-jdbc repositories like this
public interface TokenRepository extends CrudRepository<OpToken, Long> {
void deleteByBreed(Long breed);
}
When i tried to call method
private TokenRepository tokenRepository;
...
...
tokenRepository.deleteByBreed(123L);
I got exception: MethodNotFoundException:
java.lang.NoSuchMethodException: void.()
I decided, that delete method should return number of rows it processed. So, i rewrite my repository interface like this
public interface TokenRepository extends CrudRepository<OpToken, Long> {
long deleteByBreed(Long breed);
}
But now i have got another exception:
org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorrect column count: expected 1, actual 4
It's looks like method return entity or list of entities it try to delete. But i do not need them.
How can i declare this method in my case?
By Entity looks like this:
#Data
public class OpToken implements Persistable<Long> {
#Transient
private boolean newEntity;
#Id
#Column("jti")
private Long jti;
#Column("breed")
private Long breed;
#Column("id_account")
private Long idAccount;
#Column("exp")
private Date exp;
#Override
public Long getId() {
return jti;
}
#Override
public boolean isNew() {
return newEntity;
}
}
With the current version derived delete queries aren't supported yet. Watch https://github.com/spring-projects/spring-data-jdbc/issues/771 to get notified when this changes.
The solution provided by #MadMax is correct: used a dedicated query:
#Modifying
#Query("delete from account.op_token t where t.breed = :breed")
void deleteByBreed(#Param("breed") Long breed);
It's only one worked
#Modifying
#Query("delete from account.op_token t where t.breed = :breed")
Long(or void) deleteByBreed(#Param("breed") Long breed);
But the query only works as long as your entity has no value objects (which are not #Embedded). I had a Customer entity with value objects and so I used this method in my service (customerId is unique):
#Transactional
public void deleteByCustomerId(final String customerId) {
customerRepository.findByCustomerId(customerId).ifPresent(customerRepository:delete);
}
I study the spring+Hibernate bundle there Is an entity:
public class PersonEntity {
private Long id;
private String name;
private Integer age;
private City city;
private Countrycountry;
...
}
I need to perform filtering. data in this table to display in the browser window. I was thinking of making an implementation in the service of the following methods:
.findByName(name);
.findByNameAndAge(name, age);
.findByNameAndAge(name, age, city);
.findByNameAndAge(name, city);
...
But it turns out that there are too many options for methods. How to make one universal method, i.e. something like a collection in which you can add as many parameters as you need. I started reading on this issue and got completely confused. Somewhere they write about #Filter, somewhere about Hibernate Search, there is also Spring Data Elasticsearch. Tell me the easiest and most relevant way to implement this. If there are links to real examples, I would be very grateful.
Dao:
public interface PersonDao extends GeneralDAO<PersonEntity>{
public List<PersonEntity> searchName(String name);
public List<PersonEntity> searchAllFields(
String name,
Integer age,
City city);
}
GeneralDAO describes all standard methods such as get, save, etc. Repository:
#Repository
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
List<PersonEntity> findByNameIgnoreCase(String name);
List<PersonEntity> findByNameAndAgeAndCity(
String name,
Integer age,
City city);
}
Service
#Service
#Transactional
public class PersonService implements PersonRepository {
#Autowired
private PersonRepository personRepository;
...
описание всех стандартных методов чтения-записи в БД
#Override
public List<PersonEntity> searchName(String name) {
return productTypeRepository.findByNameIgnoreCase(name);
}
#Override
public List<PersonEntity> searchAllFields(
String name,
Integer age,
City city) {
return personRepository.findByNameAndAgeAndCity(
name,
age,
city);
}
}
In the ad and call controller:
#Autowired
private PersonService personService;
...
personService.searchAllFields(...);
The searchName method works fine, but searchAllFields doesn't. It always returns an empty list, even if I specify one name, the rest = null
I tried to change the method in the service:
List<PersonEntity> findByNameIsNullAndAgeIsNullAndCityIsNull
Spring responds with an error:
"Error creating bean with name personRepository. At least 1 parameter(s) provided but only 0 parameter(s) present in query".
searchAllFields Method is returning an empty list because it contains findByNameAndAgeAndCity which means all the parameters are mandatory and the condition between them is AND so better change to OR (findByNameOrAgeOrCity) so that if you pass single value like name and rest = null then also you will get data and vice-versa.
You should really consider using Criteria API since you are using Spring & Spring Data, you can use JPA Specifications as a complete example see the following example:
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
....
public interface PersonRepository extends JpaRepository<PersonEntity, Long>, JpaSpecificationExecutor {
}
// Notice the the second extended interface JpaSpecificationExecutor ^
in service:
import org.springframework.data.jpa.domain.Specification;
....
public List<PersonEntity> list(PersonEntityFilter personFilter) {
List<PersonEntity> filteredPersons = personsRepository.findAll
(Specification.where(PersonEntitySpecs.findByFilters(personFilter)));
return filteredPersons;
}
PersonEntityFilter is the payload coming from your controller submitted by your clients or your UI and it is a simple class that groups all fields you want to filter by
public class PersonEntityFilter {
private String name;
private Integer age;
private City city;
// getters & setters
}
PersonEntitySpecs is where you put your specs (criteria query logic)
public class PersonEntitySpecs {
public static Specification<PersonEntity> findByFilters(PersonEntityFilter personEntityFilter) {
return (root, query, cb) -> {
final Collection<Predicate> predicates = new ArrayList<>();
if (personEntityFilter.getName() != null) {
predicates.add(cb.like(root.get("name"), "%" + personEntityFilter.getName() + "%")));
}
if (personEntityFilter.getAge() != null) {
predicates.add(cb.equal(root.get("age"), personEntityFilter.getAge()));
}
if (personEntityFilter.getCity() != null) {
Join<PersonEntity, CityEntity> personCityJoin = root.join("city");
predicates.add(cb.equal(personCityJoin.get("id"), personEntityFilter.getCity().getId()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
In my code, I fetch an entity and try to delete it, using the interface ProductRepository which extends JpaRepository:
#Repository
public interface ProductRepository extends JpaRepository<Product, Long> {}
Code, and System.out.println() output from code:
#PostMapping("/admin/product/delete")
public String deleteProduct(
#RequestParam String productId
){
Long id = Long.parseLong(productId);
System.out.println("long id from deleteProduct: " + id);
productService.deleteProductById(id);
return "redirect:/product";
}
sysout:
long id from deleteProduct: 38
Service method deleteProductById():
public void deleteProductById(long productId){
Product product = productRepository.getOne(productId);
System.out.println("Product:\n" + product);
productRepository.delete(product);}
sysout from deleteProductById:
Product: Product{id=38, productName='zip',
producer=lightmarket.mvc.model.domain.Producer#182a383}
But the entity is not deleted...
I must point out that all other CRUD operations work. Create, Update, Read - all are alright! Only 'delete' is not working.
JpaRepository extends CrudRepository, so you can use:
Crudrepository.deleteById() which in the case of your generic types, takes a long (See the documentation at ).
So, in your service, you would have something like:
#Service
public class ProductService {
#Autowired
ProductRepository repo;
public void deleteProductById(Long id) {
System.out.println("Deleting product with id: " + id);
// USE deleteById(Long id) and directly pass the id
// Defined in CrudRepository
repo.deleteById(id);
// DON'T use delete() and pass a product
//repo.delete(product);
}
}
Then your controller calls service.deleteProductById() from the service like normal
See documentation: https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html?is-external=true#deleteById-ID-
Maybe there is something wrong with your equals and hashcode of the Product class and the object you load from database is not the same you are trying to delete.
A better way for deleting a product by id would be using the id instead of the product object.
You could replace
productRepository.delete(product);
with
productRepository.delete(productId);
where productId is of type Long.
This would also avoid the additional query.