I'm attempting to call a MS SQL server stored procedure. Im using spring-boot, JPA 2.1, hibernate.
The database has a table with isbn, title, author, description and the stored procedure i'm trying to call takes one in parameter(isbn) as a string and returns only the title.
I get the following error:
org.hibernate.procedure.ParameterStrategyException:
Attempt to access positional parameter [2] but ProcedureCall using named parameters
Anyone got a solution for this or know what the error means? I have also tried other combinations of annotations.
Book.java
#Entity
#NamedStoredProcedureQuery(
name = "bookList",
resultClasses=Book.class,
procedureName = "dbo.list_books",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "isbn", type = String.class)
})
public class Book {
#Id
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
BookRepository.java
#Repository
public interface BookRepository extends CrudRepository<Book, Long> {
#Procedure
Iterable<Book> list_books(String arg);
}
BookService.java
#RestController
#RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
public class BookService {
#Autowired
protected BookRepository bookRepository;
#RequestMapping
public Iterable<Book> books(){
return bookRepository.getBooks("1111111");
}
I didn't solve the problem with annotations, i worked around it with an EntityManager and a StoredProcedureQuery.
The Book.java is the same but without the #NamedStoredProcedureQuery. I removed the repository and rewrote the service like this:
#RestController
#RequestMapping("/api")
public class BookService {
#RequestMapping(value = "/books",
params = {"isbn"},
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Book> getByIsbn(#RequestParam(value = "isbn") String isbn){
StoredProcedureQuery sp = em.createStoredProcedureQuery("name.of.stored.procedure", Book.class);
sp.registerStoredProcedureParameter("isbn", String.class, ParameterMode.IN);
sp.setParameter("isbn", isbn);
boolean result = sp.execute();
if (result == true) {
return sp.getResultList();
} else {
// Handle the false for no result set returned, e.g.
throw new RuntimeException("No result set(s) returned from the stored procedure");
}
}
}
It is now possible to call this endpoint with a stringquery like: http://localhost/api/books?isbn=1111111
Related
I build simple REST service, I want to get data key from database based id but, when I running no result showing in postman, how can I fix it?
This is My Controller
//Get Key
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
String getKey(#PathVariable int company_id) {
String encKey = null;
gkrepo.getKeyByCompanyid(company_id);
return encKey;
}
This is My Repository
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer>
{
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
public void getKeyByCompanyid(Integer companyid);
}
The problem here is the fact, that you ignore the return value of the repository method and return null.
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
String getKey(#PathVariable int company_id) {
String encKey = null;
gkrepo.findOneByCompanyId(company_id);
return encKey; //YOU RETURN NULL HERE
}
What you need to do is to return the key from the KeyEntity object.
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
String getKey(#PathVariable int company_id) {
return gkrepo.getKeyByCompanyid(company_id).getKey();
}
You also need an additional method in your repository.
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer> {
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
public void findOneByCompanyId(Integer companyid);
}
You should change the method in repository as done below. Try using this.
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer>
{
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
public KeyEntity findByCompanyId(Integer companyid);
}
Your Controller Should be :
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
String getKey(#PathVariable int company_id) {
String encKey = null;
KeyEntity keyEntity = gkrepo.getKeyByCompanyid(company_id);
return keyEntity.getKey;
}
Your Repository should be like:
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer>
{
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
public KeyEntity findByCompanyId(Integer companyid);
}
You can try to change your method it as it bellow
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer>
{
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
public KeyEntity findByCompanyId(Integer companyid);
}
if you use this code , you must change code it as it bellow
gkrepo.findByCompanyId
instead of
gkrepo.getKeyByCompanyid(company_id);
OR
public interface GenerateKeyRepository extends JpaRepository<KeyEntity, Integer>
{
#Query(value= "SELECT * FROM tb_key", nativeQuery = true)
List<KeyEntity> getAll();
#Query(Select k from KeyEntity k where companyid = :companyid)
public KeyEntity getKeyByCompanyid(#Param("companyid") Integer companyid);
}
You are using Spring Data JPA. Your repository interface inherits various methods from the extended JpaRepository interface. That is the whole point of it.
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
There is then no read to write a query method:
#RestController
public class myController{
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
public KeyEntity getKey(#PathVariable("company_id") int companyId) {
return gkrepo.findById(companyId); //inherited method
}
}
Furthermore, if you enable Spring Data JPA's web extension then there is no need to call the repository at all as the Entity will be resolved automatically from the path variable:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web
The DomainClassConverter lets you use domain types in your Spring MVC
controller method signatures directly, so that you need not manually
lookup the instances through the repository
#RestController
public class myController{
#RequestMapping(path="/getkey/{company_id}", method = RequestMethod.GET)
KeyEntity getKey(#PathVariable KeyEntity keyEntity) {
return keyEntity;
}
}
I have an abstract class "Agent"
and 3 other subclasses "Developer", "Support" and "Admin"
Here is the code source of "Agent" :
#Entity
#Table(name = "agents")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "role", discriminatorType =
DiscriminatorType.STRING, length = 3)
public abstract class Agent implements Serializable {
#Id
#GeneratedValue
private int id;
private String name;
private String lastName;
.........}
The code source of "Developer" classe
#Entity
#DiscriminatorValue("dev")
public class Developer extends Agent {
/*------------------- constructors -------------------*/
public Developer() {
super();
}
public Developer(String name, String lastName, ....) {
super(name, lastName, ...);
}
}
The rest of the classes "Admin", "Supprort" has the same form.
Here is my controller code Admin controller :
#Controller
public class AdminController {
/*------- attributs -------*/
#Autowired
#Resource(name = "admin")
private IAdmin iAdmin;
#Autowired
private AgentValidator agentValidator;
........
#RequestMapping(value = "/admin/save/developer", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Developer developer, BindingResult result) {
Agent admin = iAdmin.profile(Integer.parseInt(admin_id));
developer.setConfirmPassword(confirmPassword);
agentValidator.validate(developer, result);
if (result.hasErrors()) {
model.addAttribute("action", action);
return "formAgents";
}
if (action.equals("create")) {
iAdmin.createAgent(admin, developer);
} else {
iAdmin.updateAgent(admin, developer);
}
return "redirect:/admin/show/agents";
}
.......
As you see this function create and update the developer account, But i need to save all agents types [admin, developer, support], I try this :
public String createAgentAccount(Model model, ... , #ModelAttribute("agent") Agent developer, BindingResult result) {.....}
But i get this error :
Tue Aug 22 19:54:03 WEST 2017
There was an unexpected error (type=Internal Server Error, status=500).
Failed to instantiate [com.GemCrmTickets.entities.Agent]: Is it an abstract class?; nested exception is java.lang.InstantiationException
I know that is impossible to instanciate an abstract Class. I don't want to do a function for each type of agent, One for all will be the best solution. So i need your help please. And thank you.
Your answer is one word. Use Ad hoc polymorphism, which means you can have multiple methods of createAgentAccount, then in each of them call an other method to handle the details.
UPDATE
This is what I think you want
#RequestMapping(value = "/admin/save/developer", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Developer developer, BindingResult result) {
return createAgentAccount(model, admin_id, confirmPassword, action, developer, result);
}
#RequestMapping(value = "/admin/save/support", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Support support, BindingResult result) {
return createAgentAccount(model, admin_id, confirmPassword, action, support, result);
}
private String createAccount(Model model, String admin_id, String confirmPassword, String action, Agent agent, BindingResult result) {
Agent admin = iAdmin.profile(Integer.parseInt(admin_id));
agent.setConfirmPassword(confirmPassword);
agentValidator.validate(agent, result);
if (result.hasErrors()) {
model.addAttribute("action", action);
return "formAgents";
}
if (action.equals("create")) {
iAdmin.createAgent(admin, agent);
} else {
iAdmin.updateAgent(admin, agent);
}
return "redirect:/admin/show/agents";
}
I have a Spring-Boot API with the endpoint below. It is throwing a Null Pointer Exception on a Spring Data JPA findAll query; when I comment out this line, I get no errors. It seems that I am getting a null result from the repository query, but I know the data is there from querying the DB directly. I cannot understand why I'm getting a null for topicsLookup variable... Can anyone point me in the right direction?
Resource:
#RequestMapping(value = "/lectures/{lectureId}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, SpeakerTopicLectures> getLecture(#PathVariable Long lectureId){
Long requestReceived = new Date().getTime();
Map<String, SpeakerTopicLectures> result = new HashMap<>();
log.debug("** GET Request to getLecture");
log.debug("Querying results");
List<SpeakerTopicLectures> dataRows = speakerTopicLecturesRepository.findBySpeakerTopicLecturesPk_LectureId(lectureId);
// This line throws the error
List<SpeakerTopic> topicsLookup = speakerTopicsRepository.findAll();
// Do stuff here...
log.debug("Got {} rows", dataRows.size());
log.debug("Request took {}ms **", (new Date().getTime() - requestReceived));
// wrap lecture in map object
result.put("content", dataRows.get(0));
return result;
}
Java Bean:
#Entity
#Table(name = "speaker_topics")
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class SpeakerTopic implements Serializable {
#Id
#Column(name = "topic_id")
private Long topicId;
#Column(name = "topic_nm")
private String topicName;
#Column(name = "topic_desc")
private String topicDesc;
#Column(name = "topic_acm_relt_rsce")
private String relatedResources;
}
Repository:
import org.acm.dl.api.domain.SpeakerTopic;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpeakerTopicsRepository extends JpaRepository<SpeakerTopic,Long> {
}
The most likely cause is that speakerTopicsRepository is itself null, which is probably caused by forgetting to autowire it, e.g.
public class YourController {
#Autowired private SpeakerTopicsRepository speakerTopicsRepository;
#RequestMapping(value = "/lectures/{lectureId}",method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, SpeakerTopicLectures> getLecture(#PathVariable Long lectureId) {
// your method...
}
}
the repository is not autowired in your controller.
Try using
#Repository
#Transactional
public interface SpeakerTopicsRepository extends JpaRepository<SpeakerTopic,Long> {
// Your Repository Code
}
I think #Repository & #Transactional is missing. Please use it.
It was missing #Autowired for me.
I'm newcomer with Spring Boot and now after some lessons I'm trying to create RESTful+Hibernat+MySQL App. I've created:
Entity
#Entity
#Table(name = "customers")
#NamedQueries({
#NamedQuery(name = "Customers.findAll", query = "SELECT c FROM Customers c")})
public class Customers implements Serializable {...};
Controller
#RestController
#RequestMapping("/customers")
public class CustomersController {
#RequestMapping(method = GET)
public List<Object> list() {
return null;
}
#RequestMapping(value = "/{id}", method = GET)
public Object get(#PathVariable String id) {
return null;
}
#RequestMapping(value = "/{id}", method = PUT)
public ResponseEntity<?> put(#PathVariable String id, #RequestBody Object input) {
return null;
}
#RequestMapping(value = "/{id}", method = POST)
public ResponseEntity<?> post(#PathVariable String id, #RequestBody Object input) {
return null;
}
#RequestMapping(value = "/{id}", method = DELETE)
public ResponseEntity<Object> delete(#PathVariable String id) {
return null;
}
}
Repository
public interface CustomersRepository extends JpaRepository<Customers, Long> {
public Optional<Customers> findOneByEmail(String email);
}
Finally ma App runs, and when I open the link in my browserand open the link localhost:8089 I see the following:
{
"customerses" : {
"href" : "http://localhost:8089/customerses{?page,size,sort}",
"templated" : true
}
}
}
My question is why I have customerses on the end of controller name and who's adding this extension?
Thank you in advance.
It's done on purpose by Spring Data Rest - it assumes that entity name is singular, so it automatically makes it plural for the endpoint.
You just need to rename your table and entity to singular - Customer.
Here is a good explanation why it should be singular - SO answer.
Environment:
spring-data-mongo: 1.7.0.RC1
mongo-java-driver: 3.2.2
Document:
#Document(collection = "products")
public class Product {
#Id
private String sid;
private String name;
private Long vendor;
(...)
}
Repository:
public interface ProductRepository extends MongoRepository<Product, String> {
Product findByName(String productName);
}
My goal is to intercept any query performed on the Product collection and add a predicate or a specification without modifying the repository or the need to implement the method findByNameAndBelongsToVendorList.
I need this interceptor or aspectJ because I have multiple methods like:
Page<Product> findAll(Pageable page);
List<Product> findByCategory(String category, Pageable pageRequest);
(...)
Goal
findByName // perform a filter by name (explicit)
// and a filter by vendor (injected via inteceptor or aspecJ)
Avoid doing this
#Repository
public class ProductRepositoryCustomImpl implements ProductRepositoryCustom {
#Autowired
private MongoTemplate template;
public Product findByNameAndBelongsToVendorList(String name, List<Long> vendors, Pageable pageRequest) {
Criteria criteriaVendor = Criteria.where("vendors").in(vendors);
Query query = new Query(criteriaVendor);
query.with(pageRequest);
return template.findOne(query, Product.class);
}
}
Aspects should do the trick.
#Aspect
public class YourAspect {
#Autowired
private MongoTemplate template;
#Pointcut("execution(public * findByName(..))")
private void findByName() {
}
#Pointcut("within(com.leonel.repository.ProductRepository)")
private void repository() {
}
#Around("repository() && findByName()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name = (String) args[0];
Criteria newCriteria = YOUR NEW LOGIC HERE;
Query query = new Query(newCriteria);
return template.find(query, Your.class);
}
I would recommend against it though, as it introduces a bit of magic to your code and manipulating queries should not be a concern of aspects.
What is the reason you want to avoid having multiple find methods in your repository?