One to Many Relation mapping using org.mapstruct framework - java

How can I map the one to many relationship using org.mapstruct framework?
DTO classes:
#Data
public class ScheduledJobDTO {
private String jobName;
private String jobGroup;
private String jobClass;
private String cronExpression;
private Boolean cronJob;
private Long repeatTime;
private Integer repeatCount;
private Set<ScheduledJobParamsDTO> paramtersDTOs;
}
#Data
#EqualsAndHashCode
public class ScheduledJobParamsDTO {
String name;
String value;
}
Domain Classes -
#Data
#Entity
#Table(name = "scheduled_job")
public class ScheduledJob {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "job_name")
private String jobName;
#Column(name = "job_group")
private String jobGroup;
#Column(name = "job_class")
private String jobClass;
#Column(name = "cron_expression")
private String cronExpression;
#Column(name = "is_cron_job")
private Boolean cronJob;
#Column(name = "repeat_time")
private Long repeatTime;
#Column(name = "repeat_count")
private Integer repeatCount;
#Column(name = "trigger_start_date")
private LocalDate triggerStartDate;
#Column(name = "trigger_end_date")
private LocalDate triggerEndDate;
#Column(name = "created_at")
private LocalDate createdAt;
#Column(name = "modified_at")
private LocalDate modifiedAt;
#Column(name = "is_active")
private Boolean active;
#OneToMany(mappedBy = "scheduledJob")
private Set<ScheduledJobParams> parameters;
}
#Data
#Entity
#Table(name = "scheduled_job_params")
#EqualsAndHashCode
public class ScheduledJobParams {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "scheduled_job_id", nullable = false)
ScheduledJob scheduledJob;
String name;
String value;
}
Mapper Class -
#Mapping(source = ".", target = ".")
#Mapping(source = "paramtersDTOs", target = "parameters")
ScheduledJob mapToDomain(ScheduledJobDTO scheduledJobDTO);
Now, the above mapper is mapping the ScheduledJob & ScheduledJobParams, but the ScheduledJobParams has reference of ScheduledJob.
How can I map the reference ScheduledJob to ScheduledJobParams?

You can achieve that through #AfterMapping with #MappedTarget. This is described in the reference documentation: 12.2. Mapping customization with before-mapping and after-mapping methods.
// Java 8+ otherwise you need to use an abstract class and a for-loop instead
#Mapper(componentModel = "spring")
public interface ScheduledJobMapper {
#Mapping(target = "parameters", source = "paramtersDTOs")
ScheduledJob mapToDomain(ScheduledJobDTO dto);
#AfterMapping
default void after(#MappingTarget ScheduledJob domain, ScheduledJobDTO dto) {
domain.getParameters().forEach(scheduledJobParams -> {
scheduledJobParams.setScheduledJob(domain);
});
}
}
However, I am sure you don't need to fill the bidirectional relationship when you map back from the DTO into the entity (this is what I understand as you refer to "domain"). Note printing out or serializing such object i.e. into JSON or XML throws java.lang.StackOverflowError if not properly handled.

Related

Spring Boot Data JPA detached entity passed to persist on #OneToMany and #OneToOne

