Manage invoke from listeners / create history object before transaction end - hibernate-envers

I have a function which update a customer.
Furthermore, I have a function which creates an Activity (createActivity)
Now I want to add to the Activity Entity also the reference to my Custom Audit Table (AuditRevision). Therefore, I try to get the last object from the History Entity (= Function: findLastRevisionFromEntity).
The problem is, that I got the object before from the Custom Audit Table because it seems that the creation from the CustomAudit entry is invoked at the whole end from updateCustomer. So, I will e.g. save AuditRevision with ID = 5, but for the current operation it's ID=6.
Any idea how I can fix this?
I'm also fine if I call the listener from Envers manually instead of automatically, but how can I do this? How can I invoke the function (public void newRevision(Object revisionEntity)) from the RevisionListener?
public void updateCustomer(Customer customer){
entityManager.merge(customer);
// Create Activity
activitiesProcessServiceBean.createActivity(customer,
customer.getEmployeeCreatorOrUpdate(),
Activities.ActionType.MODIFY.toString());
}
public Activities createActivity(EntityPropertyFinder entityPropertyFinder, Employee employeeCreator,
String activityType){
LOGGER.info("START createActivity");
Activities newActivities = new Activities();
// AuditRevision
AuditRevision auditRevision = jpaAuditUtilBean.findLastRevisionFromEntity(entityPropertyFinder.getClass(),
entityPropertyFinder.getId());
newActivities.setAuditRevision(auditRevision);
activitiesService.addActivities(newActivities);
return newActivities;
}
public AuditRevision findLastRevisionFromEntity(Class<? extends EntityPropertyFinder> class1, Long entityId) {
AuditReader auditReader = AuditReaderFactory.get(entityManager);
Object[] lastRevision = (Object[]) auditReader.createQuery()
.forRevisionsOfEntity(class1, false, false)
.add(AuditEntity.property("id").eq(entityId))
.addOrder(AuditEntity.revisionNumber().desc())
.setMaxResults(1)
.getSingleResult();
if(lastRevision == null)
return null;
AuditRevision auditRevision = (AuditRevision) lastRevision[1];
return auditRevision;
}

Related

How to get the id value from an EclipseLink ClassDescriptor?

We currently have the following, working soft delete customizer in place:
public class SoftDeleteCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getQueryManager().setDeleteSQLString(
String.format("UPDATE %s SET record_status = 'D', record_status_time = CURRENT_TIMESTAMP WHERE id = #ID",
descriptor.getTableName()
)
);
}
}
We now want to add the user that deleted the record. I could sanitize the username, but I would prefer to use a parameter / argument.
I rewrote the customizer and did not set an argument for the #ID, as it was already injected correctly somewhere. I then found out that it was not injected when you are using a DeleteObjectQuery (with arguments?). So I have to add an argument for the #ID it seems, but I don't know how to get the id / primary key value of the record / entity to be deleted from a ClassDescriptor.
This is what I have so far:
#Override
public void customize(final ClassDescriptor descriptor) {
final DeleteObjectQuery query = new DeleteObjectQuery();
query.addArgument("DELETED_BY", String.class);
query.addArgument("ID", Long.class);
query.addArgumentValue(SecurityUtils.getUsername());
query.addArgumentValue(...); // How to get the ID of the record to delete?
query.setSQLString(String.format(DELETE_SQL, descriptor.getTableName()));
descriptor.getQueryManager().setDeleteQuery(query);
}
Okay, as a workaround I used our audit listener which we added as one of the EntityListeners. It implements SessionCustomizer. There I was able to do:
#Override
public void postDelete(final DescriptorEvent event) {
final Long id = ((AbstractEntity) event.getObject()).getId();
// Create and execute update query to set the username
}

JPA : Update operation without JPA query or entitymanager

