Save creationTimestamp and updatedTime in spring + hibernate - spring

I need to update the postgres DB with createdDate and updatedDate
I tried using approach 1, But it is inserting null values.
When I read about, it seems the #prepersist annotations does not work for session.
So I decided to go with Approach 2 : Hibernate #CreationTimeStamp Annotation, I added hibernate-annotations maven dependency, But #CreationTimeStamp is not resolved and gives compilation error.
Can someone advise me on how I can resolve the issue ?
Approach 1
Entity class annotated with #Entity and #Table
public class Status{
#Id
#Column(name = "run_id")
private int run_id;
#Column(name = "status")
private String status;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date" , updatable=false)
private Date created;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_date" , insertable=false)
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
//Getters and setters here
}
implementation class is
sessionFactory.getCurrentSession().save(status);
Approach 2
using #CreationTimeStamp and #updatedTimeStamp. But the maven dependency
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-annotations -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.0-Final</version>
</dependency>
does not add these annotations to classpath

Is there a reason you are using the session.save() method instead of an entitymanager? I'll post an example of my application using an entitymanager to persist and merge entities. Also I am using java.time.LocalDateTime instead of java.util.Date, that's why I don't need #Temporal.
This may also help: How to use #PrePersist and #PreUpdate on Embeddable with JPA and Hibernate
If you want to use an entitymanager this will help: Guide to the Hibernate EntityManager
Entity class:
public abstract class AbstractEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(updatable = false, nullable = false)
private Long id;
#Column
private LocalDateTime createdTimestamp;
#Column
private LocalDateTime modifiedTimestamp;
#Version
private Long version;
#PrePersist
public void setCreationDateTime() {
this.createdTimestamp = LocalDateTime.now();
}
#PreUpdate
public void setChangeDateTime() {
this.modifiedTimestamp = LocalDateTime.now();
}
//Getter and setter
}
Abstract database service class:
public abstract class AbstractDatabaseService {
#PersistenceContext(name = "examplePU")
protected EntityManager entityManager;
}
Example Entity Repository Interface:
public interface ExampleRepository {
ExampleEntity save(ExampleEntity exampleEntity);
}
Example Entity Repository Implementation:
public class ExampleRepositoryImpl extends AbstractDatabaseService implements ExampleRepository , Serializable {
#Transactional
#Override
public ExampleEntity save(ExampleEntity exampleEntity) {
ExampleEntity toPersist;
// Updating an already existing entity
if (exampleEntity.getId() != null) {
toPersist = entityManager.find(ExampleEntity .class, exampleEntity.getId());
// Omitted merging toPersist with the given exampleEntity through a mapper class here
} else {
toPersist = exampleEntity;
}
try {
toPersist = entityManager.merge(toPersist);
} catch (Exception e) {
// Logging e
}
return toPersist;
}
}
Hope this helps.

Related

How Can I mapping DTOs using mapstruct?

I am tring to mapping entity datas to DTOs using mapstruct.
And with these sources, I could map id,title datas.
But the problem is.... I can not map userName using these sources.
How can I resolve this problem??
#Entity // DB와의 연결을 위하여
#Data // getter setter
public class Board {
#Id // id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min=2, max=30)
private String title;
#Length(min=20)
#Lob
#Column(columnDefinition="TEXT", nullable = false)
private String content;
#ManyToOne
#JoinColumn(name="userId", referencedColumnName = "id")
private User user;
}
#Builder
#AllArgsConstructor
#Data
public class BoardListDto {
private Long id;
private String title;
private String userName;
}
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name.value")
List<BoardListDto> toDtos(List<Board> board);
}
public interface EntityMapper <D, E> {
E toEntity(D dto);
D toDto(E entity);
// Entity업데이트 시 null이 아닌 값만 업데이트 하도록 함.
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(D dto, #MappingTarget E entity);
List<D> toDtos(List<E> entity);
}
no need to implement toDtos method for this. This code should be enough and Mapstruct will handle the rest alone.
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name")
BoardListDto toDto(Board board);
}

