I am trying to use the same query more than once, with different options set by annotations. Similar to:
#EntityGraph(attributePaths = {"books"})
Optional<User> findById(long id);
#EntityGraph(attributePaths = {"courses"})
Optional<User> findById(long id);
Optional<A_Projection> findById(long id);
Is there a way to add a prefix or a postfix to the method name, such that I can have different function signatures but it would be interpreted as same JPA query to avoid using #Query? Such as:
#EntityGraph(attributePaths = {"books"})
Optional<User> findByIdQ1(long id); //Q1,Q2,Q3 prefixes help distinguish these methods
#EntityGraph(attributePaths = {"courses"})
Optional<User> findByIdQ2(long id); //but they mess up the JPA syntax
Optional<A_Projection> findByIdQ3(long id);
As an example, for Projection case, this is a valid syntax:
Optional<A_Projection> findByIdByProjection(long id);
You can use EntityGraphJpaSpecificationExecutor to pass different entitygraph based on your method.
#Repository
public interface UserRepository extends JpaSpecificationExecutor<User>, JpaRepository<User, Long>, EntityGraphJpaSpecificationExecutor<User> {
}
In your service class, you can call findOne with entity graph.
List<User> users = userRepository.findOne(specification, new NamedEntityGraph(EntityGraphType.FETCH, "graphName"))
I found what I was looking for. Apparently, JPA searches for find....By in derived queries. Therefore, you can add whatever between these clauses:
Optional<User> findWithBooksById(long id); //Q1,Q2,Q3 prefixes help distinguish these methods
#EntityGraph(attributePaths = {"courses"})
Optional<User> findWithWhateverById(long id); //but they mess up the JPA syntax
Optional<A_Projection> findById(long id);
From Spring documentation:
Any text between find (or other introducing keywords) and By is considered to be descriptive unless using one of the result-limiting keywords such as a Distinct to set a distinct flag on the query to be created or Top/First to limit query results.
Related
In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)
I have a service that passes in parameters for how much I want to include for navigation properties. Based upon the boolean args it concatenates an entity list to include each required foreign entity.
At runtime I want to include either no navigation entities or many.
What I can't do is daisy chain with .Include().Include as I don't know which and how many to include based around passed in args.
I want to achieve this, but I don't seem to be able to pass in a comma separated entity list. Any ideas?
var res = db.Entity.Include(entityListCommaSeparated).Where(_=>_.ID == ID).FirstOrDefault();
This looks like a repository pattern, and generally gets messy if you want to try and "hide" EF / the DbContext from calling code.
A couple options you can consider:
Down the complexity rabit hole: use a params Expression<Func<TEntity, object>>[] includes in your applicable repository methods, and then be prepared to also pass OrderBy expressions, as well as pagination values when you want to return multiple entities.
THrough the simplicity mirror: Embrace IQueryable as a return type and let the consumers handle Includes, OrderBy's, Counts/Any/Skip/Take/First/ToList, and .Select() as they need.
Option 1:
public Order GetById(int id, params Expression<Func<Order, object>>[] includes)
{
var query = db.Orders.Where(x => x.ID == id);
// This part can be moved into an extension method or a base repository method.
if(includes.Any)
includes.Aggregate(query, (current, include) =>
{
current.Include(include);
}
// Don't use .FirstOrDefault() If you intend for 1 record to be returned, use .Single(). If it really is optional to find, .SingleOrDefault()
return query.Single();
}
//ToDo
public IEnumerable<Order> GetOrders(/* criteria?, includes?, order by?, (ascending/descending) pagination? */)
{ }
// or
public IEnumerable<Order> GetOrdersByCustomer(/* includes?, order by?, (ascending/descending) pagination? */)
{ }
// plus..
public IEnumerable<Order> GetOrdersByDate(/* includes?, order by?, (ascending/descending) pagination? */)
{ }
public bool CustomerHasOrders(int customerId)
{ }
public bool OrderExists(int id)
{ }
public int OrdersOnDate(DateTime date)
{ }
// etc. etc. etc.
Keep in mind this doesn't handle custom order by clauses, and the same will be needed for methods that are returning lists of entities. Your repository is also going to need to expose methods for .Any() (DoesExist) because everyone loves checking for #null on every return. :) Also .Count().
Option 2:
public IQueryable<Order> GetById(int id)
{
return db.Orders.Where(x => x.ID == id);
}
public IQueryable<Order> GetOrders()
{
return db.Orders.AsQueryable();
}
Callers can grok Linq and .Include() what they want before calling .Single(), or do a .Any().. They may not need the entire entity graph so they can .Select() from the entity and related entities without .Include() to compose and execute a more efficient query to populate a ViewModel / DTO. GetById might be used in a number of places so we can reduce duplication and support it in the repository. We don't need all of the filter scenarios etc, callers can call GetOrders and then filter as they see fit.
Why bother with a repository if it just returns DBSets?
Centralize low-level data filtering. For instance if you use Soft Deletes (IsActive) or are running multi-tenant, or explicit authorization. These common rules can be centralized at the repository level rather than having to remembered everywhere a DbSet is touched.
Testing is simpler. While you can mock a DbContext, or point it at an in-memory database, mocking a repository returning IQueryable is simpler. (Just populate a List<TEntity> and return .AsQueryable().
Repositories handle Create and Delete. Create to serve as a factory to ensure that all required data and relationships are established for a viable entity. Delete to handle soft-delete scenarios, cascades/audits etc. beyond what the DB handles behind the scenes.
I am working with Spring Data 2.0.6.RELEASE.
I am working about pagination for performance and presentation purposes.
Here about performance I am talking about that if we have a lot of records is better show them through pages
I have the following and works fine:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
}
The #Controller works fine with:
#GetMapping(produces=MediaType.TEXT_HTML_VALUE)
public String findAll(Pageable pageable, Model model){
Through Thymeleaf I am able to apply pagination. Therefore until here the goal has been accomplished.
Note: The Persona class is annotated with JPA (#Entity, Id, etc)
Now I am concerned about the following: even when pagination works in Spring Data about the amount the records, what about of the content of each record?.
I mean: let's assume that Persona class contains 20 fields (consider any entity you want for your app), thus for a view based in html where a report only uses 4 fields (id, firstname, lastname, date), thus we have 16 unnecessary fields for each entity in memory
I have tried the following:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
#Query("SELECT p.id, id.nombre, id.apellido, id.fecha FROM Persona p")
#Override
Page<Persona> findAll(Pageable pageable);
}
If I do a simple print in the #Controller it fails about the following:
java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to com.manuel.jordan.domain.Persona
If I avoid that the view fails with:
Caused by:
org.springframework.expression.spel.SpelEvaluationException:
EL1008E:
Property or field 'id' cannot be found on object of type
'java.lang.Object[]' - maybe not public or not valid?
I have read many posts in SO such as:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to
I understand the answer and I am agree about the Object[] return type because I am working with specific set of fields.
Is mandatory work with the complete set of fields for each entity? Should I simply accept the cost of memory about the 16 fields in this case that never are used? It for each record retrieved?
Is there a solution to work around with a specific set of fields or Object[] with the current API of Spring Data?
Have a look at Spring data Projections. For example, interface-based projections may be used to expose certain attributes through specific getter methods.
Interface:
interface PersonaSubset {
long getId();
String getNombre();
String getApellido();
String getFecha();
}
Repository method:
Page<PersonaSubset> findAll(Pageable pageable);
If you only want to read a specific set of columns you don't need to fetch the whole entity. Create a class containing requested columns - for example:
public class PersonBasicData {
private String firstName;
private String lastName;
public PersonBasicData(String firstName, String lastName) {
this.firstName = fistName;
this.lastName = lastName;
}
// getters and setters if needed
}
Then you can specify query using #Query annotation on repository method using constructor expression like this:
#Query("SELECT NEW some.package.PersonBasicData(p.firstName, p.lastName) FROM Person AS p")
You could also use Criteria API to get it done programatically:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonBasicData> query = cb.createQuery(PersonBasicData.class);
Root<Person> person = query.from(Person.class);
query.multiselect(person.get("firstName"), person.get("lastName"));
List<PersonBasicData> results = entityManager.createQuery(query).getResultList();
Be aware that instance of PersonBasicData being created just for read purposes - you won't be able to make changes to it and persist those back in your database as the class is not marked as entity and thus your JPA provider will not work with it.
Consider a Spring Data Jpa Repository:
public interface UserRepository extends JpaRepository<User, Long> {
User findOneByDeletedIsFalseAndActivationKey(String activationKey);
List<User> findAllByDeletedIsFalseAndActivatedIsFalseAndCreatedDateBefore(DateTime dateTime);
User findOneByDeletedIsFalseAndLogin(String login);
User findOneByDeletedIsFalseAndEmail(String email);
}
Notice each method has "DeletedIsFalse" in it. Is there a simple way to make method names shorter? Like i.e.:
#FullMethodName("findOneByDeletedIsFalseAndEmail")
User findOneByEmail(String email);
Use default Java 8 feature for wrapping, just like so:
interface UserInterface extends JpaRepository<User, Long> {
// use findOneByEmail instead
User findOneByDeletedIsFalseAndEmail(String email);
default User findOneByEmail(String email) {
return findOneByDeletedIsFalseAndEmail(email);
}
}
See an example.
With Kotlin, you can use extension functions, for example:
interface UserRepository : JpaRepository<User, Long> {
// use findOneByEmail instead
fun findOneByDeletedIsFalseAndEmail(email: String): User
}
fun UserRepository.findOneByEmail(email: String) =
findOneByDeletedIsFalseAndEmail(email)
Now you can use Java 8 default interface methods as #Maksim Kostromin described. But there is no such a feature in Spring.
-- Old answer
There is no such a way. You can specify any name for a method and add an annotation #Query with parameter value which holds desired query to database like this:
#Query(value="select u from User u where u.deleted=false and u.email=:email")
User findOneByEmail(#Param("email")String email);
or, with native sql query:
#Query(value="SELECT * FROM users WHERE deleted=false AND email=?1", nativeQuery=true)
User findOneByEmail(String email);
You can also use names that follow the naming convention for queries since #Query annotation will take precedence over query from method name.
#Query docs
Upd:
from Spring docs:
Although getting a query derived from the method name is quite convenient, one might face the situation in which ... the method name would get unnecessarily ugly. So you can either use JPA named queries through a naming convention ... or rather annotate your query method with #Query.
I'd like to know how to create a method that will allow me to generically do this...
public class Repo<T> : IGenericRepo<T> where T : class
{
protected PteDotNetEntities db;
public T Get(int id)
{
//how do I dynamically get to the correct entity object and select it by
//id?
}
}
Yes you can. If you know that all your entities will have simple primary key property of type int and name Id you can do simply this:
public interface IEntity
{
int Id { get; }
}
All your entities must implement this interface. Next you can simply do:
public class Repo<T> : IGenericRepo<T> where T : class, IEntity
{
protected PteDotNetEntities db;
public T Get(int id)
{
return db.CreateObjectSet<T>().FirstOrDefault(e => e.Id == id);
}
}
This is the simplest possible solution. There are better solutions using GetObjectByKey but they are more complex. The difference between FirstOrDefault and GetObjectByKey is repeatable execution. FirstOrDefault always executes DB query whereas GetObjectByKey first checks if the entity with the same key was already loaded / attached to the context and returns it without querying the database. As reference for version using GetObjectByKey you can check similar questions:
Entity Framework Simple Generic GetByID but has differents PK Name
Generic GetById for complex PK
You can simplify those examples if you know the name of the key property upfront (forced by the interface).
In case of using code first / DbContext API you can also check this question:
Generic repository EF4 CTP5 getById