I have a scenario similar to this:
Asp.NET MVC 4 website using nHibernate Session Per Request.
The session is injected using Ninject onto a Repository with the Get and Save methods.
There are a lot of articles talking about Session Per Request and saying that is the way to do things on a web application.
But i'm having problems implementing logic like this one:
Read Data From Database
Alter Entity information
Save to Database
Read another entity
Alter entity
Save ... but an EXCEPTION OCCURS
I want to show my view with a message to the user. But i have also to refresh the resulting web page,
so i have also to read some information from the database.
According to nHibernate documentation, the session with the exception must be discarded Documentation Here
But i can't find any articles about the best way to proceed here.
What's the best approach for this situation?. I will have to inject a new Session to my repository object?.
Thanks.
You can create a new session from the SessionFactory property of the original session. You can access the original session object by either exposing it in the repository class or injecting it into the controller. Then you can create a new repository with the new session.
I do this in some of my Actions where I expect unique key violations to occur and I have to reload lookup data in the model. Here's an example:
public ActionResult Create(MeasuresEditView model)
{
if (ModelState.IsValid)
{
using (var txn = _session.BeginTransaction())
{
try
{
var measure = new Measure { Code = model.Code };
_session.Save(measure);
txn.Commit();
return RedirectToAction("Index");
}
catch (UniqueKeyException)
{
txn.Rollback();
var msg = string.Format("A measure with the code '{0}' already exists, please enter a different code or cancel.", model.Code);
ModelState.AddModelError("Code", msg);
}
catch (Exception ex)
{
if (txn.IsActive)
{
txn.Rollback();
}
log.Error("Create", ex);
throw;
}
}
}
// have to rebuild selectlist on post in new txn in case it was rolled back
using (var session = _session.SessionFactory.OpenSession())
using (var txn = session.BeginTransaction())
{
SetProductGroupSelectList(session, model, manualId);
txn.Commit();
}
return View(model);
}
Related
I am working on a legacy non-Spring application, and it is being migrated from Hibernate 3 to Hibernate 5.6.0.Final (latest at this time). I have generally never used Hibernate Event Listeners in my work, so this is quite new to me, and I am studying these in Hibernate 5.
Currently in some test class we have defined the code this way for Hibernate 3:
protected static Configuration createSecuredDatabaseConfig() {
Configuration config = createUnrestrictedDatabaseConfig();
config.setListener("pre-insert", "com.app.server.services.db.eventlisteners.MySecurityHibernateEventListener");
config.setListener("pre-update", "com.app.server.services.db.eventlisteners.MySecurityHibernateEventListener");
config.setListener("pre-delete", "com.app.server.services.db.eventlisteners.MySecurityHibernateEventListener");
config.setListener("pre-load", "com.app.server.services.db.eventlisteners.EkoSecurityHibernateEventListener");
return config;
}
This is obviously no longer valid, and I believe I need to create a Hibernate Integrator, which I have done.
public class MyEventListenerIntegrator implements Integrator {
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(new MySecurityHibernateEventListener());
eventListenerRegistry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(new MySecurityHibernateEventListener());
eventListenerRegistry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(new MySecurityHibernateEventListener());
eventListenerRegistry.getEventListenerGroup(EventType.PRE_LOAD).appendListener(new MySecurityHibernateEventListener());
}
So, now I believe the next step is to add this to the session via the registry builder. I am using this website to help me:
https://www.boraji.com/hibernate-5-event-listener-example
Because we were using older Hibernate 3, we had code to create our session factory as follows:
protected static SessionFactory buildSessionFactory(Database db)
{
if (db == null) {
throw new NullPointerException("Database specifier cannot be null");
}
try {
Configuration config = createSessionFactoryConfiguration(db);
String url = config.getProperty("connection.url");
String user = config.getProperty("connection.username");
String password = config.getProperty("connection.password");
try {
String dbDriver = config.getProperty("hibernate.connection.driver_class");
Class.forName(dbDriver);
Connection conn = DriverManager.getConnection(url, user, password);
}
catch (SQLException error) {
logger.info("Didn't find driver, on QA or production, so it's okay to assume we have DB connection");
error.printStackTrace();
}
SessionFactory sessionFactory = config.buildSessionFactory();
sessionFactoryConfigs.put(sessionFactory, config); // Cannot recover config from factory instance, must be stored.
return sessionFactory;
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
logger.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);
}
}
The link that I referred to above has a much different way of creating the sessionfactory. So, I'll be testing that out to see if it works in our app.
Without Spring handling our sessions and transactions, in this app it is coded by hand the way it was done before Spring, and I haven't seen that kind of code in years.
I solved this issue with the help from the link I provided above. However, I didn't copy exactly what they did, but some of it helped. My solution is as follows:
protected static SessionFactory createSecuredDatabaseConfig() {
Configuration config = createUnrestrictedDatabaseConfig();
BootstrapServiceRegistry bootstrapRegistry =
new BootstrapServiceRegistryBuilder()
.applyIntegrator(new EkoEventListenerIntegrator())
.build();
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapRegistry).applySettings(config.getProperties()).build();
SessionFactory sessionFactory = config.buildSessionFactory(serviceRegistry);
return sessionFactory;
}
This was it. I tried multiple different ways to register the events without the BootstrapServiceRegistry, but none of those worked. I did have to create the integrator. What I did NOT include was the following:
MetadataSources sources = new MetadataSources(serviceRegistry )
.addPackage("com.myproject.server.model");
Metadata metadata = sources.getMetadataBuilder().build();
// did not create the sessionFactory this way
sessionFactory = metadata.getSessionFactoryBuilder().build();
If I had gone further and use this method to create the sessionFactory, then all of my queries would have been complaining about not being able to find the parameterName, which is something else.
The Hibernate Integrator and this method to create the sessionFactory is all for the unit tests. Without registering these events, one unit test would fail, and now it doesn't. So, this solves my problem for now.
How can we fetch liferay entities through custom-finder using custom SQL?
Following is my sql query written in default.xml (I have trimmed down the query to the bare minimum so that the logic remains simple. Since it included a few functions and joins we couldn't use DynamicQuery API ):
SELECT
grp.*
FROM
Group_
WHERE
site = 1
AND active_ = 1
AND type_ <> 3
Relevant code in MyCustomGroupFinderImpl.java:
Session session = null;
try {
session = openSession();
// fetches the query string from the default.xml
String sql = CustomSQLUtil.get(FIND_ONLY_ACTIVE_SITES);
SQLQuery sqlQuery = session.createSQLQuery(sql);
sqlQuery.addEntity("Group_", GroupImpl.class);
// sqlQuery.addEntity("Group_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.GroupImpl"));
return (List<Group>) QueryUtil.list(sqlQuery, getDialect(), 0, QueryUtil.ALL_POS);
}
catch (Exception e) {
throw new SystemException(e);
}
finally {
closeSession(session);
}
This above code won't work as the GroupImpl class is present in portal-impl.jar and this jar cannot be used in custom portlet.
I also tried using sqlQuery.addEntity("Group_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.GroupImpl"))
But this above code throws exception:
com.liferay.portal.kernel.exception.SystemException:
com.liferay.portal.kernel.dao.orm.ORMException:
org.hibernate.MappingException:
Unknown entity: com.liferay.portal.model.impl.GroupImpl
But the same code works for our custom-entity, if we write sqlQuery.addEntity("MyCustomGroup", MyCustomGroupImpl.class);.
Thanks
I found out from the liferay forum thread that instead of session = openSession();
we would need to fetch the session from liferaySessionFactory as follows to make it work:
// fetch liferay's session factory
SessionFactory sessionFactory = (SessionFactory) PortalBeanLocatorUtil.locate("liferaySessionFactory");
Session session = null;
try {
// open session using liferay's session factory
session = sessionFactory.openSession();
// fetches the query string from the default.xml
String sql = CustomSQLUtil.get(FIND_ONLY_ACTIVE_SITES);
SQLQuery sqlQuery = session.createSQLQuery(sql);
// use portal class loader, since this is portal entity
sqlQuery.addEntity("Group_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.GroupImpl"));
return (List<Group>) QueryUtil.list(sqlQuery, getDialect(), 0, QueryUtil.ALL_POS);
}
catch (Exception e) {
throw new SystemException(e);
}
finally {
sessionFactory.closeSession(session); // edited as per the comment on this answer
// closeSession(session);
}
Hope this helps somebody on stackoverflow, also I found a nice tutorial regarding custom-sql which also uses the same approach.
I am trying to write a test for my NHibernate mappings that will automatically pick up and test any new mappings that get added.
At the moment I have a test that opens a session to a known test database then attempts to load the first entity of each type and asserts that it is not null.
This all works fine but it means that every time I add a new entity mapping, I need to remember to update the test.
So, what I want to do is to inspect the mappings and try to load one of each of the mapped entities, but the NHibernate Configuration object that the sessionfactory is built from is not visible to my test so I was wondering if there is a way to access a list of mapped entities from the session or do I need to expose the original Configuration instead?
You can get SessionFactory from Session and SessionFactory has method GetAllClassMetadata() which returns list of IClassMetadata. And from IClassMetadata you can get MappedClass (GetMappedClass())
But you will need some extra work to get subclasses. This code can help:
var metaData = this.session.SessionFactory.GetClassMetadata(baseClass);
if (metaData != null && metaData.HasSubclasses)
{
foreach (string entityName in ((NHibernate.Persister.Entity.IEntityPersister)metaData).EntityMetamodel.SubclassEntityNames)
{
var metadata = this.session.SessionFactory.GetClassMetadata(entityName);
result.Add(metadata.GetMappedClass(EntityMode.Poco));
}
}
I expose the configuration object and do a mapping that queries all of my entities like this. It will output all errors from each of my mappings.:
[TestMethod()]
public void AllNHibernateMappingsAreOkay()
{
bool failed = false;
log4net.Config.XmlConfigurator.Configure();
using (ISession session = SessionFactory.GetCurrentSession())
{
foreach (var s in SessionFactory.GetConfig().ClassMappings)
{
try
{
SessionFactory.GetCurrentSession().CreateQuery(string.Format("from {0} e", s.MappedClass.Name))
.SetFirstResult(0).SetMaxResults(50).List();
}
catch (Exception ex)
{
failed = true;
log.ErrorFormat("\r\n\r\n {0} \r\n {1} \r\n\r\n", ex.Message, ex.InnerException.Message);
}
}
}
Assert.IsFalse(failed, "One or more mappings have errors in them. Please refer to output or logs.");
}
if you have only one row per entity then you could issue session.QueryOver<object>().List();
I am looking for a simple NHibernate example which will show me how iterate on an entire table. Here is what I have so far, but it is not working. I am getting an "System.InvalidOperationException: Operation is not valid due to the current state of the object.". What am I doing wrong?
public IEnumerable<EMPDATA> getEMPData()
{
using (ISession session = NHibernateHelper.OpenSession())
{
IEnumerable<EMPDATA> empData = session.CreateQuery("from EMPDATA").Enumerable<EMPDATA>();
return empData;
}
}
public static void Main(System.String[] args)
{
log.Debug("Entered main");
Console.WriteLine("Entered main");
try
{
IEMPDataRepository repository = new EMPDataRepository();
IEnumerable<EMPDATA> iterList = repository.getEMPData();
while( iterList.GetEnumerator().MoveNext())
{
EMPDATA emp = iterList.GetEnumerator().Current;
log.Debug(emp.EMP_ID);
}
}
catch (System.Exception ex)
{
log.Error("Exception occured reading emp data", ex);
}
Here is my mapping:
You request an Enumerable result, which probably relies on the session still beeing open.
since you Dispose the session after returning the Enumerable instance, you have closed the connection to the database.
EDIT: see NotSupportedException on IQuery's Enumerable when using statelesssession
Short answer: use .List instead of .Enumerable.
Longer answer:
1. I agree with Phill- looks like a job for a SP
2. Diego is (obviously) right, but if I were you i'd use SetFirstResult() and SetMaxResult() in order to control the amount of data you load into memory in each iteration (don't forget to sort by something when using this method, of course).
The entities and mappings I'm talking about in this question can be found here :)
Here is the context:
I have a parent view-model which helps to manage some entities, and which has its own session.
From this VM, I open another view-model (with its own session too), do some changements to the entity (add and/or remove children), and when I validate the changements, I commit the session and warns the first view-model to refresh the display:
public void Validate()
{
using (var tx = Session.BeginTransaction())
{
try
{
SelectedTeam.ClearRoster();
foreach (var teamPlayer in TeamPlayers)
SelectedTeam.AddPlayer(teamPlayer);
teamsRepository.SaveOrUpdate(SelectedTeam);
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
}
finally
{
if (tx.WasCommitted)
ServiceLocator.Current.GetInstance<Mediator>().NotifyColleagues(MediatorMessages.DisplayEntityInfos, SelectedTeam.Id);
}
}
}
Here is the faulted method of the parent VM:
public void RefreshEntitiesListAndDisplayEntityInfos(int selectedEntityId)
{
TEntity entity = entitiesRepository.Load(selectedEntityId);
Session.Refresh(entity);
//...
}
The exception is thrown at the Refresh line:
NHibernate.UnresolvableObjectException
And the message is:
No row with the given identifier exists[Emidee.CommonEntities.PlayerInTeam#3
I can open and change the entity multiple times, but it seems that the exception is thrown when I delete a children, then add another one, and finally delete another one.
After some readings on the web, it seems that's because when I refresh the entity, and because I changed the HasMany relationship (because I have deleted a player for example), NH tries to reload the deleted row.
I've tried to add a NotFound.Ignore statement on the HasMany in my mappings, I've tried to force a new query to the DB instead of a Load, but I still get this exception.
Does someone know how I could fix that?
Thanks in advance
Mike
This is a known behavior when refreshing objects with modified collections.
To force reload, change your method to do session.Evict with the entity as a parameter. This is the code we use in our base model class:
public T ReGet<T>(T entity) where T : IEntity
{
var id = entity.Id;
Session.Evict(entity);
return Session.Get<T>(id);
}
Well, I've just spotted the problem.
To update the players list of the team, I used to clear the list, and add the new players, before updating the entity.
Now, I update the list by removing and adding only the players who have been moved by the user, and I don't have any problems at all now.
That's weird. I don't know what was wrong before, but as least that works now.