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
Related
I have Employee class
#Data
public class Employee {
private Integer id;
private String firstname;
private String lastname;
private String email;
private String code;
private Integer managerEmployeeId;
private Integer departmentId;
private Integer roleid;
private String password;
}
And I have created EmployeeDTO class
#Data
public class EmployeeDTO extends RepresentationModel<EmployeeDTO> {
private String firstname;
private String lastname;
private String email;
private String code;
private Employee manager;
private Department department;
private Role role;
}
Department and Role both are entity classes
#Data
public class Department extends RepresentationModel<Department>{
private Integer id;
private String departmentName;
}
I have service layer class to get Department object by department id and set it to EmployeeDTO
So the mapping logic is as follows
Get departmentId from Employee -> call getDepartmentById of service and get Department object -> set Department object of EmployeeDTO
I created a TypeMap and calling addMappings as follows
private EmployeeDTO employeeToDto(Employee employee) {
ModelMapper modelMapper = new ModelMapper();
TypeMap<Employee, EmployeeDTO> propertyMap = modelMapper.createTypeMap(Employee.class, EmployeeDTO.class);
propertyMap.addMappings(
mapper -> mapper.map(
emp -> this.getDepartmentById(emp.getDepartmentId()), EmployeeDTO::setDepartment
)
);
return modelMapper.map(employee, EmployeeDTO.class);
}
I have created a custom method to handle null values as service layer returns Optional
private Department getDepartmentById(Integer departmentId) {
Optional<Department> deptOptional = this.departmentService.getDepartment(departmentId);
if (!deptOptional.isPresent()) {
return null;
}
return deptOptional.get();
}
Upon debugging, I observed that the emp in map() method is null, and my custom method getDepartmentById is called immediately with departmentId as 0. I confirmed that the employee object passed has some department id and it is not 0.
Firstly, the addMappings() is attempting to perform the operation immediately I don't know why, which I think results in departmentId being passed as 0 as I have not passed the actual object to map yet. I am not aware of any other method as of now.
I'm writing a program that changes a member's password, I fetched the user by id from the database when I test the endpoint on postman it returns 200 OK, but fails to update the password in the database to the new password, What is the right logic to use for this task? my code is below.
Member
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name ="member",
indexes = {
#Index(
columnList = "email_address",
name = "email_address_idx",
unique = true
),
},
uniqueConstraints = {
#UniqueConstraint(
columnNames = {"email_address", "phone_number"},
name = "email_address_phone_number_uq"
)
}
)
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "nationality_id")
private Country nationality;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "country_of_residence_id")
private Country countryOfResidence;
#Temporal(TemporalType.DATE)
#Column(name ="date_of_birth")
private Date dateOfBirth = new Date();
#Column(name ="current_job_title")
private String currentJobTitle;
#Column(name = "email_address", nullable = false)
private String emailAddress;
#Column(name = "username")
private String username;
#Column(name ="phone_number")
private String phoneNumber;
#Column(name ="city")
private String city;
#Column(name ="state")
private String state;
#Column(name ="password", nullable = false)
private String password;
}
PasswordDto
#Data
public class ChangePasswordDto {
private String password;
private String oldPassword;
private String newPassword;
private String reNewPassword;
PasswordService
#Slf4j
#Service
public class ChangePasswordServiceImpl implements ChangePasswordService {
#Autowired
private ModelMapper modelMapper;
#Autowired
private PasswordEncoder passwordEncoder;
private final PasswordJpaRepository jpaRepository;
public ChangePasswordServiceImpl(PasswordJpaRepository jpaRepository) {
this.jpaRepository = jpaRepository;
}
#Override
#Transactional
public Member changePassword(Long id, ChangePasswordDto password) {
final Member member = jpaRepository.findById(id);
Member getPassword = new Member();
getPassword = modelMapper.map(id, Member.class);
Member updatedPassword = new Member();
if (member.getPassword().equals(checkIfValidOldPassword(member, password.getOldPassword()))){
if (password.getNewPassword().equals(password.getReNewPassword())) {
updatedPassword = changPassword(member, password.getNewPassword());
}
}else{
return null;
}
return updatedPassword;
}
#Override
#Transactional
public boolean checkIfValidOldPassword(Member member, String oldPassword) {
return matches(oldPassword, member.getPassword());
}
#Override
#Transactional
public Member changPassword(Member member, String password) {
member.setPassword(password);
jpaRepository.save(member);
return member;
}
}
PasswordController
#RestController
#RequestMapping(
value = "password",
produces = { MediaType.APPLICATION_JSON_VALUE }
)
public class ChangePasswordController {
private ChangePasswordService service;
public ChangePasswordController(ChangePasswordService passwordService) {
this.service = passwordService;
}
#PostMapping("/change-password/{id}")
public Member changePassword(#Validated #RequestBody ChangePasswordDto password, #PathVariable(name = "id") Long id){
return service.changePassword(id, password);
}
}
Troubleshooting and Debugging
In the future, it would be helpful for you to post the request as a cURL command as well as the Catalina logs.
Your bug is in the following statement
if (member.getPassword().equals(checkIfValidOldPassword(member, password.getOldPassword()))){
// The above expression is always evaluating false
}
The member.getPassword() accessory method returns a String. However checkIfValidOldPassword method returns a boolean. Let's refactor the code for demonstration.
String pwd = member.getPassword();
String opwd = password.getOldPassword();
boolean isValud = checkIfValidOldPassword(member, opwd);
assert pwd.equals(isValid);
You are attempting to evaluate the equality of a String and a primitive boolean ( autoboxed to the Boolean wrapper class object ). Likely this statement always evaluates false thus you are returning null and not invoking the code that actually makes the update.
Autoboxing Explained
The reason this did not throw a compile time exception is due to a feature known as Autoboxing. Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes.
In your example, the equals method has a single parameter of type Object. So although you passed a primitive boolean as the first parameter in the equals method, the Java compiler converted it to an Object of type Boolean. Because Boolean is an object, and all objects inherit from Object, no exception is thrown.
Most likely you are comparing the response of ‘toString’ method on your Boolean object which returns the string “true” when the primitive boolean value corresponds with true and “false” otherwise.
Security Concerns
Please be extremely careful when you are attempting to roll your own authentication or authorization features. For the most part, a password should be salted and encrypted before storing the information at-rest. Therefore, you should only ever be able to compare one salted/encrypted string with another salted/encrypted string
I'm using Spring MVC with Spring data.
Simple example of my problem:
My dao Service class:
#Service
#AllArgsConstructor
#Transactional
public class FooService{
private FooRepository fooRepo;
public Foo save(Foo foo){
return fooRepo.save(foo);
}
}
and controller:
#Controller
#AllArgsConstructor
#Transactional //if I remove this, method add does not save a foo.
//But I don't understand why, because FooService already has #Transactional annotation
public class FooController{
private FooService fooService;
#PostMapping("/add")
public String add(#RequestParam("programName") String programName, #RequestParam("id") long id){
Foo foo = fooService.findById(id).get();
foo.setProgramName(programName);
fooService.save(foo);
return "somePage";
}
}
If I remove #Transaction annotation from controller class, method save will not update foo object.
And I don't understand why I should mark controller by #Transactional annotation if I already mark service class by this annotation?
############ UPDATE ####################
Simple detailed description:
I have Program and Education entities. One Program has many Education, Education entity has foreign key program_id.
There is a page with Program form, there are fields: program id, program theme,..., and field with a list of education id separated by commas.
I'm trying to update the education list at the program, so I add a new education id at the page form and click save. Through debugger I see, that new education has appeared in the program, but changes do not appear in the database.
#Controller
#RequestMapping("/admin/program")
#AllArgsConstructor //this is lombok, all services autowired by lombok with through constructor parameters
#Transactional//if I remove this, method add does not save a foo.
//But I don't understand why, because FooService already has #Transactional annotation
public class AdminProgramController {
private final ProgramService programService;
private final EducationService educationService;
#PostMapping("/add")
public String add(#RequestParam("themeName") String themeName, #RequestParam("orderIndex") int orderIndex,
#RequestParam(value = "educationList", defaultValue = "") String educationList,
#RequestParam(value = "practicalTestId") long practicalTestId){
saveProgram(themeName, orderIndex, educationList, practicalTestId);
return "adminProgramAdd";
private Program saveProgram(long programId, String themeName, int orderIndex, String educationList, long practicalTestId){
List<Long> longEducationList = Util.longParseEducationList(parsedEducationList); //this is list of Education id separeted by commas that I load from page form
//creating new program and set data from page form
Program program = new Program();
program.setId(programId);
program.setThemeName(themeName);
program.setOrderIndex(orderIndex);
//starting loop by education id list
longEducationList.stream()
.map(educationRepo::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(edu->{
//linking program and education
program.getEducationList().add(edu);
edu.setProgram(program);
});
//saving new program or updating by service if there is one already
Program savedProgram = programService.save(program);
//saving education with updated program
for(Education edu : savedProgram.getEducationList())
{
educationService.save(edu);
}
return savedProgram;
}
}
ProgramService:
#Service
#AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
#Transactional
public class ProgramService {
private ProgramRepo programRepo;
//other code here.....
public Program save(Program program) {
Optional<Program> programOpt = programRepo.findById(program.getId());
//checking if the program is already exist, then update it paramateres
if(programOpt.isPresent()){
Program prgm = programOpt.get();
prgm.setThemeName(program.getThemeName());
prgm.setOrderIndex(program.getOrderIndex());
prgm.setPracticalTest(program.getPracticalTest());
prgm.setEducationList(program.getEducationList());
return programRepo.save(prgm);
}
//if not exist then just save new program
else{
return programRepo.save(program);
}
}
}
Education service
#Service
#AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
#Transactional
public class EducationService {
private EducationRepo educationRepo;
//other code here....
public Education save(Education education){
return educationRepo.save(education);
}
}
Program entity:
#Entity
#ToString(exclude = {"myUserList", "educationList", "practicalTest"})
#Getter
#Setter
#NoArgsConstructor
public class Program implements Comparable<Program>{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "theme_name")
private String themeName;
#Column(name = "order_index")
private int orderIndex; //from 1 to infinity
#OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
#OrderBy("orderIndex asc")
private List<Education> educationList = new ArrayList<>();
#OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
private List<MyUser> myUserList = new ArrayList<>();
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "test_id")
private PracticalTest practicalTest;
public Program(int orderIndex, String themeName) {
this.orderIndex = orderIndex;
this.themeName = themeName;
}
public Program(long id) {
this.id = id;
}
//other code here....
}
Education entity:
#Entity
#ToString(exclude = {"program", "myUserList"})
#Getter
#Setter
#NoArgsConstructor
public class Education implements Comparable<Education>{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String link;
#Column(name = "order_index")
private int orderIndex;
private String type;
private String task;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "program_id")
private Program program;
#OneToMany(mappedBy = "education", fetch = FetchType.LAZY)
private List<MyUser> myUserList = new ArrayList<>();
public Education(String link, int orderIndex, String task, Program program) {
this.link = link;
this.orderIndex = orderIndex;
this.task = task;
this.program = program;
}
//other code here....
}
Program repo:
#Repository
public interface ProgramRepo extends CrudRepository<Program, Long> {
Optional<Program> findByPracticalTest(PracticalTest practicalTest);
Optional<Program> findByOrderIndex(int orderIndex);
List<Program> findByIdBetween(long start, long end);
}
Education repo:
#Repository
public interface EducationRepo extends CrudRepository<Education, Long> {
Optional<Education> findByProgramAndOrderIndex(Program program, int orderIndex);
#Query("select MAX(e.orderIndex) from Education e where e.program.id = ?1")
int findLastEducationIndexByProgramId(long programId);
}
I think the problem is program object created in one transaction and saved in another. That's why if I put Transactional on controller it works. There are two ways to solve the problem:
Without transactional on the controller: then I must save education object at first, because it has program id field and then save the program object.
With transactional on controller: then saving order has no matter, because saving object occurs in one transaction
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.
Can I save an object that contains another object directly in the database?
My back-end structure is like this:
Rest services
Services
Repository (extends JpaRepository)
Model
Suppose that I have two entity in my model: Company and Address. Both generated by the JPA tool provided by IntelliJ.
Company class model
#Entity
public class Company {
private int idcompany;
private String name;
private Address address;
#Id
#Column(name = "idcompany")
public int getIdcompany() {
return idcompany;
}
public void setIdcompany(int idcompany) {
this.idcompany = idcompany;
}
#Basic
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne
#JoinColumn(name = "idaddress", referencedColumnName = "idaddress", nullable = false)
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address class model
#Entity
public class Address {
private long idaddress;
private String zip;
#Id
#Column(name = "idaddress")
public long getIdaddress() {
return idaddress;
}
public void setIdaddress(long idaddress) {
this.idaddress = idaddress;
}
#Basic
#Column(name = "zip")
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
}
Moreover, both entities have an interface that extends the interface JpaRepository<T,ID>. So, I have CompanyRepository and AddressRepository. I use these two interfaces in they respective Service classes: CompanyService and AddressService. This is where I put the business logic. All ok!
Now, I recive using a REST service, through POST an object Company that contains the object Address. I need to save them into the database (MySql).
In the json file there are Company that contains Address!
Until now, I've always done these steps:
Save Address;
Retrieve the Address just saved (i need the idaddress);
I associate the Address to the company using setAddress;
Save Company
I tried to save the object Company received via REST calling the method save from CompanyService (using CompanyRepository) but I got the error:
Column 'idaddress' cannot be null
I ask you. Is there an easier way to save Company and Address at the same time using JpaRepository???
You don't have defined any cascading.
You could define PERSIST to let the Address persist when Company is persisted:
#ManyToOne
#JoinColumn(name = "idaddress", referencedColumnName = "idaddress", nullable = false,
cascade = CascadeType.PERSIST)
public Address getAddress() {
return address;
}
For every method on the EntityManager there is a CascadeType defined.
Read more about the cascade types here:
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/