Get records for last 3 days via Spring JPA Repository

I have an entity which contains field date.
#Entity
#Table(name="messages", schema = "users")
...
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "author")
private String author;
#Column(name = "tags")
private String tags;
#Column(name = "message_date")
private LocalDate date;
#Override
public String toString() {
...
}
}
#Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Message findByMessageId(Long id);
}
I'm using Spring Data JPA with repository. I want to get all messages from database for last 3 days (field date). How can I do it with Spring JPA?
#Query(...?)
List<Message> findBy...?
I suggest to split the logic from the actual queries. A service could handle all the intermediate things, e.g.:
#Service
public class MessageService {
private final MessageRepository repository;
#Autowired
public MessageService(MessageRepository repository) {
this.repository = repository;
}
List<Message> getLastThreeDays() {
// subtract 3 days from today
LocalDate threeDaysAgoDate = LocalDate.now().minusDays(3);
return this.repository.findAllWithDateAfter(threeDaysAgoDate);
}
}
and your repository stays nice and clean:
#Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Optional<Message> findByMessageId(Long id);
#Query("select m from Message m where date >= :threeDaysAgoDate")
List<Message> findAllWithDateAfter(#Param("threeDaysAgoDate") LocalDate threeDaysAgoDate);
}

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

How to override #CreatedBY value from AbstractAuditingEntity in Spring JPA

We have a microweb service environment in which AbstractAuditingEntity coming from another common micro service. And I want to override #CreatedBy property of this abstract class with my own defined value.
My code is given below.
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity {
#Column(name = "created_by", insertable = true, updatable = false, nullable = false)
#CreatedBy
private String createdBy;
#Column(name = "created_date", insertable = true, updatable = false, nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#CreatedDate
private DateTime createdDate;
#Column(name = "last_modified_by", nullable = false)
#LastModifiedBy
private String lastModifiedBy;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#LastModifiedDate
private DateTime lastModifiedDate;
// #Transient
public abstract Long getInternalId();
// #Transient
public abstract void setInternalId(Long internalId);
public DateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
public DateTime getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(DateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
}
And my Domain class is like
#Entity
#Table(name = "programs")
#AttributeOverride(name = "createdBy", column = #Column(name = "created_by"))
public class Program extends AbstractAuditingEntity {
#Id
#SequenceGenerator(name = "programs_seq", sequenceName = "programs_seq")
#GeneratedValue(generator = "programs_seq")
#Column(name = "internal_id", nullable = false)
private Long internalId;
private String programName;
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
//Start getters & setters - auto generated by IDE
#Override
public Long getInternalId() {
return internalId;
}
#Override
public void setInternalId(Long internalId) {
this.internalId = internalId;
}
public String getProgramName() {
return programName;
}
public void setProgramName(String programName) {
this.programName = programName;
}
}
When we try to persist this domain object in the database, user defined value provided by me is not getting persist, instead Spring framework still adding its own value. I have tried #AttributeOverride but it doesn't work in my case.
Thanks in advance for help.
I got a solution as below.
#PrePersist
private void setCreateByNew() {
setCreatedBy("anonymousUser");
}
Using #PrePersist I am able to override value which I am getting from common framework.
An alternate solution is to set the securityContext
SecurityContextHolder.getContext().setAuthentication(___{AuthToken}___)
with the username/principal you are using to auto populate audit fields before entity save and reset it after that. (for message or event processing in asynchronous fashion). This populates both #Createdby and #LastUpdatedBy from the JPA AuditAware listener.
#PrePersist will only work if we are trying to hardcode a pre-defined String setCreatedBy("anonymousUser"); as indicated above. It does not allow us to pass in the username/name of the user as parameter to the method.
for : user defined value provided by me is not getting persist, instead Spring framework still adding its own value
you can do it with AuditorAware interface. 3.1.3 AuditorAware
In case you use either #CreatedBy or #LastModifiedBy, the auditing
infrastructure somehow needs to become aware of the current principal.
To do so, we provide an AuditorAware SPI interface that you have to
implement to tell the infrastructure who the current user or system
interacting with the application is. The generic type T defines of
what type the properties annotated with #CreatedBy or #LastModifiedBy
have to be.
Here’s an example implementation of the interface using Spring
Security’s Authentication object:
Example 3.2. Implementation of AuditorAware based on Spring Security
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}