I am learning JPA, I found out that we have some functions which is already present in Jparepository like save,saveAll,find, findAll etc. but there is nothing like update,
I come across one scenario where I need to update the table, if the value is already present otherwise I need to insert the record in table.
I created
#Repository
public interface ProductInfoRepository
extends JpaRepository<ProductInfoTable, String>
{
Optional<ProductInfoTable> findByProductName(String productname);
}
public class ProductServiceImpl
implements ProductService
{
#Autowired
private ProductInfoRepository productRepository;
#Override
public ResponseMessage saveProductDetail(ProductInfo productInfo)
{
Optional<ProductInfoTable> productInfoinTable =
productRepository.findByProductName(productInfo.getProductName());
ProductInfoTable productInfoDetail;
Integer quantity = productInfo.getQuantity();
if (productInfoinTable.isPresent())
{
quantity += productInfoinTable.get().getQuantity();
}
productInfoDetail =
new ProductInfoTable(productInfo.getProductName(), quantity + productInfo.getQuantity(),
productInfo.getImage());
productRepository.save(productInfoDetail);
return new ResponseMessage("product saved successfully");
}
}
as you can see, I can save the record if the record is new, but when I am trying to save the record which is already present in table it is giving me error related to primarykeyviolation which is obvious. I checked somewhat, we can do the update by creating the entitymanager object or jpa query but what if I dont want to use both of them. is there any other way we can do so ?
update I also added the instance of EntityManager and trying to merge the code
#Override
public ResponseMessage saveProductDetail(ProductInfo productInfo)
{
Optional<ProductInfoTable> productInfoinTable =
productRepository.findByProductName(productInfo.getProductName());
ProductInfoTable productInfoDetail;
Integer price = productInfo.getPrice();
if (productInfoinTable.isPresent())
{
price = productInfoinTable.get().getPrice();
}
productInfoDetail =
new ProductInfoTable(productInfo.getProductName(), price, productInfo.getImage());
em.merge(productInfoDetail);
return new ResponseMessage("product saved successfully");
but no error, no execution of update statements in log, any possible reasons for that ?
}
I suspect you need code like this to solve the problem
public ResponseMessage saveProductDetail(ProductInfo productInfo)
{
Optional<ProductInfoTable> productInfoinTable =
productRepository.findByProductName(productInfo.getProductName());
final ProductInfoTable productInfoDetail;
if (productInfoinTable.isPresent()) {
// to edit
productInfoDetail = productInfoinTable.get();
Integer quantity = productInfoDetail.getQuantity() + productInfo.getQuantity();
productInfoDetail.setQuantity(quantity);
} else {
// to create new
productInfoDetail = new ProductInfoTable(productInfo.getProductName(),
productInfo.getQuantity(), productInfo.getImage());
}
productRepository.save(productInfoDetail);
return new ResponseMessage("product saved successfully");
}

Share User data between different action in the same controller in MVC

