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;
}
}
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.
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.
Related
I have an TimelineEntity entity, that uses HoTimelineType enum with custom integer value. That custom integer value is stored in the database. Implemented via Using #PostLoad and #PrePersist Annotations
Sprint JPA Repository is used to save and get entities.
Here is the issue:
#Entity
#Table(name = TABLE_NAME)
#IdClass(TimelineKey.class)
public class TimelineEntity {
public interface Persistence {
String TABLE_NAME = "timelines";
}
#Id
#Column(name = "node_id")
private Long nodeId;
#Id
#Column(name = "timeline_id")
private Long timelineId;
#Column(name = "ho_timeline_type")
private Integer hoTimelineTypeValue;
#Transient
private HoTimelineType hoTimelineType;
public Long getNodeId() {
return nodeId;
}
public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
public Long getTimelineId() {
return timelineId;
}
public void setTimelineId(Long timelineId) {
this.timelineId = timelineId;
}
public HoTimelineType getHoTimelineType() {
return hoTimelineType;
}
public void setHoTimelineType(HoTimelineType hoTimelineType) {
this.hoTimelineType = hoTimelineType;
}
public Integer getHoTimelineTypeValue() {
return hoTimelineTypeValue;
}
public void setHoTimelineTypeValue(Integer hoTimelineTypeValue) {
this.hoTimelineTypeValue = hoTimelineTypeValue;
}
#PostLoad
private void postLoad() {
this.hoTimelineType = HoTimelineType.of(hoTimelineTypeValue);
}
#PrePersist
private void prePersist() {
this.hoTimelineTypeValue = hoTimelineType.getValue();
}
}
#Eager
public interface TimelineEntityRepository extends JpaRepository<TimelineEntity, TimelineKey> {
List<TimelineEntity> findByNodeId(Long nodeId);
}
#Autowired
private TimelineEntityRepository timelineEntityRepository;
...
TimelineEntity newTE = new TimelineEntity();
newTE.setNodeId(10L);
newTE.setTimelineId(22L);
newTE.setHoTimelineType(HoTimelineType.TYPE_1);
newTE = timelineEntityRepository.save(newTE);
When the newTE entity is saved, prePersist is invoked, and inside this method, the hoTimelineType is null and I get NPE. nodeId and timelineId are not nulls. If I stay with a debugger on the last line, outside of prePersist, I see that hoTimelineType has the value, I set before.
When I load entities, inserted with test data, everything works fine and both hoTimelineType and hoTimelineTypeValue have not nullable values.
I skipped the code of TimelineKey and HoTimelineType to simplify the example. Can add it, if needed.
What could reset hoTimelineType? What do I miss?
It seems there is no way to control the saving behaviour of spring jpa repository proxy.
Possible solutions for issue:
Via javax.persistence.Converter. It is pretty clear, the structe of an entity is simple. Can confirm it works fine with Spring Jpa Repository generation.
Explicitely set hoTimelineTypeValue before you save an entity. Error-prone solution. Everytime you save an entity you must think about the difference between the hoTimelineTypeValue and hoTimelineType.
You could enrich setters and getters of the entity class, to explicitely control the consistency between the fields. It makes implementation of entity classes not so obvious. You get more compicated solution for nothing. As a result error-prone solution. Do not recommend it as well.
Cause of disadvantages of #2 and #3 I do not provide examples. It makes no sense.
Example of the solution #1 can be found here: Using JPA 2.1 #Converter Annotation
Say I have an abstract entity
#MappedSuperclass
#Data
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, updatable = false)
protected Integer id;
#Column(nullable = false, unique = true)
protected String name;
}
with some kind of AbstractRepository
#NoRepositoryBean
public interface AbstractRepository<C extends AbstractEntity> extends JpaRepository<C, Integer> {}
that's supposed to serve as a single dependency for multiple entities. The entities extend the abstract entity and thus the AbstractRepository:
public interface RealEntityRepository extends AbstractRepository<RealEntity> {}
In my service I want to use jpa inheritance to get rid of a bloat constructor (it's bloat because of too many realEntityRepos) and method implementations per entity. So instead of x methods like this per each entity
void findRealEntity(RealEntity entity) {
entity.setId(realEntityRepository.findOne(entity.getName())
.map(RealEntity ::getId)
.orElseThrow(() -> new SomeException(entity.getName())));
}
I want to have only one method call like in the following pseudocode (I know one cannot instantiate abstract classes) that takes the entity as an abstract entity for an argument, invokes the child's repo and executes the jpa methods in that specific child repo.
void findEntity(AbstractEntity entity) {
entity.setId(abstractEntityRepository.findOne(entity.getName())
.map(AbstractEntity::getId)
.orElseThrow(() -> new SomeException(entity.getName())));
}
This is my service as of now, it does work like that but it's bloat:
#Service
public class SomeService {
private final RealEntity1Repository realEntity1Repository;
private final RealEntity2Repository realEntity2Repository;
private final RealEntity3Repository realEntity3Repository ;
... 5 more times ..
public SomeService(RealEntity1Repository realEntity1Repository, RealEntity2Repository realEntity2Repository,
RealEntity3Repository realEntity3Repository,
... 5 more) {
this.realEntity1Repository = realEntity1Repository;
this.realEntity2Repository = realEntity2Repository;
this.realEntity3Repository = realEntity3Repository;
... 5 more
}
... some methods
void findRealEntity1(RealEntity1 entity) {
entity.setId(realEntity1Repository.findOne(entity.getName())
.map(RealEntity1::getId)
.orElseThrow(() -> new SomeException(entity.getName())));
}
void findRealEntity2(RealEntity2 entity) {
entity.setId(realEntity2Repository.findOne(entity.getName())
.map(RealEntity2::getId)
.orElseThrow(() -> new SomeException(entity.getName())));
}
...
UPDATE
Thx to #Antoniosss' hints i solved the problem!
Add Optional<C> findByName(String name); to SuperRepository
then create a new service that implements the entity repository specific transaction methods. Call this service in the previous service when implementing the mapping between AbstractEntity and entity specific methods:
public void mapAbstractEntity(AbstractEntity abstractEntity) {
if (abstractEntity instanceof RealEntity1 { // call RealEntity1 method from service}
else if (abstractEntity instanceof RealEntity2) { ... }
Based on actual code - you can add findByName generic method to abstract repository, create map that maps entity type to repository and than use single implementation of findRealEntity(AbstractEntity) that would select proper repository and call findByName on it.
I am using Spring boot framework with hibernate. I want to show all data from database only certain conditions. Here is my query
SELECT * FROM `client_master` WHERE CLIENT_GROUP='S'
I want to get data which CLIENT_GROUP data has only S. I have used bellow cod for spring boot..
Model I have used bellow code..
#Entity
#Table(name = "client_master")
public class ClientMasterModel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name= "ID")
private int ID;
#Column(name= "NAME")
private String name;
//getter or setter
}
My repository is bellow
public interface Staff_Add_Repository extends JpaRepository<ClientMasterModel, Long> {
}
In service, I have used bellow code..
#Autowired
Staff_Add_Repository add_Repository;
public List<ClientMasterModel> findAll(){
return add_Repository.findAll();
}
Above method returns all data. I want to get only specific data .
How to do it? Please help me..
Try
List<ClientMasterModel> findByClientGroup(String clientGroup);
Assuming you have a field named clientGroup in your ClientMasterModel you just need a correctly named method and possibly - if you wish - a default wrapper method in your repository as following:
public interface Staff_Add_Repository
extends JpaRepository<ClientMasterModel, Long> {
List<ClientMasterModel> findByClientGroup(String clientGroup);
default List<ClientMasterModel> findWhereClientGroupIsS() {
return findByClientGroup("S");
}
}
Also the findAllBy is a synonym to findBy. See this question
I have one simple class
#Entity
#Table(name="user")
public class User implements Serializable {
#Id
#GeneratedValue
private Integer Id;
#Length(min = 5, message = "Username must be at least 5 characters long.")
#Column(name="username",nullable=false,unique=true)
private String userName;
#ManyToMany(cascade= {CascadeType.PERSIST},fetch=FetchType.EAGER)
#JoinTable(name="user_user_profile")
private Set<UserProfile> userProfile = new HashSet<>();
}
And second class:
#Entity
#Table(name = "user_profile")
public class UserProfile {
#javax.persistence.Id
#GeneratedValue
private int Id;
#Column(name = "type", nullable = false, unique = true)
#Enumerated(EnumType.STRING)
private UserProfileType type = UserProfileType.USER;
}
public enum UserProfileType {
USER("USER"),
ADMIN("ADMIN");
}
I'm using Spring MVC and Spring Secuirty with Hibernate. Is there any way to on start of the app make every possible entry in UserProfile Entity (there is only two)? Do I have to get UserProfile from database (via TypedQuery or EntityManager.find() ) and then add it to the User to not make any exceptions?
The enum items are static in your application, so I wouldn't try to make automatic changes in the database. Adding a new record is trivial, but removing an item that is already referenced may need individual care. These values are essential for your application, so I think they should be included in your SQL scripts.
If you are using DB versioning tools such as Flyway or Liquibase, add/remove records of the user_profile table in the migration scripts. They can be configured to run the migrations before your application (and Hibernate) starts, so the application will always see the correct data.
You can add a application start up event and persist the user profiles. You can delete all the user profiles before the application shut down as well. But I wouldn't recommend this as I assume the UserProfiles wouldn't change frequently. If that is the case, you are better off preloading the user profiles via some sql script as suggested in the other answer. If you really want to do it via app, the safest way would be to delete before the app gets shut down. Following is the sample snippet. I assume you are using spring-data-jpa and provided the snippet.
#Component
public class AppStartedListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private UserProfileRepository repository;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
for(UserProfileType userProfileType: UserProfileType.values()) {
UserProfile up = new UserProfile(userProfileType);
repository.save(up);
}
}
}
#Component
public class AppStoppedListener implements ApplicationListener<ContextClosedEvent> {
#Autowired
private UserProfileRepository repository;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
repository.deleteAll();
}
}
public interface UserProfileRepository extends CrudRepository<UserProfile, Integer> {
}
So I added method to dao layer:
#Transactional
#EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
UserProfile user=new UserProfile();
em.persist(user);
UserProfile admin=new UserProfile();
admin.setType(UserProfileType.ADMIN);
em.persist(admin);
}
And now, before adding new User i just use HQL to get persistent UserProfile object that I can add to my User. Altough it works I will probably try to load it from some sort of *.sql file since I had to add method metioned above to the Dao layer interface (because of interface type proxy) and I don't like it to be honest.
I have one entity having composite key and I am trying to persist it by using spring data jpa repository to mysql databse as given below:
#Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name="CUSTOMERID")
private Long customerId;
#Column(name="CUSTOMERTYPE")
private Integer customerType;
#Column(name="MOBILE")
private Long mobile;
#Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name="CUSTOMERID")
private Long customerId;
#Column(name="CUSTOMERTYPE")
private Integer customerType;
#Column(name="MOBILE")
private Long mobile;
//getter and setters
}
And Entity as
#Entity
#Table(name="mobileverificationdetails")
public class MobileVerificationDetails {
#EmbeddedId
private MobileVerificationKey key;
#Column(name="MOBILETYPE")
private String mobileType;
#Column(name="MOBILEPIN")
private Integer mobilePin;
//getters and setters
}
My spring data jpa repository look like this:
public interface MobileVerificationDetailsRepository extends
CrudRepository<MobileVerificationDetails, MobileVerificationKey> {
#Override
MobileVerificationDetails save(MobileVerificationDetails mobileVerificationDetails);
#Override
MobileVerificationDetails findOne(MobileVerificationKey id);
}
Now if I am trying to add duplicate record with same key for original record and different values for other fields .when i try to insert second record it results in update of existing record with new values instead of throwing exception for violating primary key constraint...can any one please explain me this behavior.
The easiest (and least invasive) way to work around this is probably by making sure the id only gets set right before the persist. This can be achieved in a #PrePersist callback:
abstract class MobileVerificationDetails {
#EmbeddedId
private MobileVerificationKey id;
#PrePersist
void initIdentifier() {
if (id == null) {
this.id = … // Create ID instance here.
}
}
}
Alternatively to that you can enforce persist(…) being used by implementing Persistable and implementing isNew() accordingly. Make sure this method returns true on first insert. We usually see people holding a transient boolean flag that is updated in an #PostPersist/#PostLoad annotated method.
abstract class AbstractEntity<ID extends Serializable> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PostPersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
}
Spring Data Jpa Repository functionality is implemented via the SimpleJpaRepository class containing following save(..) method:
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Thus the Spring Jpa Data Repository save(...) method merges an already existing entity.
Opposed to that the naked EntityManager#persist() throws an exception if invoked with already existing entity.
The problem might be solved by adding custom behavior to Spring Data Repository/ies. The custom behavior might be added using one of the approaches as described in 1.3.1 Adding custom behavior to single repositories with example here or in 1.3.2 Adding custom behavior to all repositories with example here. In both cases the custom behavior would include a new persist() method delegating to EntityManager#persist(). Note that in approach 1.3.2. you already have a EntityManager instance, in the approach 1.3.1 you are able to inject EntityManager instance using the #PersistenceContext.
Opposed to my comment I would recommend adding new method to the repository and not overwriting the existing save(...).