Problem
I am trying to store an object in my Postgres database. This consists of the Order.class, (List) OrderDevice.class, and a Department.class.
The important thing is that the OrderDevices are always stored new in the DB, but a Department may already exist.
When I try to save the object to my database using save I get the following error message: (shown below)
I get the error message "detached entity passed to persist: com.niclas.model.OrderDevice" if the department does not exist yet, if the department exists the error message looks like this: "detached entity passed to persist: com.niclas.model.Department".
Solution attempts
This solution cannot be used because I do not use bidirectional mapping.
(I don't want to use a bidirectional mapping because I want to access the departments without an order.)
I also tried to change the Cascade types to MERGE like in this solution
I also tried using #Transactional on the method
I also tried to save the children in the database first and then the parent like this:
departmentRepository.save(order.getDepartment()); orderDeviceRepository.saveAll(order.getDevices()); orderRepository.save(order);
I hope I have described my good enough and I am happy about suggestions for solutions
Error.log
The log can be viewed here. (The formatting did not work here)
Order.class
#Entity
#Table(name = "orders")
public class Order extends AuditModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "order_id")
private String orderId;
#Column(name = "department_id")
private long departmentId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "department", referencedColumnName = "id")
private Department department;
#JsonProperty("deviceList")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "order_id")
private List<OrderDevice> devices;
#JsonProperty("forename")
#Column(name = "sender_forename")
private String senderForename;
#JsonProperty("surname")
#Column(name = "sender_surname")
private String senderSurname;
#Column(name = "notes", columnDefinition = "TEXT")
private String notes;
#Column(name = "month")
private int month;
#Column(name = "year")
private int year;
public Order() {
}
... Getter/Setters
}
OrderDevice.class
#Entity
#Table(name = "order_devices")
public class OrderDevice extends AuditModel{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column( name = "order_id", insertable = false, updatable = false )
private String orderId;
#Column(name = "device_id")
private long deviceId;
#Column(name = "device_name")
private String deviceName;
#Column(name = "priceName")
private String priceName;
#Column(name = "price")
private double price;
#Column(name = "count")
private int count;
public OrderDevice() {
}
... Getters/Setters
}
Department.class
#Entity
#Table(name = "departments")
public class Department {
//TODO add Form Validation
//TODO better Naming for From Attributes on Frontend and Backend
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "department_name")
private String department;
#Column(name = "contact_person_forename")
private String forename;
#Column(name = "contact_person_surname")
private String surname;
#Column(name = "contact_person_mail")
private String mail;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private String houseNumber;
#Column(name = "location")
private String location;
#Column(name = "postal_code")
private int postalCode;
#Column(name = "country")
private String country;
#Column(name = "auto_send_invoice")
private boolean autoSend;
#Column(name = "registered")
private boolean registered;
public Department() {
}
... Getter/Setters
}
OrderController.class
#Slf4j
#RestController
public class OrderController {
private final DepartmentRepository departmentRepository;
private final OrderRepository orderRepository;
private final OrderDeviceRepository orderDeviceRepository;
public OrderController(OrderRepository orderRepository, DepartmentRepository departmentRepository,
OrderDeviceRepository orderDeviceRepository) {
this.orderRepository = orderRepository;
this.departmentRepository = departmentRepository;
this.orderDeviceRepository = orderDeviceRepository;
}
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody Order order) throws JsonProcessingException {
order.setOrderId(order.generateOrderId());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
order.getDevices().forEach(orderDevice -> {
orderDevice.setOrderId(order.getOrderId());
});
//departmentRepository.save(order.getDepartment());
//orderDeviceRepository.saveAll(order.getDevices());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
Update
If the objects are created in this way, no error will occur and the order will be successfully saved in the database.
However, I don't understand why it works this way and not via ObjectMapper. Does anyone know why?
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody JsonNode jsonNode) throws JsonProcessingException {
Order order = new Order();
JsonNode departmentJson = jsonNode.get("department");
Department department;
if ( departmentJson.get("id").canConvertToInt() ) {
department = departmentRepository.findDepartmentById(departmentJson.get("id").asInt());
} else {
department = new Department();
department.setDepartment(departmentJson.get("department").asText());
department.setForename(departmentJson.get("forename").asText());
department.setSurname(departmentJson.get("surname").asText());
department.setMail(departmentJson.get("mail").asText());
department.setStreet(departmentJson.get("street").asText());
department.setHouseNumber(departmentJson.get("houseNumber").asText());
department.setLocation(departmentJson.get("location").asText());
department.setPostalCode(departmentJson.get("postalCode").asInt());
department.setCountry(departmentJson.get("country").asText());
department.setAutoSend(departmentJson.get("autoSend").asBoolean());
department.setRegistered(departmentJson.get("registered").asBoolean());
}
order.setDepartment(department);
order.setOrderId(order.generateOrderId());
order.setDepartmentId(department.getId());
List<OrderDevice> orderDevices = new ArrayList<>();
JsonNode devices = jsonNode.get("deviceList");
for (JsonNode node : devices) {
//TODO replace this mess with objectMapper
if (node.has("count") && node.get("count").asInt() != 0){
OrderDevice device = new OrderDevice();
device.setOrderId(order.getOrderId());
device.setDeviceId(node.get("id").asLong());
device.setDeviceName(node.get("deviceName").asText());
device.setPriceName(node.get("priceName").asText());
device.setPrice(node.get("price").asDouble());
device.setCount(node.get("count").asInt());
orderDevices.add(device);
}
}
order.setDevices(orderDevices);
order.setSenderForename(jsonNode.get("forename").asText());
order.setSenderSurname(jsonNode.get("surname").asText());
order.setNotes(jsonNode.get("notes").asText());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
You can try to use instead of orderRepository.save(order) use orderRespostiory.saveOrUpdate(order).

Specification Predicate to Search Nested jsonb column Objects

I have stored jsonb object in postgresql like
{"sample": {"lastName": "Sahani", "firstName": "Sanjay"}, "address": "Address2", "bedrooms": 2, "postcode": "40 BS", "propertyType": "Type 2"}
My table name is valuation_report_json
and jsonb column name is params
I can get address using Specification but unable to get sample's firstName
My specification is
public class ValuationReportJSONSpecification implements Specification<ValuationReportJSON>{
private String locale;
private String fieldToSearch;
private String localeParameter;
public ValuationReportJSONSpecification(String locale, String fieldToSearch,String localeParameter) {
this.locale = locale;
this.fieldToSearch = fieldToSearch;
this.localeParameter=localeParameter;
}
#Override
public Predicate toPredicate(Root<ValuationReportJSON> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// TODO Auto-generated method stub
System.err.println("INSIDE");
// return cb.equal(cb.function("jsonb_extract_path_text", String.class, root.<String>get("params"), cb.literal(this.locale)), this.fieldToSearch);
return cb.equal(cb.function("jsonb_extract_path_text", String.class,root.<String>get("params"), cb.literal(this.locale),cb.literal(this.localeParameter)),this.fieldToSearch);
}
}
My models are
ValuationReportJSON :
#Data
#NoArgsConstructor
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#Table(name = "valuation_report_json")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class ValuationReportJSON implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Type(type = "jsonb")
#Column(name = "parameters", nullable = false,columnDefinition = "jsonb")
private Params params;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "property_id", nullable = true)
private Property property;
#Column(name = "entry_id", nullable = true)
private Integer entryId;
#Column(name = "report_data", nullable = true, columnDefinition = "text")
private String reportData;
#Column(name = "entry_date", nullable = true)
#CreationTimestamp
private Timestamp entryDate;
#Column(name = "modified_date", nullable = true)
#UpdateTimestamp
private Timestamp modifiedDate;
//getter setter
}
Params
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Params implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
public String address;
private String postcode;
private SampleObj sample;
private int bedrooms;
//getter setter
}
SampleObj:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class SampleObj implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
//getter setter
}
I cannot use join in predicate as my objects are not having mapping or not an entity.
because i have seen same question but objects were entity :Specification/Predicate to Search Nested Objects

