with following code I am getting error Persistence entity must not be null. what could be the mistake.
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query(
"select d.districtId, d.districtName from District d where d.districtId in (:districtIds) group by d.districtId"
)
#RestResource(path="byList")
List<Object[]> byList(#Param("districtIds") List<Integer> districtIds);
}
If you are going to make a "search" method, use this approach:
#Projection(name = "idAndName", types = {District.class})
public interface IdAndName {
Integer getId();
String getName();
}
#RestResource(path="byIds", rel="byIds")
#Query("select d from District d where d.districtId in (:districtIds)")
List<District> findByIds(#Param("ids") Integer... ids);
Then use this url:
http://localhost:8080/api/districts/search/byIds?ids=1,2,3&projection=idAndName
More info about projection
If you need to use complex queries with grouping and aggregation that return DTOs you can not use "search" methods. Instead you have to implement custom controller, for example:
#RepositoryRestController
#RequestMapping("/districts")
public class DistrictController {
#Autoware
private DistrictRepo repo;
#GetMapping("/report")
public ResponseEntity<?> report(#RequestParam(value = "ids") Integer... ids) {
List<Dto> dtos = repo.getDtosByIds(ids);
return ResponseEntity.ok(new Resources<>(dtos));
}
}
Where Dto is something like this:
#Data // This is Lombok annotation (https://projectlombok.org/)
#Relation(value = "district", collectionRelation = "districts")
public class Dto {
private final Integer id;
private final String name;
}
And something like this the repo method:
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query("select new ...Dto(d.districtId, d.districtName) from District d where d.districtId in (:districtIds) group by d.districtId")
#RestResource(path="byList", rel="byList")
List<Dto> getDtosByIds(#Param("ids") Integer... ids);
}
More info.
Related
My native join query produces a new result that is a combination of database tables, so I created a dto for that resulting object (will be a list of records on a screen).
I believe I need to make it an entity, so JPA can recognize it, would that be the best way to do it?
Also, the entity needs an id, and I was hoping to let jpa generate it auto, but I'm getting "Invalid parameter: Unknown column name id. ERRORCODE=-4460, SQLSTATE=null"
My result set contains 4 of the same records instead of 4 different, and I think it has to do with my id field not set properly
Any help would be appreciated on the subject, thanks.
`public interface ErrorCodeRepo extends JpaRepository<Errors, ErrorsPK> {
#Query("SELECT e.transDate, e.category FROM Errors e")
List<QueuedErrors> findQueuedErrors();
}`
DTO class:
`
public class QueuedErrors {
private String transDate;
private String category;
public QueuedErrors(String transDate, String category) {
this.transDate = transDate;
this.category = category;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
`
When you create navite query which contains results from multiple tables (after joins) you don't have to create new entities.
Better way to solve this problem is to projection with interface or class DTO.
For example, if you want to combine results from Person and Address Entities, simply create interface:
public interface PersonView {
String getFirstName();
String getLastName();
String getStreet();
}
You can see combined fileds from Person (firstName, lastName) and Address (street).
You have to use it as query response, like this:
#Query(...)
List<PersonView> getPersonWithStreet(String state);
You can read more about it here:
https://www.baeldung.com/spring-data-jpa-projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
http://localhost:8080/users?firstName=a&lastName=b ---> where firstName=a and lastName=b
How to make it to or ---> where firstName=a or lastName=b
But when I set QuerydslBinderCustomizer customize
#Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(String.class).all((StringPath path, Collection<? extends String> values) -> {
BooleanBuilder predicate = new BooleanBuilder();
values.forEach( value -> predicate.or(path.containsIgnoreCase(value) );
});
}
http://localhost:8080/users?firstName=a&firstName=b&lastName=b ---> where (firstName=a or firstName = b) and lastName=b
It seem different parameters with AND. Same parameters with what I set(predicate.or/predicate.and)
How to make it different parameters with AND like this ---> where firstName=a or firstName=b or lastName=b ??
thx.
Your current request param are grouped as List firstName and String lastName. I see that you want to keep your request parameters without a binding, but in this case it would make your life easier.
My suggestion is to make a new class with request param:
public class UserRequest {
private String lastName;
private List<String> firstName;
// getters and setters
}
For QueryDSL, you can create a builder object:
public class UserPredicateBuilder{
private List<BooleanExpression> expressions = new ArrayList<>();
public UserPredicateBuilder withFirstName(List<String> firstNameList){
QUser user = QUser.user;
expressions.add(user.firstName.in(firstNameList));
return this;
}
//.. same for other fields
public BooleanExpression build(){
if(expressions.isEmpty()){
return Expressions.asBoolean(true).isTrue();
}
BooleanExpression result = expressions.get(0);
for (int i = 1; i < expressions.size(); i++) {
result = result.and(expressions.get(i));
}
return result;
}
}
And after you can just use the builder as :
public List<User> getUsers(UserRequest userRequest){
BooleanExpression expression = new UserPredicateBuilder()
.withFirstName(userRequest.getFirstName())
// other fields
.build();
return userRepository.findAll(expression).getContent();
}
This is the recommended solution.
If you really want to keep the current params without a binding (they still need some kind of validation, otherwise it can throw an Exception in query dsl binding)
you can group them by path :
Map<StringPath,List<String>> values // example firstName => a,b
and after that to create your boolean expression based on the map:
//initial value
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (Map.Entry entry: values.entrySet()) {
result = result.and(entry.getKey().in(entry.getValues());
}
return userRepository.findAll(result);
I have two entities Person and Movie.
#Entity
public class Person {
..some fields
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "actors")
#OrderBy("id")
private Set<Movie> moviesActor = new TreeSet<>();
}
#Entity
public class Movie {
..fields
#JoinTable(name = "actor_movie",
joinColumns = { #JoinColumn(name = "movie_id") },
inverseJoinColumns = { #JoinColumn(name = "actor_id") })
private Set<Person> actors = new TreeSet<>();
}
There is many to many relationship so there is new table actor_movie to keep it. And how can I get every person that has any movie in its set? So what I want is to achieve is get every person that exists in actor_movie table. I tried used Spring data jpa but couldn't find right query.
Best Practices in entity relations:
Always use fetch = FetchType.LAZY.
When you want to fetch another side of the relation too, use JOIN FETCH Query.This resolves LazyInitializationException of hibernate also.
Always use spring.jpa.open-in-view=false
Example:
By Spring Data JPA with Hibernate as JPA Provider.
Entities:
public class Blog{
...
#ManyToMany(fetch = FetchType.LAZY) //default is LAZY in ManyToMany
#JoinTable(name="blog_tag",
joinColumns = #JoinColumn(name = "blog_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
#OrderBy //order by tag id
private Set<Tag> tags = new HashSet<>();
//2 utility methods in owner side
public void addTag(Tag tag){
tags.add(tag);
tag.getBlogs().add(this);
}
public void removeTag(Tag tag){
tags.remove(tag);
tag.getBlogs().remove(this);
}
//override equals & hashcode
}
public class Tag {
...
#ManyToMany(mappedBy = "tags")
private Set<Blog> blogs = new HashSet<>();
//override equals & hashcode
}
Now suppose, you want to fetch a Blog containing Tag items:
Repository:
#Repository
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Query("select b from Blog b join fetch b.tags where b.name = :name")
Blog getBlog(#Param("name") String blogName);
}
service:
public interface BlogService {
Blog getBlog(String blogName);
}
#Service
public class BlogServiceImpl implements BlogService{
#Autowired
private BlogRepository blogRepository;
#Override
public Blog getBlog(String blogName) {
return blogRepository.getBlog(blogName);
}
}
You only need a single JOIN between Person and Movie. As Hibernate abstracts the existence of the middle table, you don't need to worry about it.
So, with Spring Data Repository:
class PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByMoviesActor();
}
With Jpql:
SELECT person FROM Person person JOIN person.moviesActor movie
Since you are using Fetch type lazy, you need to use join fetch to get moviesActor.
You can use jpql with spring data. I have not tested the queries below, but should work.
public interface PersonRepository extends JpaRepository<Person, Long> { //Long if Person.id is of type Long
#Query("SELECT p FROM Person p LEFT JOIN FETCH p.moviesActor WHERE size(p.moviesActor) > 0");
List<Person> findActors1();
// Or
#Query("SELECT p FROM Person p JOIN FETCH p.moviesActor");
List<Person> findActors2();
}
More about jpql size() operator here: https://www.thoughts-on-java.org/jpql/
You can use join directely :
#Query("SELECT p FROM Person p JOIN p.moviesActor movie");
List findPersonHasMovie();
I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.
I'm trying to get a specific set of data while joining 4 different entities together to do so. What I've done is setup a DTO to try to get this working:
public class LatestThread
{
private readonly string comment;
private readonly DateTime posted;
private readonly string userName;
private readonly int reputation;
private readonly int threadId;
private readonly string topic;
private readonly int userId;
private readonly string avatar;
public LatestThread(string comment, DateTime posted, string userName, int reputation, int threadId, string topic, int userId, string avatar)
{
this.comment = comment;
this.avatar = avatar;
this.userId = userId;
this.topic = topic;
this.threadId = threadId;
this.reputation = reputation;
this.userName = userName;
this.posted = posted;
}
public string Comment
{
get { return comment; }
}
public DateTime Posted
{
get { return posted; }
}
public string UserName
{
get { return userName; }
}
public int Reputation
{
get { return reputation; }
}
public int ThreadId
{
get { return threadId; }
}
public string Topic
{
get { return topic; }
}
public int UserId
{
get { return userId; }
}
public string Avatar
{
get { return avatar; }
}
}
Now I thought I could use SimpleQuery like so:
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
return repository.SimpleQuery(0, 10, hql);
My repository method looks like:
public virtual IList<T> SimpleQuery<T>(int firstResult, int maxResults, string hql, params object[] parameters)
{
var query = new SimpleQuery<T>(hql, parameters);
query.SetQueryRange(firstResult, maxResults);
return query.Execute();
}
Now it's asking for me to put [ActiveRecord] at the top of my LatestThread class. When I do that it wants a primary key, and that just seems to be the wrong route.
I've also read bits that refer to the Import attribute given to classes that aren't the DTO. In all the examples though it's just two entities being joined, not the 4 I have. Do I need to add Import to all 4? Or is there something to tell AR that it's a readonly DTO class? OR am I doing this all wrong and there's a really easy way to do what I'm trying to do.
TIA!
Add the Import attribute to your new Thread class
[Import(typeof(LatestThread), "LatestThread")]
[ActiveRecord("Thread")]
public class Thread : ActiveRecordBase<Thread> { /* blah blah */ }
And then, query magic happens :)
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
SimpleQuery<LatestThread> query = new SimpleQuery<LatestThread>(typeof(Thread), hql );
LatestThread[] results = query.Execute()
Source : http://www.kenegozi.com/Blog/2006/10/08/projection-using-activerecords-importattribute-and-hqls-select-new-clause.aspx
You can't query a type that isn't mapped (which is what the [ActiveRecord] attribute does). AFAIK you can't get NHibernate to create a new arbitrary object instance like that via HQL (I stand to be corrected if someone knows otherwise).
Your best bet is to do a projection query and then have a method to map the tuples returned into instances of your type.
My answer here shows how to do a projection query and map it to an anonymous type; what you want to do is not much different. You could then put a method to do this in a type-specific repository or a strongly-typed extension method to the generic repository.