I have a native query that return list of objects, and inside every object an attribute of type list :
Child interface :
public interface ChildInterface {
public Integer getId();
public String getCode();
public String getLabel();
}
Example interface :
public interface ExampleInterface {
public Integer getId();
public String getLabel();
public List<ChildInterface> getChildren();
}
I need to create a native query to get Example list with there Children :
#Query(nativeQuery = true, value = "select e.id as id from example e .....")
List<ExampleInterface> search()
Tables:
Example: id, label
Child: id, code, label, example_id
Related
I am trying to load entities containing a reference to another entity (1-n) with the help of JOOQ (based on spring-data-jdbc).
I'm started extending the spring-data-jdbc-jooq-example.
The adjusted model with the 1-n relation:
#Data
public class Category {
private #Id Long id;
private String name, description;
private AgeGroup ageGroup;
private Set<SubCategory> subCategories;
public Category() {}
public Category(Long id, String name, String description, AgeGroup ageGroup) {
this(id, name, description, ageGroup, new HashSet<>());
}
public Category(Long id, String name, String description, AgeGroup ageGroup, Set<SubCategory> subCategories) {
this.id = id;
this.name = name;
this.description = description;
this.ageGroup = ageGroup;
this.subCategories = subCategories;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public class SubCategory {
private #Id Long id;
private String title;
}
I wrote two queries, one via the #Query-Annotation in the CrudRepository and one with the help of JOOQ in the JooqRepository.
interface CategoryRepository extends CrudRepository<Category, Long>, JooqRepository {
#Query("SELECT * FROM category")
List<Category> findAllWithQuery();
}
public interface JooqRepository {
List<Category> findAllWithJooq();
}
public class JooqRepositoryImpl implements JooqRepository {
private final DSLContext dslContext;
public JooqRepositoryImpl(DSLContext dslContext) {
this.dslContext = dslContext;
}
#Override
public List<Category> findAllWithJooq() {
return dslContext.select()
.from(CATEGORY)
.fetchInto(Category.class);
}
}
(for me both methods should return the same result-set b/c they execute the same query?!)
But my unit-test fails:
#Test
public void exerciseRepositoryForSimpleEntity() {
// create some categories
SubCategory sub0 = new SubCategory(null, "sub0");
SubCategory sub1 = new SubCategory(null, "sub1");
Category cars = new Category(null, "Cars", "Anything that has approximately 4 wheels", AgeGroup._3to8, Sets.newLinkedHashSet(sub0, sub1));
// save category
repository.saveAll(asList(cars));
// execute
List<Category> actual = repository.findAllWithJooq();
List<Category> compare = repository.findAllWithQuery();
Output.list(actual, "JOOQ");
Output.list(compare, "Query");
// verify
assertThat(actual).as("same size of categories").hasSize(compare.size());
assertThat(actual.get(0).getSubCategories()).as("same size of sub-categories").hasSize(compare.get(0).getSubCategories().size());
}
with
java.lang.AssertionError: [same size of sub-categories]
Expecting actual not to be null
As you can see in the following output the sub-categories queried by JOOQ will not be loaded:
2019-11-26 16:28:00.749 INFO 18882 --- [ main] example.springdata.jdbc.jooq.Output : ==== JOOQ ====
Category(id=1,
name=Cars,
description=Anything that has approximately 4 wheels,
ageGroup=_3to8,
subCategories=null)
2019-11-26 16:28:00.749 INFO 18882 --- [ main] example.springdata.jdbc.jooq.Output : ==== Query ====
Category(id=1,
name=Cars,
description=Anything that has approximately 4 wheels,
ageGroup=_3to8,
subCategories=[SubCategory(id=1,
title=sub0),
SubCategory(id=2,
title=sub1)])
This is the used database-shema:
CREATE TABLE IF NOT EXISTS category (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(100),
description VARCHAR(2000),
age_group VARCHAR(20)
);
CREATE TABLE IF NOT EXISTS sub_category (
id INTEGER IDENTITY PRIMARY KEY,
title VARCHAR(100),
category INTEGER
)
In the JOOQ variant, JOOQ does the conversion from ResultSet to object instances. Since JOOQ doesn't know about the interpretation of aggregates as it is done by Spring Data JDBC it only hydrates the Category itself, not the contained Set of SubCategory.
Spring Data JDBC on the other hand interprets the structure of the Category and based on that executes another statement to load the subcategories.
I'm running into something odd with inheritance and mongodbrepositories.
I have the following:
`
#Document
public class Base {
public String fieldA;
}
public class Derived extends Base {
public String fieldB;
}
public interface DerivedRepository extends MongoRepository<Base, String> {
List<Derived> findByFieldA(String fieldA);
}
`
When inserting i get
Inserting DBObject containing fields: [_class, _id, fieldA, fieldB ]
in collection: base
When i do findByFieldA('some value') on the repository i get the following:
find using query: { "fieldA" : "some value" } fields: null for class:
class Derived in collection: derived
Any idea what is going on here? And how can I fix this, either by saving it to the proper derived collection or by querying from the base collection.
Regards,
First, I would make Derived class as document since the parent is going to be shared among many implementations.
public class Base {
public String fieldA;
}
#Document
public class Derived extends Base {
public String fieldB;
#Override
public String toString() {
return "{fieldA: " + getFieldA() + ", fieldB: " + fieldB + "}";
}
}
Second, change the repository specification with the type of document (class marked as #Document) as:
public interface DerivedRepository extends MongoRepository<Derived, String> {
List<Derived> findByFieldA(String fieldA);
List<Derived> findByFieldB(String fieldB);
}
I added extra method findByFieldB(String fieldB) to explain more.
With these changes, you should be able to query either with fieldA or fieldB as below:
public class SpringBootMongoApplication {
#Autowired
private DerivedRepository derivedRepository;
public void testMethod() throws Exception {
Derived derived1 = new Derived();
derived1.setFieldB("fieldB1");
derived1.setFieldA("fieldA1");
Derived derived2 = new Derived();
derived2.setFieldB("fieldB2");
derived2.setFieldA("fieldA2");
this.derivedRepository.save(Arrays.asList(derived1, derived2));
List<Derived> deriveds = this.derivedRepository.findByFieldA("fieldA1");
System.out.println(deriveds);
List<Derived> deriveds1 = this.derivedRepository.findByFieldB("fieldB2");
System.out.println(deriveds1);
}
}
The output should be:
[{fieldA: fieldA1, fieldB: fieldB1}]
[{fieldA: fieldA2, fieldB: fieldB2}]
You can also verify the object persisted and their types with mongo query as below:
I have created an Spring Boot sample app which you can find in Github.
I have a class that represents a user date of birth in two separated fields
public class User {
private int yearOfBirth;
private int monthOfBirth;
}
Is it possible to make a projection that exports the user age? I know we can concatenate fields using #Value.
The easiest way to resolve the problem (if you can add code to the domain class) is to add a method in the user class like the one below:
#JsonIgnore
public int getAge() {
return Period.between(
LocalDate.of(dobYear, dobMonth, 1),
LocalDate.now()
).getYears();
}
You can add the #JsonIgnore to block spring from exporting an "age" field when your entity is serialized. After adding that method you can create projection like the one below:
#Projection(name = "userAge ", types = {User.class})
public interface UserAge {
#Value("#{target.getAge()}")
Integer getAge();
}
Something like this, for example:
public class UserAgeDto {
private int yearOfBirth;
private int monthOfBirth;
public UserAgeDto(int yearOfBirth, int monthOfBirth) {
// constructor implementation...
}
public int getAge() {
// age calculation...
}
}
public interface UserRepo extends JpaRepository<User, Long> {
#Query("select new com.example.myapp.dto.UserAgeDto(u.yearOfBirth, u.monthOfBirth) from User u where u = ?")
UserAgeDto getUserAgeDto(User user);
}
Some info
I got stuck using a Spring Project with Spring Data + specification + criteria api.
I will try to simulate the situation with general entities we used write to get easy example.
The Entities:
Consider all attributes of the each entity is passed on the constructor showed below
Country(Long id, String name, String iso)
State(Long id, String name, String iso)
City(Long id, String name, String iso)
This is my repository:
public interface CityRepository extends PagingAndSortingRepository<City, Integer>, JpaSpecificationExecutor<City> {
}
As you can see, I don't need to implement anything on the repository
This is my service
#Service
#Transactional
public class CityService {
#Autowired
private CityRepository cityRepository;
#Transactional(readOnly = true)
public CityListVO findByNameLike(String name, PageRequest pageRequest) {
name = "%" + name + "%";
if (pageRequest == null) {
List<City> result = cityRepository.findAll(fillGridCriteria(name));
return new CityListVO(1, result.size(), result);
} else {
Page<City> result = cityRepository. findAll(fillGridCriteria(name), pageRequest);
return new CityListVO(result.getTotalPages(), result.getTotalElements(), result.getContent());
}
}
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
/*
The current return I can do a like by name, and it works fine.
My problem is if for any reason I need to do multiple joins like the folow jpql:
select ci FROM City ci, State st, Country co where ci.st = st AND st.co = co AND co.name = 'Canada';
How to do this from here ? Inside this method.
How is gonna be the return for this method ?
*/
return builder.like(root.get("name"), name.trim());
}
};
}
}
Let's assume you want all the cities that their country's name like name and you have a relational Model in which :
Country(Long id, String name, String iso)
State(Long id,Long country, String name, String iso)
City(Long id, Long state, String name, String iso)
Predicate:
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
return
builder.like(root.get("state").get("country").get("name"), name.trim());
}
};
}
Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();