Sorry for bad english.
I currently use dto for request body mapping,
Spring can not know input is null or not received, because both situation is just null.
As an example, Like this.
// dto
public class UpdateDto {
private String name;
private Integer price;
}
// entity
public class Product {
#Id
#GeneratedValue
private Long id;
#Column(nullable = true)
private String name;
#Column(nullable = true)
private Integer price;
}
I solved in string case using empty string. When empty string received, update this field to null. Because most of case empty string can treated as null.
// update name
if (updateDto.getName() != null) {
if (updateDto.getName() == "") {
// set name to null
} else {
// just update to getName() value
}
}
// update price
// when i set to null?
However integer case, I don't know how can i solve this problem.
Anything good idea?
you can use primitive data type int and set nullable as false since null doesn't make sense for numbers
use primitive data type for price in both entity and dto
Entity:
#Column(nullable = false)
private int price;
DTO:
private int price;
I found the easiest solution.
related link(In korean): https://www.it-gundan.com/ko/java/spring-rest-controller%EC%97%90%EC%84%9C-%EB%B6%80%EB%B6%84-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EC%97%90-%EB%8C%80%ED%95%B4-null-%EA%B0%92%EA%B3%BC-%EC%A0%9C%EA%B3%B5%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EA%B0%92%EC%9D%84-%EA%B5%AC%EB%B3%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95/827265839/
the solution is, add dirty check boolean flag in setter.
in case of above,
// dto
public class UpdateDto {
private String name;
private Integer price;
private Boolean isPriceDirty;
// when setter setPrice,
public void setPrice(Integer price) {
this.price = price;
this.isPriceDirty = true;
}
}
then, output can set null only when dirty is true.
Related
i'm using java, for example, i have 2 tables staff(id, name, status_id, company_id) and company(id, name), the corresponding entity looks like:
public class Staff {
private Integer id;
private String name;
private Integer statusId;
private Integer companyId;
private Company company;
}
public class Company {
private Integer id;
private String name;
private List<Staff> staffList;
}
for status_id of table staff, 0 means New, 1 represents Active and 2 stands for Inactive.
I need to show New, Active or Inactive on html page/excel when describe a staff status rather than 0, 1 or 2.
And I have a StaffDto:
public class StaffDto {
private Integer id;
private String name;
private Integer statusId;
private String companyName;
}
my questions are:
the statusName(New/Active/Inactive) should be in StaffDto, such that there is no need to calculate status name according to statusId on each client, right?
what is a good practice to get statusName base on statusId?
I should write code like
public class StaffDto {
private Integer statusId;
private String statusName;
public String getStatusName() {
switch(statusId) {
case 0: return "New";
case 1: return "Active";
case 2: return "Inactive";
}
}
}
is this a good practice? or something else(e.g. enum) is better?
if the logic of getting status name is added in StaffDto, what if there is another dtoj(e.g. ADto) also need to show status name, then I have to re-write this logic in ADto?
what if one client need to show New, Active or Inactive, while another client need to show A, B or C or something else, then what should I return in StaffDto? do I still return New, Active or Inactive in StaffDto, and other client need to calculate N, A or I base on statusId on their client? or should I return something else to client instead of xxxDto?
I too would go for enum as you mentioned, bind the status code to the name
then, you do not have to rewrite the logic in DTOs, Make your model have the enum rather than code or name
enum can have its own methods like getShortName for different representations
enum Status {
NEW(0), Active(1), InActive(2);
private final int code;
Status(int code) {
this.code = code;
}
public String getShortName() {
return this.name().substring(0, 1).toUpperCase();
}
public int getCode() {
return code;
}
}
How can I check the DB if a record already exists for a given case using Spring JPA query using one params. If it exists it does an update or else it inserts as a new record. I have tried a simple implementation, but I end up with a 500 server error no other error is logged.
Resolved [java.lang.NullPointerException] Completed 500
INTERNAL_SERVER_ERROR
This is what I have tried so far
My Controller
#RequestMapping(path="/updatedm",method = RequestMethod.POST)
public Boolean updateDMStatus(#RequestParam("country") String country,
#RequestParam("Id") String pId,
#RequestParam("case") String case,
#RequestParam("status") String status,
#RequestParam("updatedBy") String updatedBy){
Boolean createDm = eaDataStoreService.updateDM(country,Id,case,status,updatedBy);
return createDm;
}
My repository
public interface DMValidatedRepository extends CrudRepository<DMValidated, String> {
DMValidated findByCase(#Param("case") String case);
}
My Service
public boolean updateDM(String country, String Id, String case, String status,String updatedBy) {
DMValidated document = dmValidated.findByCase(case);
if(document != null){
document.setStatus(status);
document.setUpdatedBy(updatedBy);
dmValidated.save(document);
}else{
document.getId();
document.getCase();
document.getCountry();
document.getStatus();
document.getUpdatedBy();
dmValidated.save(document);
}
return true;
}
My Model
#Data
#ToString
#Entity
#Table(name = "DMStatus")
public class DMValidated{
#Id
#GeneratedValue
private String id;
#Column(name = "country")
private String country;
#Column(name = "Id")
private String Id;
#Column(name = "Case")
private String case;
#Column(name = "status")
private String status;
#Column(name = "updatedBy")
private String updatedBy;
public DMValidated( String country, String Id,
String case, String status, String updatedBy){
this.country = country;
this.Id=Id;
this.case = case;
this.status =status;
this.updatedBy = updatedBy;
}
Am not sure if this is the right way of doing this, have tried to research but I have not found something concreate. How can I achieve this?
It's not difficult you have just forgotten the code to create properly the object when it is new and needs to be inserted
if(document != null){
document.setStatus(status);
document.setUpdatedBy(updatedBy);
}else{
document = new DMValidated();
document.setId(Id);
document.setCase(case);
document.setCountry(country);
document.setStatus(status);
document.setUpdatedBy(updatedBy);
}
dmValidated.save(document);
The error that occurred previously in your code is the following
}else{
document.getId();
...
}
In this else you get only when document == null, so when you invoke document.getId() a null pointer exception is thrown, and then 500 error occurs.
else{
document.getId();
document.getCase();
document.getCountry();
document.getStatus();
document.getUpdatedBy();
dmValidated.save(document);
}
above case document object initialize but property datatype string is always null
Thats why each time document.getId() is null or other property to get occurred Nullpointer.
Correct code
else{
document = new DMValidated();
document.setId(Id);
document.setCase(case);
document.setCountry(country);
document.setStatus(status);
document.setUpdatedBy(updatedBy);
}
This operation you are attempting to do is colloquially called UPSERT, and happens to be a bit of challenge to achieve purely with JPA and Hibernate. The way I've done it in the past is with jOOQ.
That said, there are good resources here in StackOverflow that will help you find an answer faster if you search for those keywords.
Anyhow, here are some readings you may want to go over first:
https://www.jooq.org/doc/3.1/manual/sql-building/sql-statements/insert-statement/insert-on-duplicate-key/
https://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/
https://vladmihalcea.com/jooq-facts-from-jpa-annotations-to-jooq-table-mappings/
UPSERT in PostgreSQL using jOOQ
Pretty much any reading from Vlad Mihalcea will give you insights of this topic, plus JPA and Hibernate in general.
To run the application i use tomcat 8.5.50 package in war.
i use spring 5.2 version.
in my code i want to use LocalDataTime like this:
#Entity
#Table(name="meals")
public class Meal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "date_time")
#Convert(converter = MealConverter.class)
private LocalDateTime datetime;
#Column(name = "description")
private String description;
#Column(name = "calories")
private int calories;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public LocalDateTime getDatetime() {
return datetime;
}
public void setDatetime(LocalDateTime datetime) {
this.datetime = datetime;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getCalories() {
return calories;
}
public void setCalories(int calories) {
this.calories = calories;
}
}
my Converter:
#Converter(autoApply = true)
public class MealConverter implements AttributeConverter<Meal, String> {
private static final String SEPARATOR = ", ";
#Override
public String convertToDatabaseColumn(Meal meal) {
StringBuilder sb = new StringBuilder();
sb.append(meal.getCalories()).append(SEPARATOR)
.append(meal.getDatetime())
.append(SEPARATOR)
.append(meal.getDescription());
return sb.toString();
}
#Override
public Meal convertToEntityAttribute(String dbData) {
String[] rgb = dbData.split(SEPARATOR);
Meal meal = new Meal(Integer.valueOf(rgb[0]),
LocalDateTime(valueOf(rgb[1]),
rgb[2],
rgb[3]);
return meal;
}
}
I am trying to use the converter in the convertToEntityAttribute method but the compiler does not allow me to do this. What needs to be fixed in my Converter?
Meal meal = new Meal(Integer.valueOf(rgb[0]),
LocalDateTime(valueOf(rgb[1]),
rgb[2],
rgb[3]);
Your Meal class doesn’t seem to have any explicit constructor, so you cannot pass arguments to new Meal(). You seem to be trying to pass two arguments. You may want to create a suitable constructor, or you may want to pass the two values into the Meal using setters after the object has been created.
LocalDateTime is a class, but you seem to try to call it as a method with three arguments. If that’s java.time.LocalDateTime, you probably intended LocalDateTime.of(someArguemts), but there isn’t any three-argument of method of that class. If you explain better what result you expect, we can guide you better.
As the first argument to LocalDateTime you have a call to a valueOf method that doesn’t seem to be declared in your class. You may have intended Integer.valueOf as in the preceding line.
If you are trying to use your RGB values for initializing a date (don’t know what sense that might make), be aware that if your RGB values go up to 255, this will likely fail with an exception since month numbers only go up to 12 and day of month up to 31.
I am far from sure that the following is correct or does what you want it to do, but it’s a guess at what you may be after.
#Override
public Meal convertToEntityAttribute(String dbData) {
String[] fields = dbData.split(SEPARATOR);
Meal meal = new Meal();
meal.setCalories(Integer.parseInt(fields[0]));
meal.setDateTime(LocalDateTime.parse(fields[1]));
meal.setDescription(fields[2]);
return meal;
}
I am trying to do the opposite of your convertToDatabaseColumn method. I have discarded the variable name rgb because I didn’t see how it couldn’t be misleading here.
I am creating a REST api service for a mysql database. I've generated classes using IntelliJ's persistence tool. It does a pretty good job.
There are some quirks to the schema that I am working with. The users want the endpoints to be accessible by another property other than the "id" primary key column.
Ex: /object/<name property>' versus/object/`.
Here is the catch though. The schema can change. The name property is not going anywhere though so I can safely assume that will always be on the object.
I've learned that you can use Superclasses to force these generated entites to have custom properties without affecting the database schema. I dont want to make a model change in the generated entity and have that update the database table layout as it is not my database.
I have a class called Animal.
#Entity
#Table(name = "animals", schema = "xyz123", catalog = "")
public class AnimalEntity extends AnimalSuperclass {
private Integer id;
private String name;
private String description;
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = true, length = 80)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Basic
#Column(name = "description", nullable = true, length = 255)
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleEntity that = (RoleEntity) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(description, that.description);
}
#Override
public int hashCode() {
return Objects.hash(id, name, description);
}
}
I have to manually add extends AnimalSuperclass. Which is fine for now. Eventually I am going to try to generate these using .xmls on runtime.
Then I have this superclass..
#MappedSuperclass
public class AnimalSuperclass implements Serializable {
private String testMessage;
private String name;
private Integer id;
#Transient
public String getTestMessage() {
return this.testMessage;
}
public void setTestMessage(String id) {
this.testMessage = testMessage;
}
}
What I want to do is force the #Id annotation to be on the name property from within the superclass. Something like this..
#MappedSuperclass
public class AnimalSuperclass implements Serializable {
private String testMessage;
private String name;
private Integer id;
#Transient
public String getTestMessage() {
return this.testMessage;
}
public void setTestMessage(String id) {
this.testMessage = testMessage;
}
#Basic
#Id
#Column(name = "name", nullable = false, length = 15)
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
#NaturalId
#Column(name = "id", nullable = false)
private Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
}
How do I go about doing that? Currently this throws an error when I hit the endpoint: {"cause":null,"message":"Id must be assignable to Serializable!: null"}
Java is not my first language so I am not an expert by any means. But from what I've read, its not possible to override subclass properties from the superclass. Is there a better way to approach this, maybe by using RepositoryRestConfiguration? I am using PagingAndSortingRepository to serve these entities. I cannot extend the entities and use my superclass as a child as that creates a dType property in the schema and I cannot alter the table layout.
There is no hard link between the request and your entity. In your repository you can write methods that can query the data that is brought it from the request.
For example if they are requesting a name you can do something like
Page<AnimalEntity> findByName(String name, Pageable pageable);
in your Repository. Spring will take care of the rest and then you can call this in your controller.
#Service
public class AnimalService {
#Autowired
private AnimalEntityRepository animalRepo;
public Page<AnimalEntity> findAnimal(String name) {
Page<AnimalEntity> animals = animalRepo.findByName(name, new PageRequest(1,20));
return animals;
}
}
One thing to mention is that depending on how you configured Hibernate when sending an entity back to the client and the entity is seralized you might get an failed to lazy initialize error. If that is the case your entities will have to be converted to a POJO (plain old java object) and that sent back.
I am facing, "could not resolve property isManager of" Hibernate query exception.
I have the following java class:
public class Employee implements Serializable {
#Column(name = "employee_id")
private Integer employeeId;
#Column(name = "name")
private String name;
#Column(name = "is_manager")
private boolean manager;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isManager(){
return employeeId;
}
public void setManager(boolean manager) {
this.manager = manager;
}
These are the eclipse generated setters and getters. When I replaced,
public boolean isManager() {
return employeeId;
} with
public boolean getManager() {
return employeeId;
}
The error is gone. I have gone through so many stack overflow examples. But everywhere, it's given, we shouldn't name the field that starts with "is".
Could anyone please explain?
Thanks.
Hibernate use the getXxx and setXxx for the all the variable . So the getter the isManager variable getter getManager() is not available for hibernate.
so it get error. To resolve the issue.
You can generate the your own getter and setter as getManager() & setManager()
You can use the Boolean object instead of primitives.
Note: getIsManager is not meaningful to access the primitive boolean type. So Eclipse generate the getter for all the primitive start with isXXX as a getter.
Thanks for the comments.
I found the solution. When we name the field as isManager with the STS generated setter and getter, default value of Boolean, false is saved in DB.
field shouldn't start with auxiliary verb. According to my example:
#Column(name = "is_manager")
private boolean manager;
public boolean isManager() {
return employeeId;
}
public void setManager(boolean manager) {
this.manager = manager;
}
The above setter and getter works properly. I haven't updated the #param value in DB interface method.
Thanks.