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.
Related
I have Spring Boot application (v3.0.2, Java 17), and in it, a simple entity ActivityType and corresponding ActivityDto.
//Entity (uses Lombok 1.18.24)...
#Getter
#Setter
#Entity
public class ActivityType {
#Id
#Column(name = "ActivityTypeId", nullable = false)
private Integer id;
#Column(name = "ActivityName", nullable = false, length = 30)
private String activityName;
#Column(name = "ActivityDescription")
private String activityDescription;
}
//DTO...
public record ActivityTypeDto(
Integer id,
String activityName,
String activityDescription) implements Serializable {
}
I'm using IntelliJ Idea (v2022.2.4) and JPA Buddy (v2022.5.4-222) to generate the Mapper Interface (MapStruct v1.5.3.Final). When I build the Mapper implementation, in the generated code, both the toEntity and toDto methods are incorrect.
#Component public class ActivityTypeMapperImpl implements ActivityTypeMapper {
#Override
public ActivityType toEntity(ActivityTypeDto activityTypeDto) {
if ( activityTypeDto == null ) {
return null;
}
ActivityType activityType = new ActivityType();
return activityType;
}
#Override
public ActivityTypeDto toDto(ActivityType activityType) {
if ( activityType == null ) {
return null;
}
// What's this all about?? Why not activityType.id, etc??
Integer id = null;
String activityName = null;
String activityDescription = null;
ActivityTypeDto activityTypeDto = new ActivityTypeDto( id, activityName, activityDescription );
return activityTypeDto;
}
#Override
public ActivityType partialUpdate(ActivityTypeDto activityTypeDto, ActivityType activityType) {
if ( activityTypeDto == null ) {
return activityType;
}
return activityType;
}
I've tried various alternatives, including using a class for the DTO instead of a record, but no success. Looks like I've missed something, but not sure what.
Update:
I can fix this by not using Lombok for the Entity getters/setters, which leads me on to final question, is there a setting on the MapStruct plugin to take Lomboz into account?
please define you entity like this,
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ActivityType {
#Id
#Column(name = "ActivityTypeId", nullable = false)
private Integer id;
#Column(name = "ActivityName", nullable = false, length = 30)
private String activityName;
#Column(name = "ActivityDescription")
private String activityDescription;
}
then define ActivityTypeDTO like this,
#Data
public class ActivityTypeDTO {
#JsonProperty("id")
private Integer id;
#JsonProperty("ActivityName")
private String ActivityName;
#JsonProperty("activityDescription")
private String activityDescription;
best practice to use MapStruct is like this,
#Mapper(componentModel = "spring", uses = {})
public interface ActivityMapper extends EntityMapper<ActivityTypeDTO, ActivityType> {
ActivityTypeDTO toDto(ActivityType activityType);
ActivityType toEntity(ActivityTypeDTO activityTypeDTO);
}
and EntityMApper in Mapper should be like this,
public interface EntityMapper<D, E> {
E toEntity(D dto);
D toDto(E entity);
}
Now I am sure you mapper work correctly.
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
[Java, Spring Reactive, MongoDB]
I'm currently trying to learn Reactive programming by doing and I found a challenge.
I have db object CategoryDB which looks like this:
#NoArgsConstructor
#Getter
#Setter
#Document(collection = DBConstraints.CATEGORY_COLLECTION_NAME)
class CategoryDB {
#Id
private String id;
private String name;
private String details = "";
#Version
private Long version;
private String parentCategoryId;
private Set<String> childCategoriesIds = new HashSet<>();
}
In a service layer I want to use model object Category.
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
}
I want to create Service with method Mono<Category getById(String id). In this case I want to fetch just one level of childCategories and direct parent Category. By default repository deliver Mono findById(..) and Flux findAllById(..) which I could use, but I'm not sure what would be the best way to receive expected result. I would be grateful for either working example or directions where can I find solution for this problem.
I've spent some time to figure out solution for this problem, but as I'm learning I don't know if it's good way of solving problems.
Added some methods to Category:
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
public void addChildCategory(Category childCategory) {
childCategory.updateParentCategory(this);
this.childCategories.add(childCategory);
}
public void updateParentCategory(Category parentCategory) {
this.parentCategory = parentCategory;
}
}
Function inside service would look like this:
#Override
public Mono<Category> findById(String id) {
return categoryRepository.findById(id).flatMap(
categoryDB -> {
Category category = CategoryDBMapper.INSTANCE.toDomain(categoryDB);
Mono<CategoryDB> parentCategoryMono;
if(!categoryDB.getParentCategoryId().isBlank()){
parentCategoryMono = categoryRepository.findById(categoryDB.getParentCategoryId());
}
else {
parentCategoryMono = Mono.empty();
}
Mono<List<CategoryDB>> childCategoriesMono = categoryRepository.findAllById(categoryDB.getChildCategoriesIds()).collectList();
return Mono.zip(parentCategoryMono, childCategoriesMono, (parentCategoryDB, childCategoriesDB) -> {
Category parentCategory = CategoryDBMapper.INSTANCE.toDomain(parentCategoryDB);
category.updateParentCategory(parentCategory);
childCategoriesDB.forEach(childCategoryDB -> {
Category childCategory = CategoryDBMapper.INSTANCE.toDomain(childCategoryDB);
category.addChildCategory(childCategory);
});
return category;
});
}
);
}
Where mapper is used for just basic properties:
#Mapper
interface CategoryDBMapper {
CategoryDBMapper INSTANCE = Mappers.getMapper(CategoryDBMapper.class);
#Mappings({
#Mapping(target = "parentCategoryId", source = "parentCategory.id"),
#Mapping(target = "childCategoriesIds", ignore = true)
})
CategoryDB toDb(Category category);
#Mappings({
#Mapping(target = "parentCategory", ignore = true),
#Mapping(target = "childCategories", ignore = true)
})
Category toDomain(CategoryDB categoryDB);
}
As I said I don't know if it's correct way of solving the problem, but it seem to work. I would be grateful for review and directions.
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.
I use GraphQL-SPQR Library
The problem is "Validation error of type SubSelectionRequired: Sub selection required for type Timestamp"
Maybe there is expression in query for timestamp
or format in Entity
{"query":
"{findUserPointByUserId(userId:73){rowNum userAccountPointUserId totalPoint pointTypeDescription point userAccountCreatedDate} findUserAccountImgByUserId(userId:73){imageId,userId,presentImgNum}}"
}
Error
{
"errors": [
{
"message": "Validation error of type SubSelectionRequired: Sub selection required for type Timestamp of field userAccountCreatedDate",
"locations": [
{
"line": 1,
"column": 103
}
]
}
]
}
Entity
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "view_user_account_point", schema = "public", catalog = "corus")
public class ViewUserAccountPoint {
#Id
#Basic
#GraphQLQuery(name = "rowNum")
#Column(name = "row_num", nullable = true)
private Long rowNum;
#Basic
#Column(name = "user_account_point_userid", nullable = true)
#GraphQLQuery(name = "userAccountPointUserId")
private Integer userAccountPointUserId;
#Basic
#Column(name = "subject_id", nullable = true)
#GraphQLQuery(name = "subjectId")
private Integer subjectId;
#Basic
#Column(name = "point", nullable = true)
#GraphQLQuery(name = "point")
private Integer point;
#Basic
#Column(name = "user_account_point_typeid", nullable = true)
#GraphQLQuery(name = "userAccountPointTypeId")
private Integer userAccountPointTypeId;
#Basic
#Column(name = "date_created", nullable = true)
#GraphQLQuery(name = "userAccountCreatedDate")
private Timestamp userAccountCreatedDate;
Service
public List<ViewUserAccountPoint> findUserPointByUserId(#GraphQLArgument(name = "userId") Integer userId){
return viewUserAccountPointRepository.findByUserAccountPointUserIdOrderByUserAccountCreatedDateDesc(userId);
}
Controller
private final GraphQL graphQL;
public UserController(UserAccountService userAccountService) {
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withResolverBuilders(
//Resolve by annotations
new AnnotatedResolverBuilder())
.withOperationsFromSingleton(userAccountService,UserAccountService.class)
.withValueMapperFactory(new JacksonValueMapperFactory())
.generate();
graphQL = GraphQL.newGraphQL(schema).build();
}
#PostMapping(value = "/graphql", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> graphql(#RequestBody Map<String, String> request, HttpServletRequest raw) {
ExecutionResult executionResult = graphQL.execute(ExecutionInput.newExecutionInput()
.query(request.get("query"))
.operationName(request.get("operationName"))
.context(raw)
.build());
return executionResult.toSpecification();
}
I search through all query timestamp format
However, i couldn't find
i hope to hear the solution.
thank you
For one reason or another, Timestamp got mapped incorrectly. It ended up being an object and not a scalar.
As mentioned in the issue you opened, it's unclear where is Timestamp in your code coming from.
java.sql.Timestamp is supported out of the box in recent versions of GraphQL SPQR, so you might be on an older version.
If that's not the case, it would mean Timestamp is some other than java.sql.Timestamp, and you'd need to register a custom mapper for it.
public class TimestampMapper implements TypeMapper {
// Define the scalar as needed, see io.leangen.graphql.util.Scalars for inspiration
private static final GraphQLScalarType TIMESTAMP = ...;
#Override
public GraphQLOutputType toGraphQLType(AnnotatedType javaType, OperationMapper operationMapper, Set<Class<? extends TypeMapper>> mappersToSkip, BuildContext buildContext) {
return TIMESTAMP; //it's important to always return the same instance
}
#Override
public GraphQLInputType toGraphQLInputType(AnnotatedType javaType, OperationMapper operationMapper, Set<Class<? extends TypeMapper>> mappersToSkip, BuildContext buildContext) {
return TIMESTAMP; //same as above
}
#Override
public boolean supports(AnnotatedType type) {
return ClassUtils.isSuperClass(Timestamp.class, type);
}
}
Then register your mapper:
generator.withTypeMappers(new TimestampMapper())
It's incorrect query body for my case, make sure you have the right one.