2

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!

viniciussss
  • 4,404
  • 2
  • 25
  • 42
  • better would be to sepearate entityManager logic to a seperate service. You could also move the native query to a JPA Repository. These 2 things are just "clean code" kinda things, so kinda up to you/to the team working on the project. Also SO is not a code reviewing website, you should only be asking specific question about a code that doesnt work. – J Asgarov Feb 09 '22 at 08:17
  • Thanks J Asgarov! I want to move the native query to a JPA Repository. I Tried a lot, but I was not able to do it without creating an entity. The code I posted was just to illustrate what I want to accomplish and to show that I tried. The intention was not to have my code reviewed, but to find out the right way to do it (maybe how to write a JPA Repository without creating an entity). I will edit the post to show how I tried to create the repository. – viniciussss Feb 09 '22 at 14:05
  • Is the app using spring-data-rest? – indybee Feb 13 '22 at 13:15
  • 1
    Yes, app is using spring-data-rest. I edited the tags to include it. – viniciussss Feb 14 '22 at 00:02

2 Answers2

2

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. link1link2
Note: I have not created any Entity classes in my code base.

TriS
  • 3,668
  • 3
  • 11
  • 25
  • 1
    From what I could understand, what you did was the same thing as the last solution I presented (CustomQueryController), but using the PersistenceContext annotation instead of Autowired for the EntityManager. For me it was already working only with Autowired, but with the problem of not exposing the endpoint. The PersistenceContext annotation didn't change this. I appreciate your help and apologize if I misunderstood or missed something. – viniciussss Feb 14 '22 at 01:11
  • Thanks! for the feedback. I might have missed the some information or may be confused by the question. I see that answer from @indybee has helped you. I hope it has solved your query. – TriS Feb 14 '22 at 04:45
  • Still, I will update my answer , expecting it will help someone else. Added `Link` in the response and updated the DB response to `List`. – TriS Feb 14 '22 at 05:05
2

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

indybee
  • 1,507
  • 13
  • 17
  • Solved the 1st and 3rd items on my help request list. Thanks! But for learning, I would also like to know about Kowalski's and Gagarwa's solutions. Especially to know if using ResponseEntity.ok().body(...) is the only way to go. – viniciussss Feb 14 '22 at 00:30
  • 1
    the other solutions seem to be for spring-data-jpa without spring-data-rest. spring-data-rest has a different type of [projections](https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts), and does not seem to support the jpa projections with interfaces - see https://github.com/spring-projects/spring-data-rest/issues/1597 – indybee Feb 14 '22 at 13:20