How to search by embedded object in a CrudRepository? - java

Suppose I have an embeddable class, that is embedded in an entity class like below.
#Embeddable
public class FullName {
private String firstName;
private String lastName;
// constructor, getters, setters, as needed
}
#Entity
public class Account {
#Embedded
private FullName fullName;
// id, other data and methods
}
One person can have multiple accounts, hence the full name is not a key for the Account class. What I would like to have, is the following method in a jpa repository
public interface AccountRepository extends CrudRepository<Account, Long> {
Collection<Account> findAllByFullName(FullName fullName);
}
Initially, I thought this would just work, but apparently it's not that simple. I could not get a method like this to work. The only thing I thought might help, was implement an equals method on FullName, saying that a FullName with same first and last name are equal, but this had no effect.
What I ended up doing for the moment is this:
public interface AccountRepository extends CrudRepository<Account, Long> {
Collection<Account> findAllByFullNameFirstNameAndFullNameLastName(String firstName, String lastName);
default Collection<Account> findAllByFullName(FullName fullName) {
return findAllByFullNameFirstNameAndFullNameLastName(fullName.getFirstName(), fullName.getLastName());
}
}
Is there a way for me to avoid creating this intermediary (visible) method?

You can use Example as below,
Collection<Account> findAll(Example< Account > account);
And then pass it as,
FullName fullName = new FullName(fname, lname);
Account acc = new Account();
acc.setFullName(fullName);
Collection<Account> accounts = accountRepository.findAll(Example.of(acc));
Hope it helps.

Related

Filtering data in a table - how to organize it correctly