How to update existing value in table without inserting new using Java SpringBoot

I have two tables one is parent and other one is child. When I am trying to save initially, I am able to insert values in both the tables if values not present in Parent table. But at the time of update/insert the values in child table, it is inserting duplicate values.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class RuleApi {
Long id;
private String market;
private int modelYear;
private String vehicleLine;
private String vehicleLineName;
private String locale;
private String binding;
private String description;
private String createUser;
private String updateUser;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class DescriptorSaveRequest {
#Valid
#NotNull
RuleApi rule;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "MNAVS03_DESCRIPTOR_CONTEXT")
#EntityListeners(AuditingEntityListener.class)
public class DescriptorContext implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(value = AccessLevel.NONE)
#Column(name = "NAVS03_DESCRIPTOR_CONTEXT_K")
private Long id;
#Column(name = "NAVS03_MARKET_N")
private String market;
#Column(name = "NAVS03_MODEL_YEAR_R")
private Integer modelYear;
#Column(name = "NAVS03_VEHICLE_LINE_C")
private String vehicleLine;
#Column(name = "NAVS03_VEHICLE_LINE_N")
private String vehicleLineName;
#Column(name = "NAVS03_LOCALE_N")
private String locale;
#Column(name = "NAVS03_CREATE_USER_C", nullable = false)
private String createUserId;
#CreationTimestamp
#Column(name = "NAVS03_CREATE_S")
private Timestamp createTimestamp;
#Column(name = "NAVS03_LAST_UPDT_USER_C", nullable = false)
private String updateUserId;
#UpdateTimestamp
#Column(name = "NAVS03_LAST_UPDT_S")
private Timestamp updateTimestamp;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "MNAVS04_DESCRIPTOR_RULE")
#EntityListeners(AuditingEntityListener.class)
public class DescriptorRule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(value = AccessLevel.NONE)
#Column(name = "NAVS04_DESCRIPTOR_RULE_K")
private Long id;
#JoinColumn(name = "NAVS03_DESCRIPTOR_CONTEXT_K", nullable = false)
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private DescriptorContext descriptorContextId;
#Column(name = "NAVS04_BINDING_N",
unique = true)
private String binding;
#Column(name = "NAVS04_DESCRIPTOR_RULE_X")
private String description;
#Column(name = "NAVS04_CREATE_USER_C", nullable = false)
private String createUserId;
#CreationTimestamp
#Column(name = "NAVS04_CREATE_S")
private Timestamp createTimestamp;
#Column(name = "NAVS04_LAST_UPDT_USER_C", nullable = false)
private String updateUserId;
#UpdateTimestamp
#Column(name = "NAVS04_LAST_UPDT_S")
private Timestamp updateTimestamp;
}
#ApiOperation(value = "Create/Update Feature Descriptions", notes = "Create/Update a descriptions based on the given input")
#PostMapping("/descriptor/saveFeatures")
public ResponseEntity<BaseBodyResponse<String>> saveFeatureDescriptions(#Valid #RequestBody DescriptorSaveRequest descriptorSaveRequest) throws Exception {
this.descriptorContextService.saveFeatureDescriptions(
this.descriptorContextMapper.mapDescriptorContext(descriptorSaveRequest),
this.descriptorContextMapper.mapDescriptorRule(descriptorSaveRequest)
);
return ResponseEntity.ok(BaseBodyResponse.result("Saved Successfully"));
}
#Service
public class DescriptorContextService {
//SaveFeatureDescriptions
public void saveFeatureDescriptions(DescriptorContext descriptorContext, DescriptorRule descriptorRule) throws Exception {
DescriptorContext descriptorContext1 =
this.descriptorContextRepository.findByMarketAndModelYearAndVehicleLineAndVehicleLineNameAndLocale(
descriptorContext.getMarket(),
descriptorContext.getModelYear(),
descriptorContext.getVehicleLine(),
descriptorContext.getVehicleLineName(),
descriptorContext.getLocale());
if (descriptorContext1 == null) {
// add a new context
descriptorContext1 = descriptorContextRepository.save(DescriptorContext.builder()
.market(descriptorContext.getMarket())
.modelYear(descriptorContext.getModelYear())
.vehicleLine(descriptorContext.getVehicleLine())
.vehicleLineName(descriptorContext.getVehicleLineName())
.locale(descriptorContext.getLocale())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
}
Long contextId = descriptorContext1.getId();
List<DescriptorRule> rule = this.descriptorRuleRepository.findByDescriptorContextId(contextId);
if (rule.size() == 0) {
// add a new rule
this.descriptorRuleRepository.save(DescriptorRule.builder()
.descriptorContextId(descriptorContext1)
.binding(descriptorRule.getBinding())
.description(descriptorRule.getDescription())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
} else {
// update a existing rule
for (DescriptorRule descriptorRule1 : rule) {
if (descriptorRule1.getBinding().equals(descriptorRule.getBinding())) {
descriptorRule1.setDescription(descriptorRule.getDescription());
descriptorRule1.setupdateUserId(descriptorRule.getupdateUserId());
this.descriptorRuleRepository.save(descriptorRule1);
} else {
this.descriptorRuleRepository.save(DescriptorRule.builder()
.descriptorContextId(descriptorContext1)
.binding(descriptorRule.getBinding())
.description(descriptorRule.getDescription())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
}
}
}
}
}
}
#Component
public class DescriptorContextMapper {
public DescriptorContext mapDescriptorContext(DescriptorSaveRequest descriptorSaveRequest) {
return DescriptorContext.builder()
.market(descriptorSaveRequest.getRule().getMarket())
.vehicleLine(descriptorSaveRequest.getRule().getVehicleLine())
.vehicleLineName(descriptorSaveRequest.getRule().getVehicleLineName())
.modelYear(descriptorSaveRequest.getRule().getModelYear())
.locale(descriptorSaveRequest.getRule().getLocale())
.createUserId(descriptorSaveRequest.getRule().getCreateUser())
.updateUserId(descriptorSaveRequest.getRule().getUpdateUser())
.build();
}
public DescriptorRule mapDescriptorRule(DescriptorSaveRequest descriptorSaveRequest) {
return DescriptorRule.builder()
.id(descriptorSaveRequest.getRule().getId())
.binding(descriptorSaveRequest.getRule().getBinding())
.description(descriptorSaveRequest.getRule().getDescription())
.createUserId(descriptorSaveRequest.getRule().getCreateUser())
.updateUserId(descriptorSaveRequest.getRule().getUpdateUser())
.build();
}
}
{
"rule": {
"binding": "5003",
"description": "Test new 5003-2023 Escape",
"locale": "fr_CA",
"market": "WANAC",
"modelYear": 2023,
"vehicleLine": "TMC",
"vehicleLineName": "Escape",
"createUser": "rdongre",
"updateUser": "rdongre"
}
}
If I am passing this request and values are not present in both the tables then it should insert the values in both the tables which is working as expected with above code. But at the time of update it is going inside the loop and inserting duplicate values. I am trying to update DESCRIPTION in child table if BINDING is present if not it should insert BINDING plus DESCRIPTION
I fixed this by separating Save and Update methods. Thanks to all.

Java ModelMapper -Failed to convert java.util.List to java.util.List

I am using the Java ModelMapper library to map DTOs to ORM #Entity objects. I have the following test set up:
public class MapperTest {
#Autowired
private ModelMapper mapper;
#Autowired
private TicketRepository ticketRepo;
#Test
public void testTicket() {
Ticket ticket = ticketRepo.findById(4).get();
TicketDTO dto = mapper.map(ticket, TicketDTO.class);
assertThat(dto.getEquipmenDescription()).isEqualTo(ticket.getEquipment().getDescription());
assertThat(dto.getEquipmentNotes()).isEqualTo(ticket.getEquipmentNotes());
assertThat(dto.getId()).isEqualTo(ticket.getId());
assertThat(dto.getNotes()).isEqualTo(ticket.getNotes());
assertThat(dto.getOracleID()).isEqualTo(ticket.getOracleID());
assertThat(dto.getPropertyID()).isEqualTo(ticket.getPropertyID());
assertThat(dto.getNotes().size()).isEqualTo(ticket.getNotes().size());
for (TicketNoteDTO note : dto.getNotes()) {
assertThat(note.getId()).isNotEqualTo(0);
assertThat(note.getIssueDate()).isNotNull();
assertThat(note.getUserUserName()).isNotEmpty();
}
}
}
This fails with the following error:
org.modelmapper.MappingException: ModelMapper mapping errors:
1) Converter org.modelmapper.internal.converter.CollectionConverter#501c6dba failed to convert java.util.List to java.util.List.
The following are my Entity and corresponding DTOs. Getters and setters are omitted for brevity.
Ticket
#Entity
#Table(name = "ticket")
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "equipment_notes")
private String equipmentNotes;
#Column(name = "is_open")
private boolean isOpen;
#Column(name = "oracle_id")
private String oracleID;
#ManyToOne
#JoinColumn(name = "equipment_id")
private EquipmentCategory equipment;
#Column(name = "property_id")
private String propertyID;
#OneToMany(mappedBy = "ticket")
private List<TicketNote> notes = new ArrayList<>();
}
TicketNote (getters and Setters omitted for brevity)
#Entity
#Table(name = "ticket_note")
public class TicketNote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "ticket_id")
private Ticket ticket;
#ManyToOne
#JoinColumn(name = "user_id")
private AppUser user;
#Column(name = "issue_date")
private LocalDate issueDate;
#Column(name = "oracle_contact")
private String oracleContact;
#Column(name = "issue_resolved")
private boolean issueResolved;
}
TicketDTO
public class TicketDTO {
private int id;
private String equipmentNotes;
private boolean isOpen;
private String oracleID;
private String equipmenDescription;
private String propertyID;
private List<TicketNoteDTO> notes = new ArrayList<>();
}
TicketNoteDTO
public class TicketNoteDTO {
private int id;
private String userUserName;
private LocalDate issueDate;
private String oracleContact;
private boolean issueResolved;
}
I have some experience with the ModelMapper library, but I am not sure what the issue is. Any advice is appreciated.
Thanks.

