1

I have several JPA entities, each Entity has a database user column, in that column I have to store the user that makes changes to a specific row in the table.

I created a 'MappedSuperclass' Bean that all the entities would extend from, this is the MappedSuperclass.

@MappedSuperclass
public abstract class AuditableBean {

    @Column(name = "modifier_user", nullable = false)
    private String modifier_user;

    // Getters and Setters
}

This is one Entity that extends from the 'MappedSuperclass'.

@Entity
@Table(name = "nm_area_ref")
public class ServingAreaReferenceBean extends AuditableBean {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "nm_area_ref_id")
    private UUID id;

    @Column(name = "nm_srv_area_desc", nullable = false)
    private String description;

    @Column(name = "nm_retired", nullable = false)
    private boolean retired;

    // Getters and Setters
}

And, all the Beans has a corresponding service method used to save the data on the database, this is one of the services class (each service injects a repository for CRUD operations).

// Service
@Component
public class ServingAreaReferenceBO {

    @Inject private ServingAreaReferenceRepository repository; //repository injection
    @Inject private CloudContextProvider cloudContextProvider;

    public List<ServingAreaReferenceBean> getAllServingAreaReferences() {
        return Lists.newArrayList(repository.findAll());
    }

    public Optional<ServingAreaReferenceBean> findById(UUID id) {
        return repository.findById(id);
    }

    public ServingAreaReferenceBean create(ServingAreaReferenceBean servingAreaReference) {
        Optional<CloudContext> cloudContext = Optional.ofNullable(cloudContextProvider.get());// line 1
        servingAreaReference.setUpdaterUser(cloudContext.map(CloudContext::getUserId).orElse(null));// line 2
        return repository.save(servingAreaReference);// line 3
    }

}

// Repository - It extends from CrudRepository (insert, update, delete operations)
@Repository
public interface ServingAreaReferenceRepository extends CrudRepository<ServingAreaReferenceBean, UUID> {

    boolean existsByDescription(String description);

    boolean existsByDescriptionAndIdIsNot(String description, UUID id);
}

When 'repository.save()' (line 3) executes, It stores the user successfully, but I put the user logic just before executing the save method (lines 1, 2). So I don't think that repeating those two lines on each service would be the best approach, instead, I'd like to implement a generic method or a generic class that sets the user for all the Bean Entities before executing the save method.

Is that possible? what is the better approach for that?

I was thinking to implement something like this, but not sure how to make it generic?

@Component
public class AuditableBeanHandler {

    @Inject private CloudContextProvider cloudContextProvider;

    public AuditableBean populateAuditInformation(AuditableBean auditableBean) {
        Optional<CloudContext> cloudContext = Optional.ofNullable(CloudContextProvider.get());
        auditableBean.setUpdaterUser(CloudContext.map(cloudContext::getUserId).orElse(null));
        return auditableBean;
    }

}
chrtnmrc
  • 31
  • 3

2 Answers2

0

Well what I understood, you have to set user before each save call of an entities :

This can be solved by using a well known design pattern called "Template method design pattern".

Just create a parent class for service class :

public abstract class AbstractService<T extends AuditableBean> {

        public AuditableBean populateAuditInformation(AuditableBean auditableBean) {
            Optional<CloudContext> cloudContext = Optional.ofNullable(CloudContextProvider.get());
auditableBean.setLastUpdatedByUser(CloudContext.map(cloudContext::getUserId).orElse(null));
                    return auditableBean;
                }

public absract T save(T areaReference);

public final T create(T t) {
             t = populateAuditInformation(t);
             return save(t);
            }

And in your service class extends this abstract service and add save method:

public class AreaReferenceService extends AbstractService<AreaReferenceBean> {

public AreaReferenceBean save(AreaReferenceBean AreaReference) {
        return repository.save(AreaReference);
    }
}

While calling service method, call create() method. hope this will solve your problem, and also you can read more about Template method design pattern.

Gaurav
  • 3,615
  • 2
  • 27
  • 50
  • I was trying to follow your suggestion, but when I tried to implement the 'save' abstract method inside the ServingAreaReferenceBO, It implements with the AuditableBean parameter. But I want to leave this service method and all service methods intact (my intention is not to be invasive) and don't change the name and just extend the AbstractService that you suggested. and delegate the functionality there. But when I change the signature of the save abstract method the class does not recognize its implementation. – chrtnmrc Jan 20 '22 at 02:46
  • @ChristianMarceloReinozoCues can you share exact error, and what you tried, Will definitely give you solution specific to your case. – Gaurav Jan 20 '22 at 05:18
  • I shared this document with the scenario I'm trying to deal and with the errors that I experimented with in your solution. https://docs.google.com/document/d/17mDdi0eM8tra3FBxiNQ_JLZbiGtSCs8gFtC0KiHLvKQ/edit?usp=sharing – chrtnmrc Jan 20 '22 at 19:05
  • dont make changes on repository level, handle this in service layer – Gaurav Jan 21 '22 at 05:26
0

I think you're pretty close with the AuditableBean.

Add the following configuration to enable auditing:

// Annotate a configuration class with this
@EnableJpaAuditing(auditorAwareRef = "auditAware")

Add this "auditAware" bean, you'll want to tweak it to match whatever auth mechanism you're using. It's sole purpose is to return the username of the authenticated user, Spring will use this.

@Bean
public AuditorAware<String> auditAware() {

return () -> {
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
   return Optional.of(authentication.getPrincipal());
};

}

Add one more annotation to your modified_user field (@LastModifiedBy). This tells Spring that when an update occurs on the entity, set this field to the value returned from your AuditAware bean.

@Column(name = "modifier_user", nullable = false)
@LastModifiedBy
private String modifier_user;

See the Spring Data JPA Documentation for more information on the available audit fields.

lane.maxwell
  • 5,002
  • 1
  • 20
  • 30