How to get column names in result set in Spring data JPA - java

I have simple procedure which lists out users. I am using #NamedStoredProcedureQueries for procedure declaration and used EntityManager.createNamedStoredProcedureQuery for StoredProcedureQuery.
It returns the result properly but I need column name so that I will know which value is for which column.
My code goes something like this
Entity Class
#Entity
#NamedStoredProcedureQueries({ #NamedStoredProcedureQuery(name =
"sGetUserList", procedureName = "sGetUserList", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "user_id", type =
Integer.class) })
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String email;
//getters and setters
}
Custom Repositoty
public interface UserRepositoryCustom {
List<?> testProc() ;
}
Repository
public interface UserRepository extends JpaRepository<User, Long>,
UserRepositoryCustom{
}
Repository Implementation
public class UserRepositoryImpl implements UserRepositoryCustom{
#PersistenceContext
EntityManager em;
public List<Object> testProc() {
StoredProcedureQuery q = em.createNamedStoredProcedureQuery("sGetUserList");
q.setParameter("user_id", 1);
List<Object> res = q.getResultList();
return res;
}
}
I need result with column names.

You can get the column names along with their values in a Map. i.e Map<'column-name', value>.
Query query = entityManager.createNativeQuery("{call <<Your procedure>>}");
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
This will be very helpful in a places where you want to use column names as a placeholder in HTML where value will replace it in runtime.

Here , I have written a method from you can get JSON from you can get key value pair of column and value
#Transactional
#Component
public class CustomRepository<T> {
#Autowired
private EntityManager em;
private ObjectMapper mapper = new ObjectMapper();
public List<T> getResultOfQuery(String argQueryString,Class<T> valueType) {
try {
Query query = em.createNativeQuery(argQueryString);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
List<T> resultList = result.stream()
.map(o -> {
try {
return mapper.readValue(mapper.writeValueAsString(o),valueType);
} catch (Exception e) {
ApplicationLogger.logger.error(e.getMessage(),e);
}
return null;
}).collect(Collectors.toList());
return resultList;
} catch (Exception ex) {
ApplicationLogger.logger.error(ex.getMessage(),ex);
throw ex;
}
}
}
The Only condition is that your query and pojo attribute name should be same