How can you make a created_at column generate the creation date-time automatically like an ID automatically gets created?

I currently have an Entity as below:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productId;
private String productImage;
private String productTitle;
private String productDescription;
private Integer productPrice;
private Date createdAt;
private Date updatedAt;
Upon creation of this object, the value of createdAt and updatedAt shows null in the database and was wondering how I can implement code so that createdAt and updateAt automatically gets inserted?
My post method is as below:
#PostMapping("/products")
public ProductResponse createProduct(#Validated #RequestBody ProductForm productForm) {
Product product = productForm.asProduct();
Product createdProduct = productRepository.save(product);
return new ProductResponse(createdProduct, "Product created");
}
JPA
There isn't anything as convenient as annotating the Timestamp field directly but you could use the #PrePersist, #PreUpdate annotations and with little effort achieve the same results.
Hibernate
#CreationTimestamp - Documentation
#UpdateTimestamp - Documentation
Spring Data JPA
#CreatedDate - Documentation
#LastModifiedDate - Documentation
Extend the following abstract class in your entity:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class DateAudit implements Serializable {
#CreatedDate
#Column(name = "created_at", nullable = false, updatable = false)
private Date createdAt;
#LastModifiedDate
#Column(name = "updated_at")
private LocalDateTime updatedAt;
}
Don't forget to enable JPA Auditing feature using #EnableJpaAuditing
Read this: https://docs.spring.io/spring-data/jpa/docs/1.7.0.DATAJPA-580-SNAPSHOT/reference/html/auditing.html
With the mix of #dimitrisli and #buddha answers, something pretty clean is
#Data
#MappedSuperclass
public abstract class BaseEntity {
#Column(updatable = false)
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
}
And now you all your entity can extend that class like so
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
#Id
#GeneratedValue
public UUID id;
public String userName;
public String email;
public String firstName;
public String lastName;
}
Note that you might not need #Data & #EqualsAndHashCode annotations from lombok as it generate getter/setter
You can create a BaseEntity. Each entity extends the BaseEntity. In the Base entity ,it will set the time automatically
#Data
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "Id")
private String id;
#Column(name = "deleted", columnDefinition = "Bit(1) default false")
private boolean deleted = false;
#Column(name = "DataChange_CreatedBy", nullable = false)
private String dataChangeCreatedBy;
#Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
#Column(name = "DataChange_LastModifiedBy")
private String dataChangeLastModifiedBy;
#Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
#PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();
if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();
}
#PreUpdate
protected void preUpdate() {
this.dataChangeLastModifiedTime = new Date();
}
#PreRemove
protected void preRemove() {
this.dataChangeLastModifiedTime = new Date();
}
}

Categories