Auditing and #Embedded in Spring Data JPA

I am having a problem with JPA auditing and for #Embedded members. Consider the following example scenario:
I set up a test table in an Oracle DB:
CREATE TABLE AUDIT_TEST (
ID NUMBER(38) NOT NULL PRIMARY KEY,
CREATION_DATE TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL
);
I define a JPA #Entity with auditing:
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "AUDIT_TEST")
public class AuditTest {
private Long id;
private LocalDateTime creationDate;
#Id
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#CreatedDate
#Column(name = "CREATION_DATE")
public LocalDateTime getCreationDate() { return creationDate; }
public void setCreationDate(LocalDateTime creationDate) {
this.creationDate = creationDate;
}
}
Finally, I enable JPA auditing in my #Configuration:
#SpringBootApplication()
#EnableJpaAuditing()
public class AuditTestApplication {
}
So far so good; when I construct an AuditTest instance, assign it an id and commit, the creationDate column gets populated with the current timestamp as expected.
However, things stop working when I encapsulate the audit column in an #Embeddable:
#Embeddable
public class AuditTestEmbeddable {
private LocalDateTime creationDate;
#CreatedDate
#Column(name = "CREATION_DATE")
public LocalDateTime getCreationDate() { return creationDate; }
public void setCreationDate(LocalDateTime creationDate) {
this.creationDate = creationDate;
}
}
Then I change my entity class to embed the creation date:
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "AUDIT_TEST")
public class AuditTest {
private Long id;
private AuditTestEmbeddable auditTestEmbeddable = new AuditTestEmbeddable();
#Id
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Embedded
public AuditTestEmbeddable getAuditTestEmbeddable() {
return auditTestEmbeddable;
}
public void setAuditTestEmbeddable(AuditTestEmbeddable auditTestEmbeddable) {
this.auditTestEmbeddable = auditTestEmbeddable;
}
}
And unfortunately, the auditing is no longer working.
Is anyone here aware of a way to save the auditing functionality while still using #Embedded classes?
Update:
This functionality has been added to Spring Data 2.1 M2 (Lovelace).
https://jira.spring.io/browse/DATACMNS-1274
Spring Data audit annotations in nested (embeddable) classes isn't supported yet. Here's the jira ticket requesting this feature.
However, we could use custom audit listener to set audit information in embeddable classes.
Here's the sample implementation taken from a blog: How to audit entity modifications using the JPA #EntityListeners, #Embedded, and #Embeddable annotations.
Embeddable Audit
#Embeddable
public class Audit {
#Column(name = "created_on")
private LocalDateTime createdOn;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_on")
private LocalDateTime updatedOn;
#Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
Audit Listener
public class AuditListener {
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if(audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
audit.setCreatedBy(LoggedUser.get());
}
#PreUpdate
public void setUpdadtedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
audit.setUpdatedBy(LoggedUser.get());
}
}
Auditable
public interface Auditable {
Audit getAudit();
void setAudit(Audit audit);
}
Sample Entity
#Entity
#EntityListeners(AuditListener.class)
public class Post implements Auditable {
#Id
private Long id;
#Embedded
private Audit audit;
private String title;
}
With spring-data 2.4.4 the AuditListener works fine with embedded objects, see
documentation spring-data
The minimal version of spring-data is bundled in spring-boot version 2.4.3

Resources