I want to store globally the object User (that is the table USER in my db) in my HomeController, in that way i don't have to instantiate it in every single action.
I found the following solution that works pretty fine
public class HomeController : Controller
{
private DatabaseContext db = new DatabaseContext();
private User currentUser;
private User CurrentUser
{
get
{
if (User.Identity.IsAuthenticated)
//This function returns the object "User" (table USER in db) based on the PK of the table
currentUser = CustomDbFunctions.GetUserEntityFromUsername(User.Identity.Name, db);
return currentUser;
}
}
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
return View(CurrentUser);
else
return Redirect("login");
}
}
I'd like to know if there's a better (or more elegant) way to achieve the same goal.
Please note that i'm not using the MembershipProvider.
In your example the user object is instantiated in every single action (in contrast to what you said). This is because actions are usually invoked per http request and controller instances are disposed after each use.
Your code shares the instance structurally (you don't have to repeat the code) which is ok but what about sharing the code between different controllers? I'd suggest to refactor your GetUserEntityFromUsername a little bit so that you retrieve the object only once per request, using the Items container to get the request scope:
public class CustomDbFunctions
{
const string itemsUserKey = "_itemsUserKey";
public static User GetUserEntityFromUsername( IPrincipal principal, DatabaseContext db )
{
if ( principal == null || principal.Identity == null ||
!principal.Identity.IsAuthenticated
)
return null;
if ( HttpContext.Current.Items[itemsUserKey] == null )
{
// retrieve the data from the Db
var user = db.Users.FirstOrDefault( u => u.Name == User.Identity.Name );
HttpContext.Current.Items[itemsUserKey] = user;
}
return (User)HttpContext.Current.Items[itemsUserKey];
}
This way your wrapper takes care of retrieving the instance from the database once per request.
Note that this requires sharing your database context as entities should not be reused on different contexts. Fortunately, this can be done in a similar way:
public class CustomDbFunctions
{
const string dbUserKey = "dbUserKey";
public static DatabaseContext CurrentDatabaseContext
{
get
{
if ( HttpContext.Current.Items[dbUserKey] == null )
{
DatabaseContext ctx = new DatabaseContext(); // or any other way to create instance
HttpContext.Current.Items[dbUserKey] = ctx;
}
return (DatabaseContext)HttpContext.Current.Items[dbUserKey];
}
}
This way, the context instance, shared per request, is always available as
CustomDbFunctions.CurrentDatabaseContext

How to ensure proxies are created when using the repository pattern with entity framework?

I have this method in my SurveyController class:
public ActionResult AddProperties(int id, int[] propertyids, int page = 1)
{
var survey = _uow.SurveyRepository.Find(id);
if (propertyids == null)
return GetPropertiesTable(survey, page);
var repo = _uow.PropertySurveyRepository;
propertyids.Select(propertyid => new PropertySurvey
{
//Setting the Property rather than the PropertyID
//prevents the error occurring later
//Property = _uow.PropertyRepository.Find(propertyid),
PropertyID = propertyid,
SurveyID = id
})
.ForEach(x => repo.InsertOrUpdate(x));
_uow.Save();
return GetPropertiesTable(survey, page);
}
The GetPropertiesTable redisplays Properties but PropertySurvey.Property is marked virtual and I have created the entity using the new operator, so a proxy to support lazy loading was never created and it is null when I access it. When we have access direct to the DbContext we can use the Create method to explicitly create the proxy. But I have a unit of work and repository pattern here. I guess I could expose the context.Create method via a repository.Create method and then I need to remember to use that instead of the new operator when I add an entity . But wouldn't it be better to encapsulate the problem in my InsertOrUpdate method? Is there some way to detect that the entity being added is not a proxy when it should be and substitute a proxy? This is my InsertOrUpdate method in my base repository class:
protected virtual void InsertOrUpdate(T e, int id)
{
if (id == default(int))
{
// New entity
context.Set<T>().Add(e);
}
else
{
// Existing entity
context.Entry(e).State = EntityState.Modified;
}
}
Based on the answer supplied by qujck. Here is how you can do it without having to employ automapper:
Edited to always check for proxy - not just during insert - as suggested in comments
Edited again to use a different way of checking whether a proxy was passed in to the method. The reason for changing the technique is that I ran into a problem when I introduced an entity that inherited from another. In that case an inherited entity can fail the entity.e.GetType().Equals(instance.GetType() check even if it is a proxy. I got the new technique from this answer
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = Context.Set<T>();
DbEntityEntry<T> entry;
if (e.GetType().BaseType != null
&& e.GetType().Namespace == "System.Data.Entity.DynamicProxies")
{
//The entity being added is already a proxy type that supports lazy
//loading - just get the context entry
entry = Context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Generate a proxy type to support lazy loading and attach it
T instance = dbSet.Create();
instance.ID = e.ID;
entry = Context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
public abstract class ModelBase
{
public int ID { get; set; }
}
I agree with you that this should be handled in one place and the best place to catch all looks to be your repository. You can compare the type of T with an instance created by the context and use something like Automapper to quickly transfer all of the values if the types do not match.
private bool mapCreated = false;
protected virtual void InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
instance = e;
else
{
//this bit should really be managed somewhere else
if (!mapCreated)
{
Mapper.CreateMap(e.GetType(), instance.GetType());
mapCreated = true;
}
instance = Mapper.Map(e, instance);
}
if (id == default(int))
context.Set<T>().Add(instance);
else
context.Entry(instance).State = EntityState.Modified;
}

How to log changed values to database on all action?

The controller above has a standard edit ActionResult. I simply find rows in a database by ID and update it. Before db.SaveChanges() there is log.Save() static function that saves all changes in model to separate tables in the database.It simply check old and new values from ChangeTracker.
The problem is, i want use log.Save() after db.SaveChanges(), not before, to be sure that data was really saved.
But after, in the ChangeTracker there aren't any changes so log.Save() doesn't have anything to save.
Controller:
[HttpPost]
public ActionResult edit(int id, MyModel model)
{
var hihi = db.MyModel.First(s => s.ID == model.ID);
hihi.col1 = model.col1;
hihi.col2 = model.col2;
...
log.Save(Log.ChangeType.Edit, db, id);
^ Here i save changes to log.
db.SaveChanges();
return RedirectToAction("Index");
}
Log Class:
public void Save(ChangeType changeType, DBContext parentContext, int id)
{
DBContext db = new DBContext();
foreach (System.Data.Entity.Infrastructure.DbEntityEntry ee in parentContext.ChangeTracker.Entries())
{
foreach (string column in ee.OriginalValues.PropertyNames)
{
string oldValue = ee.OriginalValues[column].ToString();
string newValue = ee.CurrentValues[column].ToString();
if (oldValue != newValue)
{
var model = new LogModel
{
Log_Time = DateTime.Now,
Log_Operator = User.Ope_ID,
Log_Table = ee.Entity.ToString().Replace("xxx.Models.", ""),
Log_Key = id,
Log_Column = column,
Log_Type = (int)changeType,
Log_OldValue = oldValue,
Log_NewValue = newValue
};
var log = db.Log.Add(model);
db.SaveChanges();
}
}
}
}
public enum ChangeType
{
Create = 1,
Delete = 2,
Edit = 3
}
... or maybe someone has another way to save all changes in a database to another table on all controller actions, so after the project release I can see what users do.
PS. I don't what user triggers.
SaveChanges in EF4 is virtual, so you can override it, add custom logging etc.
Why don't you use try{} catch{} within Log Class and change the return parameter from 'void' to 'bool'. This would return true if the db.SaveChanges() succeeds. Then within "ActionResult edit" use bool result = log.Save(Log.ChangeType.Edit, db, id); to retrieve if the log saved the changes, then use a simple if-sentence to validate if you can save all changes to db or not.