Expose endpoint with native query without creating entity - java

I want to run some native queries and expose the results through endpoints, but I want to do this without having to create all the entities. I just want the data obtained from the database to be exposed as it comes.
I found some suggestions at: Create spring repository without entity
However, I was not able to make them work. I'm very new to Spring.
I tried Maciej Kowalski's solution like this:
Interface:
public interface CustomNativeRepository {
Object runNativeQuery();
}
Implementation:
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
#Autowired
private EntityManager entityManager;
#Override
public Object runNativeQuery() {
return entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
)
.getResultList();
}
}
However, no endpoints were exposed, as happens when you extend CrudRepository. Should I have done something else with CustomNativeRepositoryImpl? I don't know how to proceed.
I also tried Gagarwa's solution:
RootEntity:
#Entity
public class RootEntity {
#Id
private Integer id;
}
RootEntityRepository:
#Repository
public interface RootEntityRepository extends JpaRepository<RootEntity, Integer> {
#Query(value = """
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9""",
nativeQuery = true)
public Collection<Object> findFromCustomQuery();
}
The endpoint http://localhost:8080/rootEntities was exposed, but when I accessed it, I got the exception: "Relation root_entity does not exist". So, I created the table in the database:
create table root_entity(
id SERIAL PRIMARY KEY
)
After that, the endpoint worked, and returned an empty array (the table root_entity is empty in the database).
I tried to access the endpoint: http://localhost:8080/rootEntities/search/findFromCustomQuery, but I got an exception (Couldn't find PersistentEntity for type class).
Again, I was not able to make it work.
After trying a lot, I made some progress doing the following:
#RestController
public class CustomQueryController {
#Autowired
private EntityManager entityManager;
#GetMapping("/myEndpoint")
#ResponseBody
public Object runNativeQuery() {
return ResponseEntity
.ok()
.body(
entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
).getResultList()
);
}
}
With the code above, I can access http://localhost:8080/myEndpoint and see the result of the query.
However, the endpoint didn't appear in the endpoints listing that is showed in http://localhost:8080/. I had to type it manually in the browser. I would like the endpoint to be exposed in order to see it in Swagger.
Also, I have a feeling that there must be a better way to do this. And I want to learn.
I would like help to:
Get a solution that works and exposes the endpoint.
Understand what I did wrong and how to implement Kowalski's and Gagarwa's solutions.
Being able to expose the endpoint for the last solution (CustomQueryController).
Thanks in advance!

try changing your CustomQueryController to implement RepresentationModelProcessor
public class CustomQueryController implements RepresentationModelProcessor<RepresentationModel<RepositoryLinksResource>> {
and implementing the process method with:
#Override
public RepresentationModel<RepositoryLinksResource> process(RepresentationModel<RepositoryLinksResource> model) {
if (model instanceof RepositoryLinksResource) {
model.add(Link.of( "http://localhost:8080" + "/myEndpoint", "myEndpoint"));
}
return model;
}
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.customizing-json-output.representation-model-processor

I tried the first example that you have put here and it worked for me. But there is a bit of change. I have used PersistenceContext.
To return a Link as response I have used Link of WebMvcLinkBuilder.
Solution
In the below example I have used two tables Employee and Address in PostgresSQL . Both have area_code in common.
Interface
public interface CustomNativeRepository {
List<Object> runNativeQuery(Integer name);
}
Repository
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
Logger logger = LoggerFactory.getLogger(this.getClass());
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Object> runNativeQuery(Integer areaCode) {
Query query = entityManager.createNativeQuery(
"Select e.first_name as name from employees e where e.area_code = ? "
+ "union all " +
"Select a.address as address from address a where a.area_code = ?");
query.setParameter(1, areaCode);
query.setParameter(2, areaCode);
List<Object> response = query.getResultList();
logger.info("Response from database: {}", response);
return response;
}
}
RestEndpoint Layer
#GetMapping(path ="/employee/{areaCode}")
public ResponseEntity<?> getEmployeeByCode(#PathVariable(value = "areaCode") Integer areaCode) throws NoSuchMethodException {
List<Object> response = customCustomerRepository.runNativeQuery(areaCode);
Link link = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getEmployeeByCode(areaCode)).withSelfRel();
return ResponseEntity.ok().body(CollectionModel.of(response, link));
}
Few examples which may help. link1   link2
Note: I have not created any Entity classes in my code base.

Related

Spring Boot/GraphQL and the Number of SQL Statements (N+1 Issue)

I am new to Graphql and looking into creating a proof of concept to see how it works. I am using Spring Boot (2.2.2.RELEASE) and bringing in the graphql-spring-boot-starter.
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>6.0.1</version>
</dependency>
I have setup my graphql schema as a file on the classpath with the following configuration:
type Order {
orderNumber: ID!
customers: [Customer]
items: [Item]
}
type Customer {
customerNumber: ID!
fullName: String
postalAddresses: [PostalAddress]
}
type PostalAddress {
line1: String
line2: String
city: String
stateCode: String
postalCode: String
postalCodeExtension: String
countryCode: String
}
type Item {
itemId: ID!
fullDescription: String
}
type Query {
findOrdersByCustomerNumber(customerNumber: String): [Order]
}
I have created a root Query class:
#Component
public class Query implements GraphQLQueryResolver {
private OrderService orderService;
#Autowired
public Query(OrderService orderService) {
this.orderService = orderService;
}
public List<Order> findOrdersByCustomerNumber(String customerNumber) {
return this.orderService.findOrdersByCustomerNumber(customerNumber);
}
}
And here is my Order resolver:
#Component
public class OrderResolver implements GraphQLResolver<Order> {
private ItemRepository itemRepository;
private CustomerRepository customerRepository;
#Autowired
public OrderResolver(CustomerRepository customerRepository, ItemRepository itemRepository) {
this.customerRepository = customerRepository;
this.itemRepository = itemRepository;
}
public List<Item> item(Order order) {
return itemRepository.findItemsByOrderNumber(order.getOrderNumber());
}
public List<Customer> customers(Order order) {
return customerRepository.findCustomersByOrderNumber(order.getOrderNumber());
}
}
Everything seems to work fine and I can send a graphql request and get a response. It was actually really easy to implement and impressed with how quickly this library allowed me to do that.
However, here is my issue. It's the dreaded n+1 SQL issue.
So, when I have customer with 162 orders.
1 = Driver SQL (get all the orders for the customer number)
162 = Customer SQL (One query is fired off for each order and it's same customer for each select)
162 = Postal Address SQL (One query is fired off for each customer...note, not included in the code snippets)
162 = Item SQL (assume one item per order).
So, that totals 487 SQL queries. And as a result, this has performance implications. I am using straight JDBC to query the database (no JPA or ORM's at the moment).
My questions is how can I get hold of the GraphQL request in the root Query resolver so that can manipulate the SQL for the dependent objects in the graph? When doing some research, I see that in the Node/Javacript world, there a dataloader utility that can help address this issue (https://github.com/graphql/dataloader)
So, I am unclear on how to solve this with this java implementation. If anyone has any suggestions or sample code, that would be really helpful to see if this POC has any merit.

Java spring JPA Repository Entity is not deleted

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.

Getting a list of results by using CRUD Repository

I am new to using CRUD Repository.
I have a database table with three columns:
course_id, name, course
I want to get a list of course_id give name, example,
SELECT id FROM table WHERE name='charmaine';
However, I do not want to do it with query but using crud repository.
There is an error shown in my controller.
May I know there is this error?
My controller
#GetMapping(value = "getting/{name}")
//#ResponseBody
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = CourseService.findIdByName(Name); —> error icon here
return getIds; —> error icon here
}
Service
public List<CourseMaster> findIdByName(String Name) {
return CourseMasterRepo.findByName(Name);
}
Repository
public interface CourseMasterRepo extends CrudRepository<CourseMaster, Long> {
List<CourseMaster> findByName(String Name);
}
You have to autowired service class in your controller like.
#Autowired
CourseService courseService;
#GetMapping(value = "getting/{name}")
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = courseService.findIdByName(Name);
return getIds;
}
if your code is done in java spring you must use hql language in hibernate, that is a interface of sql query.
hql query that use lambda expression is very simple and useful.
For example,
String hql = "FROM Employee E WHERE E.id = 10";
Query query = session.createQuery(hql);
List results = query.list();

Spring Pageable doesn't work for ordering

On the internet I found that Spring can do pagination as well as ordering for a list of data retrieved from the database. Accordingly, I created my test class as following:
#Test
public void testPageable() {
int pageSize = 5;
Sort sort = new Sort( Direction.DESC, "someColumnA" );
Pageable pageable = new PageRequest( 0, pageSize, sort );
List<SomeObject> listOFSomeObject = getDao().getListData( "paramOne", pageable );
}
When I analyze the List I never get ordering of someColumnA in a DESC fashion, although I get back only 5 records which is correct.
Can someone please let me know what I might be doing wrong? Just as an FYI, I am using Hibernate for database access and Spring named query.
EDIT:
Code for getListData()->
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
My Hibernate entity is as follows:
#NamedQueries(value = {
#NamedQuery(name = "SomeRepository.getListData", query = "select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
})
#Entity
#Table(name = "entity_mapped_via_hibernate")
public class EntityMappedViaHibernate implements Serializable {
// Code Omitted on purpose
}
So those of you who are struggling like me the solution to this problem is that you need to have the query inside the Repository. In my case it has to be inside the SomeRepository. So in the code of the repo do the following:
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
#Query("select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
No need to have the NamedQuery inside the EntityMappedViaHibernate. That's it!!
Hope someone find the answer and do not struggle like I did.

Spring Boot and JPA Repository -- how to filter a GET by ID

I'm rewriting an application, this time using a RESTful interface from Spring. I'm presuming that server-side authorization is best. That is:
Supppose user 1 works this REST repository. He/she accesses mysite.com/heroes/1 and gets the (id = 1) hero from the hero table.
User 2 doesn't have rights to see the (id = 1) hero, but could craft a cURL statement to try anyway. I claim the server should prevent user 2 from accessing the (id = 1) hero.
I believe that the server can extract a JWT payload that gives me the user name or password (I put it in there). From that payload the server fetches the user's account and knows what heroes he/she is entitled to see.
I have already accomplished this goal through services and DAO classes. However, the Spring Boot and JPA tutorials I see promote using CrudRepository implementations to reduce coding. I'd like to know how to do my filtering using this technology.
Here is an example from the web:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
}
When mysite.com/heroes/1 is accessed it automagically returns the data from hero (id = 1). I'd like to instruct it to let me choose which ID values to permit. That is, at runtime a query parameter is provided to it through code.
As a test I provided this code:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
#Query ("from Hero h where id in (1, 3, 5)")
public Hero get();
}
However, it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
How do I get to my desired goal?
Thanks, Jerome.
UPDATE 5/13, 5:50 PM
My request is being misunderstood, so I further explain my intent.
Users 1 and 2 are ordinary users, accessing their accounts.
Each user must be confined to his/her own account.
A user can't cheat by crafting requests for other peoples' data.
Thus the need for the server to extract a user ID, or such, from a JWT token and apply it in code to whatever causes the /heroes query to work.
My original example originated with this tutorial. In it the only Java classes are Hero and HeroRepository. There are no explicit classes for DAO, services or controllers. The included Spring libraries let all of the /heroes fetching occur without further coding.
Thanks again for all of your interest and help. Jerome.
You can create a custom #Query, that uses informations (here: id) of the logged in user. With this solution an user have only access to an entity with the same id as he has.
#Override
#Query("SELECT h FROM Hero h WHERE h.id=?1 AND h.id=?#{principal.id}")
public Hero findOne(Long id);
You need to enable SpEl for #Query (link) and create an custom UserDetailsService (link) with custom UserDetails, that contains the id of the user, so you can do principal.id.
In the same way you should secure the findAll() method.
I have created HeroRepository to resolve all the queries up to my understanding.
I'd like to instruct it to let me choose which ID values to permit.
You can achieve the same using.
List<Hero> findByIdIn(List<Long> ids);
Or, if you prefer Query
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
I cannot see your Controller/Service methods, so I am assuming that findOne() is being called. You can prevent it using..
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
OR, if you want more control over your method invocations, you can also use #PreAuthorize from spring-security.
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
Summary
public interface HeroRepository extends CrudRepository<Hero, Long> {
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
// If u want to pass ids as a list
List<Hero> findByIdIn(List<Long> ids);
// Alternative to above one
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
}
PS: Note that I am returning Optional<Hero> from the method. Optional.empty() will be returned if query produces no results. This will force us to check if the value is present before doing any operation, thereby avoiding NullPointerException.
use this code for Controller : -
#RestController
#RequestMapping("/cities")
public class CityController {
private static final Logger logger = LoggerFactory.getLogger(CityController.class);
#Autowired
private CityService cityService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RestResponse find(#PathVariable("id") Long id) {
.
.
}
use below code for Repo :-
public interface CityRepo extends JpaRepository<FCity, Long> {
#Query("select e from FCity e where e.cityId = :id")
FCity findOne(#Param("id") Long id);
}
use below code for service :-
#Service
#Transactional
public class CityService {
#Autowired(required = true)
private CityRepo cityRepo;
public FCity findOne(Long id) {
return cityRepo.findOne(id);
}
}

Categories

Resources