Hibernate's session.currentSession() with #Transactional errors while session.openSession() doesn't - spring

I have a web app where a RESTful front end produces entries in the database and a job wired with cron expressions fire off logic to process the records. The relationship I have is
User 1 -> * Notification
When notifications are sent they are removed. On rare occasion, if a User receives a notification (to be persisted) as the job is deleting records out of their list of notifications (after being sent) I get a constraint violation on the join table where the join column must reference a notification in the notification table.
public void deleteNotifsInUser(List<Notification> notifsToDelete, User user) {
if(user != null && notifsToDelete != null && notifsToDelete.size() > 0) {
if(log.isDebugEnabled()) {
log.debug(notifsToDelete);
}
Session session = sessionFactory.openSession();
User userInDb = (User) session.merge(user);
for (Notification n : notifsToDelete) {
Object notifInDb = session.merge(n);
userInDb.getNotifications().remove(notifInDb);
session.delete(notifInDb);
}
session.close();
}
}
Can anyone tell me why using a #Transaction on this method would cause a different behavior than using the API to open a session?

By having a quick look at your code you open the session inside an if statement and base on some conditions. When you use the annotation it opens the session before the if statements and regardless of the conditions. That could be the reason behind the different behaviours.

Related

Google App Engine: Object with id “” is managed by a different Object Manager - Revisited

I'm getting the following error using GAE, JPA, and Spring
Object with id “” is managed by a different Object Manager
When I first create an account, I put the User object in the session. Then when I update the user profile during that initial session, I merge the detached User. All works great.
I then logout and later create a new session. This time, I load the User object and place into the session. Still OK, but problem is when I update the user profile, the merge fails with the above error.
public boolean loadProfile(String openId, String email) {
User user = null;
try {
user = userDao.findByOpenId(openId);
} catch (NoResultException e) {
}
if (user != null) {
logger.error(JDOHelper.getPersistenceManager(user));
getSessionBean().setUser(user);
return true;
} else {
user = createNewAccount(openId, email);
getSessionBean().setUser(user);
return false;
}
}
#Transactional(propagation=Propagation.REQUIRES_NEW)
private User createNewAccount(String openId, String email) {
User user = new User();
user.setDisplayName(Long.toString(System.currentTimeMillis()));
OpenIdentifier oid = new OpenIdentifier();
oid.setOpenId(openId);
oid.setEmail(email);
oid.setUser(user);
Set<OpenIdentifier> openIds = new HashSet<OpenIdentifier>();
openIds.add(oid);
user.setOpenIds(openIds);
user = userDao.merge(user);
return user;
}
#Transactional(propagation=Propagation.REQUIRED)
public void createOrUpdate(ActionEvent e) {
logger.error(JDOHelper.getPersistenceManager(userFacade.getDelegate()));
User user = userDao.merge(userFacade.getDelegate());
sessionBean.setUser(user);
}
I found these related questions, but I'm still not able to fix.
AppEngine datastore: "Object with id ... is managed by a different Object Manager"
Google App Engine - Object with id "" is managed by a different - JPA
Datanucleus: moving from #Transactional to non-transactional
http://www.atentia.net/2010/03/object-with-id-is-managed-by-a-different-object-manager/
WRT closing the PM (as per 1 & 2), I'm not able to explicitly close the PM since I'm using Spring
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter. From logs, it appears to be opening and closing on each page request.
WRT making the entity detachable (as per 3 & 4), first of all, I'm using JPA and it seems wrong to use a JDO-related annotation. Secondly, it didn't work when I tried.
For extra credit, how do you debug with JDOHelper.getPersistenceManager(obj)? I am getting null in this case, as the User was detached between page requests. That seems normal to me so I'm not clear how to debug with it.
You don't have a PM, you have an EM. No idea what you're referring to there.
Detachable : with JPA all classes are (enhanced as) detachable
You're using some ancient GAE JPA plugin there (v1.x?), and that uses old versions of DataNucleus that are not supported. Use GAE JPA v2.x. "ObjectManager" hasn't existed in DataNucleus for years.
You (or the software you're using) have to close the EM or you get resources leaked all over the place.
NucleusJPAHelper.getEntityManager(obj); is how you get the EntityManager that manages an object (in DataNucleus v3.x, used by GAE JPA v2.x)

Session management for a RESTful Web Service using Jersey

I am developing a Restful Web Service using Jersey between my Android, iPhone apps and MySQL. I also use Hibernate to map the data to the database.
I have a sessionId (key). it is generated when user Login to the system.
In User class:
public Session daoCreateSession() {
if (session == null) {
session = new Session(this);
} else {
session.daoUpdate();
}
return session;
}
In Session Class:
Session(User user) {
this.key = UUID.randomUUID().toString();
this.user = user;
this.date = new Date();
}
void daoUpdate() {
this.key = UUID.randomUUID().toString();
this.date = new Date();
}
When user Sign in to the system successfully, I send this sessionId to the Mobile app client. Then when I want to get some information from database based on the logged in user, I check this Session key as authentication in the REST Services for every request.
For example for the list of project that user is involved in, I use client.GET(SERVER_ADDRESS/project/get/{SessionID})
insetead of client.GET(SERVER_ADDRESS/project/get/{username}).
And if it is not a valid session key, I'll send back to the client a 403 forbidden code.
You can also take a look here
The thing is I am not sure about my approach. what do you think about cons in this approach considering for Jersey and a mobile app?
I still don't know if the Session key approach is a good idea in my case.
If you want to use SessionId then it should have a validation time, like this:
private static final int MINUTES = 90;
public boolean isValid() {
return System.currentTimeMillis() - date.getTime() < 1000 * 60 * MINUTES;
}
This is a solved problem - servlet containers like Tomcat already do session management, and can distribute session state to other containers in the cluster either by broadcasting over TCP, or by using a shared data source like memcache.
I'd suggest reading up on what's already available, rather than inadvertently reinventing the wheel. Additionally, this is going to become an incredibly hot table table if your application proves popular. How will you clear out old session IDs?

How can I display a message through multiple sessions? JSF 2.1 & Primefaces 3.1

I have an application that shows some data in p:DataTable.... This table is accessible by many users. When one user modify a record in the table, (create, edit, delete), this action is notified to an #ApplicationScoped ManagedBean that notify all other session (opened by other users), that the items in the table are changed, then it must be reload from database.
As you can see this is the controller that notify to all session that the items are changed,
/**
* #author Simone Rondelli
*/
#ManagedBean(name="singleton")
#ApplicationScoped
public class SingletonBean {
private int count;
private HashMap<Class, List<AbstractController>> sessions;
public SingletonBean() {
sessions = new HashMap<Class, List<AbstractController>>();
}
public void addSession(AbstractController session, Class c) {
List<AbstractController> sessionList = sessions.get(c);
if(sessionList == null)
sessionList = new ArrayList<AbstractController>();
sessionList.add(session);
sessions.put(c, sessionList);
}
public void notifyItemsChanged(Class type) {
for(AbstractController a : sessions.get(type)) {
a.prepareList();
a.addWarningMessage("Attenzione i record sono stati modificati!!");
}
}
}
this is the code in AbstractController that "try" to show message
public void addWarningMessage(String msg) {
//JsfUtil.addWarningMessage(msg);
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_WARN, msg, msg);
FacesContext.getCurrentInstance().addMessage(null, facesMsg);
}
if you press f5, in any other page, the new records are showed... But i want that messages are sent to all other sessions... Now with my code the messages are showed in the page where the modify is done, many time as the number of opened sessions... So if i have 3 sessions with 3 users and one of these makes some change in the table, he will see 3 messages in his page, meanwhile the other users don't see anything.
How can i send Messages to all sessions??
I think that you should check Primefaces Push , take a look at the examples over there...
PrimePush
Or you can try the Ajax Poll AJAX Poll
Here a ref' for Push in Glassfish question