I study the spring+Hibernate bundle there Is an entity:
public class PersonEntity {
private Long id;
private String name;
private Integer age;
private City city;
private Countrycountry;
...
}
I need to perform filtering. data in this table to display in the browser window. I was thinking of making an implementation in the service of the following methods:
.findByName(name);
.findByNameAndAge(name, age);
.findByNameAndAge(name, age, city);
.findByNameAndAge(name, city);
...
But it turns out that there are too many options for methods. How to make one universal method, i.e. something like a collection in which you can add as many parameters as you need. I started reading on this issue and got completely confused. Somewhere they write about #Filter, somewhere about Hibernate Search, there is also Spring Data Elasticsearch. Tell me the easiest and most relevant way to implement this. If there are links to real examples, I would be very grateful.
Dao:
public interface PersonDao extends GeneralDAO<PersonEntity>{
public List<PersonEntity> searchName(String name);
public List<PersonEntity> searchAllFields(
String name,
Integer age,
City city);
}
GeneralDAO describes all standard methods such as get, save, etc. Repository:
#Repository
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
List<PersonEntity> findByNameIgnoreCase(String name);
List<PersonEntity> findByNameAndAgeAndCity(
String name,
Integer age,
City city);
}
Service
#Service
#Transactional
public class PersonService implements PersonRepository {
#Autowired
private PersonRepository personRepository;
...
описание всех стандартных методов чтения-записи в БД
#Override
public List<PersonEntity> searchName(String name) {
return productTypeRepository.findByNameIgnoreCase(name);
}
#Override
public List<PersonEntity> searchAllFields(
String name,
Integer age,
City city) {
return personRepository.findByNameAndAgeAndCity(
name,
age,
city);
}
}
In the ad and call controller:
#Autowired
private PersonService personService;
...
personService.searchAllFields(...);
The searchName method works fine, but searchAllFields doesn't. It always returns an empty list, even if I specify one name, the rest = null
I tried to change the method in the service:
List<PersonEntity> findByNameIsNullAndAgeIsNullAndCityIsNull
Spring responds with an error:
"Error creating bean with name personRepository. At least 1 parameter(s) provided but only 0 parameter(s) present in query".
searchAllFields Method is returning an empty list because it contains findByNameAndAgeAndCity which means all the parameters are mandatory and the condition between them is AND so better change to OR (findByNameOrAgeOrCity) so that if you pass single value like name and rest = null then also you will get data and vice-versa.
You should really consider using Criteria API since you are using Spring & Spring Data, you can use JPA Specifications as a complete example see the following example:
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
....
public interface PersonRepository extends JpaRepository<PersonEntity, Long>, JpaSpecificationExecutor {
}
// Notice the the second extended interface JpaSpecificationExecutor ^
in service:
import org.springframework.data.jpa.domain.Specification;
....
public List<PersonEntity> list(PersonEntityFilter personFilter) {
List<PersonEntity> filteredPersons = personsRepository.findAll
(Specification.where(PersonEntitySpecs.findByFilters(personFilter)));
return filteredPersons;
}
PersonEntityFilter is the payload coming from your controller submitted by your clients or your UI and it is a simple class that groups all fields you want to filter by
public class PersonEntityFilter {
private String name;
private Integer age;
private City city;
// getters & setters
}
PersonEntitySpecs is where you put your specs (criteria query logic)
public class PersonEntitySpecs {
public static Specification<PersonEntity> findByFilters(PersonEntityFilter personEntityFilter) {
return (root, query, cb) -> {
final Collection<Predicate> predicates = new ArrayList<>();
if (personEntityFilter.getName() != null) {
predicates.add(cb.like(root.get("name"), "%" + personEntityFilter.getName() + "%")));
}
if (personEntityFilter.getAge() != null) {
predicates.add(cb.equal(root.get("age"), personEntityFilter.getAge()));
}
if (personEntityFilter.getCity() != null) {
Join<PersonEntity, CityEntity> personCityJoin = root.join("city");
predicates.add(cb.equal(personCityJoin.get("id"), personEntityFilter.getCity().getId()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}

How to test a controller function with argument form?

Heads Up: It is my first post here, please excuse any missing information or the really novice questions.
So I am currently trying to write jUnit tests for the already finished web application that uses spring (everything works, I just have to get full coverage with the tests).
I have the classes: "Employee", "EmployeeController" and "EmployeeManagement".
I want to test the "registerNew" function which creates a new Employee with the filled form "EmployeeRegistrationForm" if it has no errors ("Errors result").
Now I want to write a Test for this to make sure that the function really does create a new object "Employee" which should be saved in the "EmployeeRepository" with said form.
However, I cannot seem to be able to create a filled "EmployeeForm" since it is abstract and cannot be instantiated. Therefore I am struggling to give any argument to that function and do not know how to pass the information needed for the test to function being tested.
#Service
#Transactional
public class EmployeeManagement {
private final EmployeeRepository employees;
private final UserAccountManager userAccounts;
EmployeeManagement(EmployeeRepository employees, UserAccountManager userAccounts) {
Assert.notNull(employees, "employeeRepository must not be null!");
Assert.notNull(userAccounts, "UserAccountManager must not be null!");
this.employees=employees;
this.userAccounts = userAccounts;
}
//the function that creates the employee
public Employee createEmployee(EmployeeRegistrationForm form) {
Assert.notNull(form, "Registration form must not be null!");
String type = form.getType();
Role role = this.setRole(type);
UserAccount useraccount = userAccounts.create(form.getUsername(), form.getPassword(), role);
useraccount.setFirstname(form.getFirstname());
useraccount.setLastname(form.getLastname());
return employees.save(new Employee(form.getNumber(), form.getAddress(), useraccount));
}
#Controller
public class EmployeeController {
private final EmployeeManagement employeeManagement;
EmployeeController(EmployeeManagement employeeManagement) {
Assert.notNull(employeeManagement, "userManagement must not be null!");
this.employeeManagement = employeeManagement;
}
#PostMapping("/registerEmployee")
#PreAuthorize("hasRole('ROLE_ADMIN')")
String registerNew(#Valid EmployeeRegistrationForm form, Errors result) {
if (result.hasErrors()) {
return "registerEmployee";
}
employeeManagement.createEmployee(form);
return "redirect:/";
}
public interface EmployeeRegistrationForm {
#NotEmpty(message = "{RegistrationForm.firstname.NotEmpty}")
String getFirstname();
#NotEmpty(message = "{RegistrationForm.lastname.NotEmpty}")
String getLastname();
#NotEmpty(message = "{RegistrationForm.password.NotEmpty}")
String getPassword();
#NotEmpty(message = "{RegistrationForm.address.NotEmpty}")
String getAddress();
#NotEmpty(message = "{RegistrationForm.number.NotEmpty}")
String getNumber();
#NotEmpty(message = "{RegistrationForm.type.NotEmpty}")
String getType();
#NotEmpty(message = "{RegistrationForm.username.NotEmpty}")
String getUsername();
}
However, I cannot seem to be able to create a filled "EmployeeForm" since it is abstract and cannot be instantiated.
Use Mockito to instantiate your abstract classes.
You can use it like this:
EmployeeForm form = mock(EmployeeForm.class);
Now you have an instance of EmployeeForm which you can pass to your methods. If you need to call some methods from your mock you can do somethifg like this:
given(form.getFirstname()).willReturn("John");
This way the form will behave the way you want.
Note: mock() comes from org.mockito.Mockito and given comes from org.mockito.BDDMockito.

Mapstruct: join on id

I am using Mapstruct to map from generated DTOs (metro, xsd) to our business domain objects. My difficulty is that the DTOs don't actually reference child objects but instead use IDs to reference associated instances.
Trying to break this down to a simplified case, I have come up with an example:
SchoolDTO has a lists of teachers and courses. The teacher of a
course is only referenced through a teacherId in each course.
In the business domain School only has a list of teachers who each
hold a list of their courses.
Class diagram: UML: DTO / Domain
Initially I was hoping to solve this in mapstruct syntax with something like a join on foreignId and teacher id (or some qualifiedBy association), pseudo code as follows:
#Mapping(source="courses", target="teachers.courses", where="teacher.id = course.teacherId")
DTOs:
public class SchoolDto {
List<TeacherDto> teachers;
List<CourseDto> courses;
}
public class TeacherDto {
String id;
String name;
}
public class CourseDto {
String name;
String teacherId;
}
Domain:
public class School {
List<Teacher> teachers;
}
public class Teacher {
String name;
List<Course> courses;
}
public class Course {
String name;
}
I am right now working around it with fairly big #AfterMapping methods but I feel this isn't such an exceptional use case - so maybe I am missing something rather obvious. What is the correct/intended way to solve these type of "joins" in a mapping with Mapstruct?
I doubt that you can do this without an #AfterMapping. MapStruct is "just" for mapping one object to another one, it doesn't support any kind of queries to find or join data.
If you are not already using it this sounds like a good use-case for using a context. Then the #AfterMapping is not really big:
#Mapper
public abstract class SchoolMapper {
public School toSchool(SchoolDto school) {
return toSchool( school, school.getCourses() );
}
protected abstract School toSchool(SchoolDto school, #Context List<CourseDto> courses);
#Mapping(target = "courses", ignore = true) // see afterMappingToTeacher
protected abstract Teacher toTeacher(TeacherDto teacher, #Context List<CourseDto> courses);
protected abstract Course toCourse(CourseDto course);
#AfterMapping
void afterMappingToTeacher(#MappingTarget target, TeacherDto source, #Context List<CourseDto> courses) {
// omitted null-checks
List<Course> courses = new ArrayList<>();
for(CourseDto course : courses) {
if(course.getTeacherId().equals(source.getId())) {
courses.add( toCourse(course) );
}
}
target.setCourses( courses );
}
}
(when using Java >= 8 you can use an interface with default methods)
In case you need to query things multiple times you can things create an own class as a context which for example has own methods for finding all courses by a teacher ID.

Play Framework - How to map web data to object data

Imagine the following scenario:
I send a request to a service (which uses Play framework) with the following parameters (parameter's name should be underscored by convention):
first_name=James&second_name=Parker
Moreover I have a model class in my codebase which looks like this.
public class User {
#Constraints.Required
private String firstName;
#Constraints.Required
private String secondName;
public String getFirstName() {
return firstName;
}
public String getSecondName() {
return secondName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
}
All I want to do is to map parameter's names with the corresponding
field names. The following statement using Play Framework fails because
User object cannot be initialized with the given parameters of request.
Form<User> form = Form.form(User.class).bindFromRequest();
Read this first: https://www.playframework.com/documentation/2.1.1/JavaRouting
And then:
GET /myMethod/:firstName/:seccondName controllers.MyController.myMethod(firstName: String, seccondName: String)
and simple Method:
public myMethod(String firstName, String lastName) {
User u = new User();
u.setFirstName(firstName);
u.setSeccondName(seccondName);
}
Obviously if you use play framework 2.
You have underscores in the request parameter names, yet your class has the members in camelCase (e.g. first_name vs fistName [Missing a r here]).
Secondly it also seems like these class members are declared private AND you have no setter methods only getters (i.e. setFirstName).
In order to do the binding your class is instantiated and the values are set and with no way to do so it will fail.
Once you've fixed the above, you should be able to bind and then call form.hasErrors() to check if validation has failed.
Hope that helps.
Update:
To handle the mismatch between the request parameter names and the class member names you would probably have to manually set things up.
In your controller method you would do something like this:
Map<String, String[]> reqBody = request().body().asFormUrlEncoded()
Map<String, String[]> newReqBody = new HashMap<>();
for(Map.Entry<String, String[]> entry: body.entrySet()) {
newBody.put(underscoreToCamelCase(entry.key()), entry.value());
}
Form<User> form = Form.form(User.class).bindFromRequest(newReqBody);
Note that I'm using the overloaded version of bindFromRequest as seen here
You should then implement the underscoreToCamelCase method in a generic enough way to handle all your conventions (Perhaps you might have a situation where there are more than one underscores).

Possible to query a database with subclass entity

Ok, lets say I have a model.
#Entity
public class Person extends Model {
public String name;
public String email;
}
Now. I have a couple of transient fields that don't exist in the database, but I need to access them. Basically they are fields for displaying the Person on a web page.
#Entity
public class PersonDisplay extends Person {
#Transient
public String DT_RowClass = "";
#Transient
public String cellColor = "";
}
Is it possible to query the PersonDisplay and get the Person object plus the PersonDisplay default values?
Like this.
PersonDisplay display = PersonDisplay.find("byEmail" , "test#test.com").first();
I'm running into a few errors when I do this.
Let me know if I need to explain this again.
Look up the #PostLoad annotation, which you can use to initialize any transient variables.

Categories

Resources