I was asked to create a request that will list customers based on their genders. However the request method has to be POST and I was adviced to use a dto and a mapper to achieve this goal. I'll give some examples to further explain my problem.
My Customer entity is as follows:
#Data
#Entity
#Table(name = "customer", schema = "public")
public class Customer implements Serializable {
#Id
#Column(name = "id_", unique = true)
private Integer id_;
#Column(name = "name_", nullable = false)
private String name_;
#Column(name = "surname", nullable = false)
private String surname;
#Column(name = "phone", nullable = false)
private String phone;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "gender", columnDefinition = "text", nullable = false)
private String gender;
#JsonBackReference
#OneToMany(mappedBy = "customer")
Set<PurchaseOrder> purchaseOrder = new HashSet();
public Customer() {
}
This is an example for customer stream based on my code:
{
"id_": 1,
"name_": "Laura",
"surname": "Blake",
"phone": "95334567865",
"email": "Bulvar 216 PF Changs",
"gender": "W"
}
I am asked to give this stream as input:
{ "gender": "W" }
As an output I am expected to receive a list of customer entities with gender 'W'. So, I have created a CustomerDto class:
#Data
public class CustomerDto {
private String gender;
}
This is the method I'm going to use defined in CustomerRepository:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(Customer customer);
}
This is what I have on my controller and service, respectively:
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody #Valid Customer customer) {
return service.getCustomersByGender(customer);
}
public List<Customer> getCustomersByGender(Customer customer) {
return repo.findAllByGender(customer);
}
I added ModelMapper to my dependencies and I tried several methods both with customer and customerDto inputs. But I failed to successfully list customers by gender. I'd appreciate a code answer with proper explanations so that I can understand what's going on.
EDIT:
This is the answer without using ModelMapper. In case anyone is searching for a solution:
Controller:
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody #Valid CustomerDto dto) {
String gender = dto.getGender();
return service.getCustomersByGender(gender);
}
Repository:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(String gender);
}
Service:
public List<Customer> getCustomersByGender(String gender) {
return repo.findAllByGender(gender);
}
Okay, that's pretty simple.
In Your Controller
//Pass a parameter geneder = someValue and you will get that in the gender variable
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody AnotherDTO gender) {
return service.getCustomersByGender(anotherDTO.getGender());
}
DTO's are meant to be used to accept a request body if they have a large data payload or for casting custom responses. As I think your superior would have asked to use a DTO for customizing response. Put only those varibles which you want to be there in the response.
ResponseDTO.java
public class CustomerDto {
private String gender;
private Long id;
private String name;
private String surname;
private String phone;
private String email;
//Standard Setters and getters
//Also, you need to make sure that the variable name in the Entity should
//be exactly same as your Entity. In DTO you just need to put those
//variables which you want to be in response.
}
Your Repo
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(String gender);
}
Your Business layer. Some Java class.
public List<Customer> getCustomersByGender(String gender) {
List<Customer> response = new ArrayList<>();
List<Customer> list = repo.findAllByGender(gender);
//Autowire Object mapper of manually create a new instance.
ObjectMapper mapper = new ObjectMapper();
list.forEach(customer ->{
YourDTO ref = mapper.map(list, YourDTO.class);
response.add(ref);
});
return response;
}
Now, you can simply return the response that you've received from the service layer.
Related
So. I'm trying to have a custom validator for my method. This one:
#PostMapping("/person")
public Person create(#Validated(value = IPersonValidator.class) #RequestBody Person person, Errors errors) {
LOGGER.info("Creating a person with the following fields: ");
LOGGER.info(person.toString());
if (errors.hasErrors()) {
LOGGER.info("ERROR. Error creating the person!!");
return null;
}
//return personService.create(person);
return null;
}
This is my Validator class:
#Component
public class PersonValidator implements Validator, IPersonValidator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Person person = (Person) target;
if (person.getName() == null || person.getName().trim().isEmpty()) {
errors.reject("name.empty", "null or empty");
}
}
}
And my interface:
public interface IPersonValidator {
}
And my class:
#Entity
#Table(name = "persons")
#Data
#Getter
#Setter
#NoArgsConstructor
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_sequence")
#SequenceGenerator(name = "person_sequence", sequenceName = "person_sequence", allocationSize = 1)
#Column(name = "id")
private Long id;
//#NotBlank(message = "Name cannot be null or empty")
#Column(name = "name")
private String name;
#NotBlank(message = "Lastname cannot be null or empty")
#Column(name = "last_name")
private String lastName;
#Column(name = "last_name_2")
private String lastName2;
public Person(String name, String lastName, String lastName2) {
this.name = name;
this.lastName = lastName;
this.lastName2 = lastName2;
}
}
What am I expecting is for it to enter the validate method since Im using the annotation (#Validate) with the class that I want. I tried it too using the #Valid annotation, but it still won't enter in my custom validate method.
What am I doing wrong?
This code #Validated(value = IPersonValidator.class) is use for "grouping" of validations. Yoy say to validator valid only these constrains with groups = {IPersonValidator.class}.
For better understanding of custom validators see: https://www.baeldung.com/spring-mvc-custom-validator
edit:
This code #Validated(value = IPersonValidator.class) doesn't use your validator. From #Validated documantation:
value() specify one or more validation groups to apply to the
validation step kicked off by this annotation.
Validation groups are use for turning on/off of some valdations for some fields in data class. Se more here: https://www.baeldung.com/javax-validation-groups
For creating you own custom valdation you must create custom constrain annotation and custom validator for this annotation. This custom constrain annotation you may use on some field in data class. I really strong recommend read this https://www.baeldung.com/spring-mvc-custom-validator
Im trying to save data to the database, but instead of it JPA is saving null to the database, I am usually doing it with dto, but since it s a very small project, I want to do it without it
Entity
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String department;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_COURSE_TABLE",
joinColumns = {
#JoinColumn(name="student_id",referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "couse_id",referencedColumnName = "id")
})
#JsonManagedReference
private Set<Course> courses;
}
DAO
#Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
Controller
#RestController
#RequestMapping("/api")
public class StudentCourseController {
private final StudentRepository studentRepository;
private final CourseRepository courseRepository;
public StudentCourseController(StudentRepository studentRepository,
CourseRepository courseRepository) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository;
}
#PostMapping
public Student addStudent (Student student){
return studentRepository.save(student);
}
}
and in my application.properties
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialec
t
Make sure is deserialization correct in controller method "addStudent" - If You want to pass Student entity in request body, add annotation #RequestBody to method parameter like:
#PostMapping
public Student addStudent (#RequestBody Student student){
return studentRepository.save(student);
}
If You do not do that - there is possibility to null/empty parameter, what can lead to saving nulls into db.
By the way:
Consider using DTO or Request classes to pass entity in/out your REST application - it will help you avoid circular reference in future and problems with de/serialization your entity.
Consider using ResponseEntity instead of returning object to output - method with ResponseEntity should be like:
#PostMapping
public ResponseEntity<Student> addStudent (#RequestBody Student
student){
return ResponseEntity.ok(studentRepository.save(student));
}
I have an error about "findAll" when I use JPA inheritage tables.
I what make the Json result like this ["asdf" : "adf", "asdf" : "asdf"]
but the return values are like [com.example.model.AccountEntity#57af674a]
Controller
#RequestMapping(value = "/getMyInfoall", produces = MediaType.APPLICATION_JSON_VALUE)
public String getMemberall(#RequestBody JSONObject sendInfo) throws IOException {
List user = UserService.findAll();
JSONObject result = new JSONObject();
result.put("data", user);
return result.toJSONString();
}
Service
public List findAll() {
List users = UserRepository.findAll();
return users;
}
Repository
#Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
}
Entity
#Entity(name = "Users")
#Inheritance(strategy = InheritanceType.JOINED)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int userkey;
#Column(nullable = false, unique = true)
private String id;
#Column(nullable = false, length = 50)
private String name;
#Column(nullable = false)
private String password;
#Column(nullable = true)
private String email;
}
#Entity(name = "Account")
public class AccountEntity extends UserEntity{
#Column(nullable = false, unique = true)
private String accountno;
#Column(nullable = true)
private String accountname;
#Column(nullable = false)
private int accountpw;
#Column(nullable = false)
private long balance;
}```
I would highly recommend to use Spring's default HTTPMessageConverters, e.g. Jackson for JSON.
Building a JSON-array from a List
But you can also use JSON.org's light-weight library like guided on JSON-java README:
convert the List to an array, e.g. UserEntity[]
create a JSONArray from this Java array
return this JSON-array representation formatted as String, using method toString()
List<UserEntity> userList = // a list returned from your database/repo
UserEntity[] myArr = userList.toArray(new UserEntity[userList.size()]); // convert this to an array
// here simply follow the guide on JSON
JSONArray jArr = new JSONArray(myArr);
// return the JSON-array as string
return jArr.toString();
You should convert your UserEntity objects to a UserDto DTO that would then be returned in your Controller. Rely on Jackson instead of JSONObject managed and created by you.
public class UserDto {
private String id;
private String name;
}
You Service should do the mapping:
public List<UserDto> findAll() {
List<UserEntity> users = UserRepository.findAll();
return users.stream().map(user -> // your mapping logic to UserDto object);
}
And your Controller just needs to return it:
#RequestMapping(value = "/getMyInfoall", produces = MediaType.APPLICATION_JSON_VALUE)
public List<UserDto> getMemberall(#RequestBody JSONObject sendInfo) throws IOException {
return UserService.findAll();
}
You can do a similar thing with JSONObject sendInfo and replace it with an object of your own.
Hello I'm trying to test method:
#RequestMapping(value="addOwner", method = RequestMethod.POST)
public String addOwnerDo(#Valid #ModelAttribute(value = "Owner") Owner owner, BindingResult result) {
if (result.hasErrors()){
return "addOwner";
}
ownerService.add(owner);
return "redirect:addOwner";
}
so I wrote this unit test case:
#Test
public void testAddOwnerPost() throws Exception{
mockMvc.perform(post("/addOwner")
.param("firstName", "Adam")
.param("lastName", "Kowalski")
.requestAttr("pet", new Pet())
.requestAttr("phone", new Phone()))
.andExpect(status().is3xxRedirection());
}
the problem is, that Owner entity contains fields that are type of Pet and Phone:
#Entity
public class Owner {
#Id
#GeneratedValue
private int id;
#Column(name = "first_name")
#Size(min=2,max=15,message="{Size}")
private String firstName;
#Column(name = "last_name")
#Size(min=2,max=15,message="{Size}")
private String lastName;
#Valid
#NotNull(message="{NotNull}")
#OneToOne(cascade = CascadeType.ALL,orphanRemoval=true)
private Pet pet;
#Valid
#NotNull(message="{NotNull}")
#OneToOne(cascade = CascadeType.ALL, orphanRemoval=true)
private Phone phone;
So the object beeing send to controller while testing has values of Pet and Phone equal to null. And I would like to send object that has those fields set.
I suggest you to do the following:
On your test class create two attributes :
private Pet pet = new Pet();
private Phone phone = new Phone();
Before your public void testAddOwnerPost() create a new method : #Before
public void setUp()
And then set all of our attribute inside the setUp class.
If you don't care about the value of your phone and pet I suggest you to use Mockito.anyString() // for a string value
O
Given a User entity with the following attributes mapped:
#Entity
#Table(name = "user")
public class User {
//...
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "user_email")
private String email;
#Column(name = "user_password")
private String password;
#Column(name = "user_type")
#Enumerated(EnumType.STRING)
private UserType type;
#Column(name = "user_registered_date")
private Timestamp registeredDate;
#Column(name = "user_dob")
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
//...getters and setters
}
I have created a controller method that returns a user by ID.
#RestController
public class UserController {
//...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userService.findOne(id);
if (user != null) {
return new ResponseEntity<User>(user, HttpStatus.OK);
}
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
//...
}
A service in my business logic layer.
public class UserServiceBean implements UserService {
//...
public User findOne(Long id) {
User user = userRepository.findOne(id);
return user;
}
//...
}
And a repository in my data layer.
public interface UserRepository extends JpaRepository<User, Long> {
}
This works fine, it returns everything about the user, but I use this in several different parts of my application, and have cases when I only want specific fields of the user.
I am learning spring-boot to create web services, and was wondering: Given the current implementation, is there a way of picking the attributes I want to publish in a web service?
If not, what should I change in my implementation to be able to do this?
Thanks.
Firstly, I agree on using DTOs, but if it just a dummy PoC, you can use #JsonIgnore (jackson annotation) in User attributes to avoid serializing them, for example:
#Entity
#Table(name = "user")
public class User {
//...
#Column(name = "user_password")
#JsonIgnore
private String password;
But you can see there, since you are not using DTOs, you would be mixing JPA and Jackson annotations (awful!)
More info about jackson: https://github.com/FasterXML/jackson-annotations