简体   繁体   中英

Spring Data Repository with ORM, EntityManager, @Query, what is the most elegant way to deal with custom SQL queries?

I've been learning Spring Boot framework recently, according to my research, I can either

  • Include Hibernate JPA implementation in maven dependency list, extends CrudRepository and add custom methods

or

  • Use EntityManager from Hibernate to execute custom SQL query

or

  • Use annotation @org.springframework.data.jpa.repository.Query

Questions

  • Do they cache data I queried?

  • Do they have the same performance?

  • Which one is the most elegant way to go with custom SQL queries? If none of them is, is there any other way to deal with custom SQL queries when using Spring framework (not limited to Repository pattern) and Hibernate?

1. Repository approach

@Entity
@Table(name = Collection.TABLE_NAME)
public class Collection {
    public final static String TABLE_NAME = "collection";
    ...
}

public interface CollectionRepository extends CrudRepository<Collection, Integer> {
}

public class CollectionRepositoryImpl {
    @Autowired
    private CollectionRepository collectionRepository;

    public List<Collection> findCollectionsForSeason(int season, int count) {
        List<Collection> results = new ArrayList<>();

        for (Collection c : collectionRepository.findAll()) {
            if (c.getSeason() == season) {
                results.add(c);
            }
        }

        return results;
    }
}

2. EntityManager approach

public class xxx {
    private EntityManager em;
    ...

    public List<Collection> findCollectionsForSeason(int season, int count) {
        String sqlString = String.format("SELECT * FROM `collection` WHERE `season`=%d LIMIT %d", season, count);
        return this.em.createNativeQuery(sqlString).getResultList();
    }
}

3. @Query approach

NOTE: this code snippet below is wrong, but I can't figure out how to do it correctly. So it would be nice if someone can help me to implement native query with named parameters and limit clause (mysql).

public interface CollectionRepository extends CrudRepository<Collection, Integer> {
    @Query(value = "SELECT * FROM `collection` WHERE `season`=%d LIMIT %d", nativeQuery = true)
    List<Collection> findCollectionsForSeason(int season, int count) {
    }
}

Thanks!

  1. You can cache the data using each of the above-mentioned methods. Because it's a different thing. You need to use for example Ehcache or Redis and provide @Cacheable annotation on methods you want to have being cached.

  2. Pretty similar, just make sure you don't mess things up. I would rather go with JPARepository and native queries, but it's a matter of personal preference. You can check a few articles, for example: http://wiki.aiwsolutions.net/2014/03/18/spring-data-versus-traditional-jpa-implementation/

  3. For this: @Query(value = "SELECT * FROM collection WHERE season=%d LIMIT %d", nativeQuery = true) , you can create JPARepository method instead of native query. Something like that:

    findBySeason(Object season, Pageable pageable);

    Read more here: http://docs.spring.io/spring-data/jpa/docs/1.4.3.RELEASE/reference/html/jpa.repositories.html

All the three ways use the underlaying Hibernate implementation to execute the native query and grab the result (in your first implementation you're retrieving every single entity, so it'll be worse in performance). Keep in mind you might mix all of them in one repository implementation, no need to follow the same pattern for one repository.

Personally, I tend to use your third approach, which in my opinion is the cleanest way as it let's Spring handle the query without the need of your code dealing with the Entity Manager.

public interface CollectionRepository extends CrudRepository<Collection, Integer> {
    @Query(value = "SELECT * FROM collection WHERE season=:season LIMIT :count", nativeQuery = true)
    List<Collection> findCollectionsForSeason(@Param("season") int season, @Param("count") int count);
}

Just keep in mind when implementing native queries that the columns being returned must match the names for the mapping, if you're converting them to entities.

Whenever you need to implement a more complex query which should be processed at runtime, keep in mind you'll be able to provide your own repository implementation extending the given interface.

Don't do any of those things. Instead of extending CrudRepository extend PagingAndSortingRepository or (as you are using JPA) JpaRepository . How to work with that and how to limit results is explained in the reference guide .

public interface CollectionRepository extends PagingAndSortingRepository <Collection, Integer> {}

Then add a finder method that takes your arguments and a Pageable argument.

List<Collection> findByReason(String reason, Pageable page);

Now in your service you can do something like

return collectionRepository.findByReason(reason, new PageRequest(0, count));

If you need more flexibility for your queries you can always combine the paging approach with a Specification .

public interface CollectionRepository 
    extends CrudRepository<Collection, Integer>,
            JpaSpecificationExecutor<Collection> {}

Then in your service

return collectionRepository.findAll(new YourSpecification(), new PageRequest(0, count));

That allows for very flexible query generation (we use a general implementation to serve most of our searchable data tables).

Basically it comes down to working with the framework (and understanding it) instead of working around it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM