How to set criteria fetch mode using specification in spring jpa? - jpa

I need to implement a dynamic query and for that I'm using org.springframework.data.jpa.domain.Specification interface.
For example:
public class PlayerSpecification {
public static Specification<Player> name(String name{
return (root, criteraQuery, criteriaBuilder)->
criteriaBuilder.equal(root.get("name"), name);
}
public static Specification<Player> teamName (String teamName){
return (root, criteraQuery, criteriaBuilder)->
criteriaBuilder.equal(root.get("team").get("name"), teamName);
}
}
I would like to set JOIN FETCH in the Criteria.
Something like:
criteraQuery.setFetchMode("teams", FetchMode.EAGER);
Anyone knows how to do it?

I'm not sure how the entities look like, but it should be:
root.fetch( "teams" );
See Hibernate ORM fetch example in the documentation.

Related

JPA query attribute with a converter

I have a SpringBoot 2.6.11 application with JPA 2.2.
I have an entity like this:
#Data
#Entity
#Table(name = "entity")
public class Entity implements Serializable {
....
#Convert(converter = ListConverter.class)
private List<String> referenceCode;
....
}
I have this Converter:
#Converter(autoApply = true)
public class ListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
return String.join(";", attribute);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
return new ArrayList<>(Arrays.asList(dbData.split(";")));
}
}
And when I insert or extract this element all working fine. But now I wanna query that element and I don't know how to do it. If I do something like that:
public List<Entity> findByReferenceCode(String reference);
It doesn't work, if I do:
#Query("select e from Entity e where e.referenceCode IN ?1")
public List<Entity> findByReferenceCode(List<String> reference);
Still doesn't work..
The only way I found is by the nativeQuery but is really an extrema ratio. Ho can I solve this?
Thank you
To really do what you want here, you need to use an #ElementCollection. The reason being that there is no reliable way for JPA to query a single column and treat it as a collection. Reliably querying a collection requires a second table (which is what #ElementCollection does). You can continue to use the #Converter, but your queries will have to be customized to handle the disparity between the entity attribute type (list) and the actual database column type (string).
If you are okay with the limitations of the #Converter then it's fine (I have used them this way) but if you truly need to query the attribute like a collection (e.g. search for multiple independent items, perform counts, aggregations, etc) and you want those queries to be generated by a JPA layer, then you will have to use #ElementCollection and let it create a second table.

Web API GET with array of complex type

I want to allow users of an API to send GET request that will run multiple queries on db and then return the result.
I have a query model like this
public class QueryModel
{
public int A {get;set;}
public int B {get;set;}
public int C {get;set;}
}
I have a controller with a Get method like this -
public async Task<IActionResult> Get(List<QueryModel> queryModels)
{
foreach(var queryModel in queryModels)
{
// some logic to search the db
}
// combine the results and return
}
This is not working for me, but I don't know if my query string is wrong or the method is wrong.
I have tried a number of variations of
?model={[{1,2,3},{1,2,3}]}
But they do not work.
You can use a construction like this:
\Get?model[0].A=1&model[0].B=2&model[0].C=3&model[1].A=4&model[1].B=5&model[1].C=6
Almost forgot, add FromUri:
public async Task<IActionResult> Get([FromUri] List<QueryModel> model)
{
...
}
Please let me know if this works for you.

Spring Data REST custom finder for JpaRepository