Saving Data Locally and Remotely (Syncing)

When data is entered, it ultimately needs to be saved remotely on a server. I do want the app to work if there is no data connection at the time also, so I need to save everything locally on the phone too. The app can then sync with the server when it gets a connection.
This brings up a little issue. I'm used to saving everything on the server and then getting the records back with id's generated from the server for them. If there is no connection, the app will save locally to the phone but not the server. When syncing with the server, I don't see a way for the phone to know when a record comes back which locally record it's associated with. There isn't enough unique data to figure this out.
What is the best way to handle this?
One way I've been thinking is to change the id of the records to a GUID and let the phone set the id. This way, all records will have an id locally, and when saving to the server, it should still be a unique id.
I'd like to know what other people have been doing, and what works and what doesn't from experience.
This is how we done with a first windows phone 7 app finished few days ago with my friend.
It might not be the best solution but 'till additional refactoring it works just fine.
It's an application for a web app like a mint.com called slamarica.
If we have feature like save transaction, we first check if we have connection to internet.
// Check if application is in online or in offline mode
if (NetworkDetector.IsOnline)
{
// Save through REST API
_transactionBl.AddTransaction(_currentTransaction);
}
else
{
// Save to phone database
SaveTransactionToPhone(_currentTransaction);
}
If transaction is successfully saved via REST, it responses with transaction object and than we save it to local database. If REST save failed we save data to local database.
private void OnTransactionSaveCompleted(bool isSuccessful, string message, Transaction savedTransaction)
{
MessageBox.Show(message);
if(isSuccessful)
{
// save new transaction to local database
DatabaseBl.Save(savedTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(savedTransaction));
App.ViewModel.SortTransactionList();
// Go back to Transaction List
NavigationService.GoBack();
}
else
{
// if REST is failed save unsent transaction to Phone database
SaveTransactionToPhone(_currentTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(_currentTransaction));
App.ViewModel.SortTransactionList();
}
}
Every Transaction object has IsInSync property. It is set to false by default until we got confirmation from REST API that it's saved successful on the server.
User has ability to refresh transactions. User can click on a button Refresh to fetch new data from the server. We do the syncing in the background like this:
private void RefreshTransactions(object sender, RoutedEventArgs e)
{
if (NetworkDetector.IsOnline)
{
var notSyncTransactions = DatabaseBl.GetData<Transaction>().Where(x => x.IsInSync == false).ToList();
if(notSyncTransactions.Count > 0)
{
// we must Sync all transactions
_isAllInSync = true;
_transactionSyncCount = notSyncTransactions.Count;
_transactionBl.AddTransactionCompleted += OnSyncTransactionCompleted;
if (_progress == null)
{
_progress = new ProgressIndicator();
}
foreach (var notSyncTransaction in notSyncTransactions)
{
_transactionBl.AddTransaction(notSyncTransaction);
}
_progress.Show();
}
else
{
// just refresh transactions
DoTransactionRefresh();
}
}
else
{
MessageBox.Show(ApplicationStrings.NETWORK_OFFLINE);
}
}
private void DoTransactionRefresh()
{
if (_progress == null)
{
_progress = new ProgressIndicator();
}
// after all data is sent do full reload
App.ViewModel.LoadMore = true;
App.ViewModel.ShowButton = false;
ApplicationBl<Transaction>.GetDataLoadingCompleted += OnTransactionsRefreshCompleted;
ApplicationBl<Transaction>.GetData(0, 10);
_progress.Show();
}
OnTransactionRefreshCompleted we delete all transaction data in local database and get the latest 10 transactions. We don't need all the data, and this way user have synced data. He can always load more data by taping load more at the end of transaction list. It's something similar like those twitter apps.
private void OnTransactionsRefreshCompleted(object entities)
{
if (entities is IList<Transaction>)
{
// save transactions
var transactions = (IList<Transaction>)entities;
DatabaseBl.TruncateTable<Transaction>();
DatabaseBl.Save(transactions);
((MainViewModel) DataContext).Transactions.Clear();
//reset offset
_offset = 1;
//update list with new transactions
App.ViewModel.LoadDataForTransactions(transactions);
App.ViewModel.LoadMore = false;
App.ViewModel.ShowButton = true;
}
if (entities == null)
{
App.ViewModel.ShowButton = false;
App.ViewModel.LoadMore = false;
}
// hide progress
_progress.Hide();
// remove event handler
ApplicationBl<Transaction>.GetDataLoadingCompleted -= OnTransactionsRefreshCompleted;
}
Caveat - I haven't tried this with windows phone development but use of GUID identities is something I usually do when faced with similar situations - eg creating records when I only have a one-way connection to the database - such as via a message bus or queue.
It works fine, albeit with a minor penalty in record sizes, and can also cause less performant indexes. I suggest you just give it a shot.