I'm not sure I understand what you are trying to do here. If you want to get all the users using Spring data you shouldn't be implementing your UserRepository. Spring Data does this for you.
In fact the JpaRepository already has the method you need.
List<User> findAll();
You can just call this to get a list of all your users and won't need to worry about the column names.
Just inject your repository where you need it and call the method to get all users:
#Autowire
UserRepository userRepository;
List<Users> allUsers = userRepository.findAll();
EDIT: If there is a particular reason you want to use stored procedures though there is a Spring Data way of doing this without implementing UserRepository yourself. You can do this by defining the following method:
public interface UserRepository extends JpaRepository<User, Long>{
#Procedure(name = "sGetUserList")
List<User> sGetUserList(#Param("user_id") Integer userId);
}
Again there shouldn't be any issue with resolving column names with this method.

Related

spring boot Cannot invoke Repository because Repository is null

public class UserList {
private String id;
private String email;
private String userType;
private String rolls;
private String partner;
private Integer customersLinked;
private String position;
private String status;
#Autowired
ICustomerRepository customerRepository;
public UserList (Users user){
this.id = user.getId();
this.email = user.getEmail();
this.userType = user.getUserType();
this.rolls = user.getRolls();
this.partner = user.getPartner();
List<Customer> customersLinked = customerRepository.findAllByLinkedUsersIn(user.getId());
this.customersLinked = 0;
this.position = user.getPosition();
this.status =user.getStatus();
}
//Getter and Setter
}
This class is used as a list in the frontEnd ,get specific data ,not send all the data
#RequestMapping(value = "usersLinked/{id}/{type}", method = RequestMethod.GET)
public Object getUsersLinkedById(#PathVariable("id") String id,#PathVariable("type") Integer type) {
List<String> users = null;
switch (type) {
case 0:
users = usersRepository.findAll().stream().map(m -> m.getId()).collect(Collectors.toList());
break;
}
//Add userList
List<UserList> userList = new ArrayList<>();
if(users != null)
{
users.forEach(userId ->
{
Optional<Users> user = this.usersRepository.findById(userId);
userList.add(new UserList(user.get()));
});
}
return userList;
}
}
As you can see from above I am calling al the data from the user repository and sending it the list
My customer repository
public interface ICustomerRepository extends MongoRepository<Customer, String> {
Customer findByBusinessInformation_businessName(String businessName);
List<Customer> findByBusinessInformation_partnerAssigned(String partnerAssigned);
#Query("{ 'LinkedUsers' : ?0 }")
Customer findByLinkedUsers(String id);
List<Customer> findAllByLinkedUsersIn(String id);
}
In the userList I get the error when I add the logic wityh the customerRepository ,without the repository there everything is working(Want to use the repository to get an array of customer and then get the size() of the array and add it to linkedCustomers). Am I missing sommething
You are trying to inject the field customerRepository using Autowired annotation, but your class is not injectable.
You can add an annotation #Repository on your class UserList
Or use constructor injection (better way to inject your beans)
You're probably missing the #repository annotation on top of your repository class.
Another unrelated word of advice:
In your controller you use findAll and filter in java to keep only the ids.
Then you go to the same repository and perform another query per user-id from above.
This is a causing you to create multiple database calls which are one of the most expensive operations you can do, when you already have all your data from the first single query...
Also if you're only looking at the bottom part of the function you don't event need a query per each user-id (when you have a list of user ids as input), you can create a query that uses the 'in' convention and pass a list of user-ids to create a single db call.
First of all I would get rid of #Autowired ICustomerRepository customerRepository; in UserList class. It doesn't belong there. The counting of linked customers should be executed in ICustomerRepository and the result to be passed into UserList via the constructor.
e.g.
public class UserList {
private String id;
private String email;
private String userType;
private String rolls;
private String partner;
private Long customersLinked; //better use Long instead of Integer
private String position;
private String status;
// constructor takes the number of linked customers as parameter
public UserList (Users user, Long customersLinked ) {
this.id = user.getId();
this.email = user.getEmail();
this.userType = user.getUserType();
this.rolls = user.getRolls();
this.partner = user.getPartner();
this.customersLinked = customersLinked;
this.position = user.getPosition();
this.status =user.getStatus();
}
//Getter and Setter
}
and then create the count query in ICustomerRepository
e.g.
public interface ICustomerRepository extends MongoRepository<Customer, String> {
//other methods
Long countByLinkedUsersIn(String id); //not so sure if this query works in mongo
}
and finally in your controller
Optional<Users> user = this.usersRepository.findById(userId);
Long count = this.usersRepository.countByLinkedUsersIn(userId);
userList.add(new UserList(user.get(), count));
P.S. I have a doubt for the query method: Long countByLinkedUsersIn(String id);. Usually when repository methods have "In" in their names, countByLinkedUsersIn, then it is expected as parameter a List and not a single user id. However if your previous method List<Customer> findAllByLinkedUsersIn(String id); worked for you, then this one should work too.

Spring Data JPA Repository findAll() Null Pointer

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.

Spring-data-mongodb intercept query and inject predicate or specification

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?

Call SQL server stored procedure with JPA 2.1 annotations

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

Java JPA "Error compiling the query" when it uses an enum

The following JPA query doesn't compile:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem.id = :sourceSystemId")
p.sourceSystem is the following enum:
public enum SourceSystem {
FIRST(3, "ABC"), SECOND(9, "DEF"), THIRD(17, "GHI");
private int id;
private String code;
...
}
and is mapped in PSA's base class:
public class PsaBase implements Serializable {
#Column(name = "sourceSystemId")
#Enumerated(EnumType.ORDINAL)
protected SourceSystem sourceSystem;
...
}
The query compiles and runs fine if I replace p.sourceSystem.id in the query with something more benign.
Thank you in advance for any help.
It shouldn't compile.
You have to resolve the required enum value manually before passing it as a query parameter:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem = :sourceSystem")
.
public enum SourceSystem {
...
private static Map<Integer, SourceSystem> valuesById = new HashMap<Integer, SourceSystem>();
static {
for (SourceSystem s: values())
valuesById.put(s.id, s);
}
public static SourceSystem findById(int id) {
return valuesById.get(id);
}
}
.
em.createNamedQuery("PSA.findBySourceSystem")
.setParameter("sourceSystem", SourceSystem.findById(sourceSystemId));
EDIT:
Since sourceSystem is annotated as #Enumerated(EnumType.ORDINAL), it's stored in the database as the ordinal numbers of the corresponding enum values, therefore FIRST is stored as 0. JPA doesn't directly support using arbitrary field of the enum value to identify it in the database. If your database schema assumes so, you can do the following trick to decouple state of your object from the database schema:
public class PsaBase implements Serializable {
protected SourceSystem sourceSystem;
#Column(name = "sourceSystemId")
public Integer getSourceSystemId() {
return sourceSystem.getId();
}
public void setSourceSystemId(Integer id) {
this.sourceSystem = SourceSystem.findById(id);
}
... getter and setter of sourceSystem with #Transient ...
}

Categories

Resources