I am looking to build a REST interface with a generic finder. The idea is to provide a search form where users can get all records by not providing any parameter or refine their search results by typing any combination of the fields.
The simple example I have annotates the JpaRepository with #RestResource which provides a nice out of the box way to add finders either by using #Query or by method name conventions
#RestResource(path = "users", rel = "users")
public interface UserRepository extends JpaRepository<User, Long>{
public Page<User> findByFirstNameStartingWithIgnoreCase(#Param("first") String fName, Pageable page);
}
I am looking to add a custom finder that would map my parameters and would leverage the paging, sorting and REST support where the actual implementation query will be composed dynamically (probably using QueryDSL) the method will have n parameters (p 1 ... p n) and will look like:
public Page<User> findCustom(#Param("p1") String p1, #Param("p2") String p2, ... #Param("pn") String pn, Pageable page);
I have tried the approach described in:
http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations
but my custom method is not available from the repository's REST interface (/users/search)
I hope someone already figured this out and would be kind to give me some direction.
Try something like this but of course adopted to your scenario:
public interface LocationRepository extends CrudRepository,
PagingAndSortingRepository,
LocationRepositoryExt {
}
public interface LocationRepositoryExt {
#Query
public List findByStateCodeAndLocationNumber(#Param("stateCode") StateCode stateCode, #Param("locationNumber") String locationNumber);
}
class LocationRepositoryImpl extends QueryDslRepositorySupport implements LocationRepositoryExt {
private static final QLocation location = QLocation.location;
public LocationRepositoryImpl() {
super(Location.class);
}
#Override
public Page findByStateAndLocationNumber(#Param("state") State state, #Param("locationNumber") String locationNumber, Pageable pageable) {
List locations = from(location)
.where(location.state.eq(state)
.and(location.locationNumber.eq(locationNumber)))
.list(location);
return new PageImpl(locations, pageable, locations.size());
}
}

MVC4 and Entity Framework Inheritance and Include

I have some simple objects
public class DataClass
{
public int id;
public string Data;
}
public class Job()
{
public int id;
}
public class NewJob : Job
{
public DateTime StartDate;
public DataClass data;
}
I have then defined them in my dBContext()
public DbSet<Job> Jobs { get; set; }
public DbSet<DataClass> DataClass { get; set; }
Now if I use the following code
NewJob job = (NewJob) db.Jobs.Find(id);
This works fine but returns "data" as null
I know I define the class with the virtual keyword and it works and populates the "data" object.
public class NewJob : Job
{
public DateTime StartDate;
public virtual DataClass data;
}
But in my case I "normally" do not want the "data" object to be populated. So I need to load it on demand.
If I try something like
NewJob job = (NewJob)db.Jobs.Include("data").First();
I get an exception
A specified Include path is not valid. The EntityType 'Models.Job' does not declare a navigation property with the name 'data'.
I guess this is because it is looking at "job" and not "NewJob" when it is trying to do the include.
I also do not like the include with a string - no design time checking.
It looks like you are trying to convert data object to your domain object via type casting which is a very bad idea. What you want to do is grab your data object, instantiate your domain object, and map your data values to the domain object using some type of helper class. A very helpful tool I have been using is Automapper. Its a tool that will allow you to map one object to another. It also allows the use of regular expression to help with the mappings if the naming conventions between the 2 objects are different.
If you're using Entity Framework Code First and want to create instances of derived classes/entities you should do the following:
using (var db = new MyDbContext())
{
var newJob = db.Jobs.Create<NewJob>();
newJob.data.Data = "some data for a new job"; // this is string Data from DataClass
db.Jobs.Add(newJob);
db.SaveChanges();
}
After a lot of searching I found the following which can help.
If you include the System.Data.Entity namespace in your using clause then you can use the extension method .Include() after OfType<>() which is not normally available.
Slightly different code sample
using System.Data.Entity;
NewJob job = (NewJob)db.Jobs.OfType<NewJob>().Include(m => m.data).Where(x => x.id == id).FirstOrDefault();
This seems to be working for me in the example I used.

EF - Why doesn't Include work on Properties

So I have classes that looks like this.
public class User {
public virtual IList<Member> Members {get;set;}
}
public class Member {
public virtual AnotherTable Another {get;set;}
}
public class AnotherTable {
public string Name {get;set;}
}
When I perform the query directly against the DataContext the Include works, but when I do an AsQueryable() on the IList of members the include doesn't work.
Is there a way to have Include/Eager functionality on lazy loaded properties, such as the Members property above, or do I always have to go through the DataContext to get that feature?
User.Members.AsQueryable().Include(a => a.Another).ToList() // <-- nada, no way Jose
_db.Members.Include(m => m.Another).ToList() // <-- all good in the neighborhood
I ask cause it can be a huge difference of 1 sql query vs. 100 queries for something result equivalent.
Thanks in advance.
AsQueryable doesn't make it linq-to-entities query. It is still Linq-to-object query on top of List. List doesn't know how to handle Include - only DbQuery knows it so you must get DbQuery:
var entry = context.Entry(user);
entry.Collection(u => u.Member).Query().Include(m => m.Another).Load();
You'll have to go through the DbContext in order for Include() to work. You could abstract it into a Repository, but you'll still need to pass your Include() expression to your underlying context.
private IQueryable<T> GetQuery<T>(params Expression<Func<T, object>>[] includeProperties) where T : class
{
IQueryable<T> query = _db.Set<T>();
if (includeProperties != null)
{
foreach (Expression<Func<T, object>> expression in includeProperties)
{
query = query.Include(expression);
}
}
return query;
}
I also faced same problem.
I solved this just adding the reference System.Data.Entity & use following namespace:
using System.Data.Entity;
You can try with it.