Cannot get session ID for "remember me" users inside spring security login event listener

I'm writing a web app using grails and spring-security 3.0.3 which requires me to keep a databse record of all successful logins, including the username, time, ip, and sessionId.
I have created a login listener as so(groovy code):
class DpLoginListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
def source = event.getSource()
def principal = source.principal
def details = source.details
TrafficLogin.withTransaction {
new TrafficLogin(userId: principal.id, ipAddress: details.remoteAddress, sessionId: details.sessionId ?: 'remember me').save(failOnError: true)
}
}
}
This fires as expected when a user logs in, but when someone comes back to a site as a "remember me" user, the sessionId is null. Is this the expected behavior? Shouldn't a session be created by the time login has succeeded?
I tried to workaround this by adding a separate SessionCreationEvent listener, which would find the latest databse login record for the user and update that row with the correct sessionId as soon as the session exists, but it seems that this session creation event never fires, and I can't figure out why.
The session creation listener looks like this:
class DpSessionCreatedListener implements ApplicationListener<SessionCreationEvent> {
void onApplicationEvent(SessionCreationEvent event) {
def source = event.getSource()
def principal = source.principal
def details = source.details
TrafficLogin.withTransaction {
def rememberMe = TrafficLogin.find("from TrafficLogin as t where t.userId=? and t.sessionId='remember me' order by t.dateCreated desc", principal.id)
if (rememberMe) {
rememberMe.sessionId = details.sessionId
rememberMe.save(failOnError:true)
}
}
}
}
And a bean is defined for it in my resources.groovy file, in the same manner as the login listener, which fires fine.
Any ideas how to correctly set this sessionId?
Not related to grails, but this SO question may offer a workaround if grails supports it.

Resources