Reputation: 5313
I am working on a Spring-MVC application in which we have a method which updates contents of an object. This method requires lot of processing and can be called by multiple users for updating the same object. How can I ensure that the method gets updated in sequence as triggered by each user, and none of the user has stale data while updating.
Service layer method :
@Transactional
@Service
private GroupNotesServiceImpl implements GroupNotesService{
@Override
public String editNoteWithMap(Map<String, Object> noteMap) {
Person person = this.personService.getCurrentlyAuthenticatedUser();
if ((noteMap != null) && (!noteMap.isEmpty())) {
int noteId = (Integer) noteMap.get("id");
// How to ensure below object is not stale.
GroupNotes databaseNoteObject = this.groupNotesDAO.getGroupNoteById(noteId);
{ // Process the Map }
// Update the object as below :
this.groupNotesDAO.editGroupNote(databaseNoteObject, databaseNoteObject.getOwnedSectionId());
}
DAO layer method :
@Repository
@Transactional
public class GroupNotesDAOImpl implements GroupNotesDAO {
private final SessionFactory sessionFactory;
@Override
public GroupNotes editGroupNote(GroupNotes mnotes, int msectionid) {
Session session = this.sessionFactory.getCurrentSession();
session.flush();
// I had the lock before, but it started messing around for other updates of associated objects. That was caused by getById also being under lock.
// session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(mnotes);
GroupSection groupSection = (GroupSection) session.get(GroupSection.class, msectionid);
groupSection.getSectionsnotes().add(mnotes);
mnotes.setOwnednotes(groupSection);
GroupNotes savedObject = (GroupNotes) session.merge(mnotes);
session.merge(groupSection);
session.flush();
return savedObject;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public GroupNotes getGroupNoteById(int id) {
Session session = this.sessionFactory.getCurrentSession();
session.flush();
return (GroupNotes) session.get(GroupNotes.class, id);
}
}
root-context.xml :
<context:component-scan base-package="com.tooltank.spring">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:property-placeholder location="classpath:application.properties"/>
<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<beans:property name="driverClassName" value="org.postgresql.Driver"/>
<beans:property name="url"
value="jdbc:postgresql://localhost:5432/DBNAME"/>
<beans:property name="username" value="USERNAME"/>
<beans:property name="password" value="PASSWORD"/>
<beans:property name="removeAbandoned" value="true"/>
<beans:property name="removeAbandonedTimeout" value="20"/>
<beans:property name="defaultAutoCommit" value="false"/>
</beans:bean>
<!-- Hibernate 4 SessionFactory Bean definition -->
<beans:bean id="hibernate4AnnotatedSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="packagesToScan" value="com.tooltank.spring.model"/>
<beans:property name="hibernateProperties">
<beans:props>
<beans:prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</beans:prop>
<beans:prop key="hibernate.show_sql">false</beans:prop>
<!-- <beans:prop key="hibernate.jdbc.batch_size">1000</beans:prop>-->
<beans:prop key="hibernate.hbm2ddl.auto">update</beans:prop>
<beans:prop key="cache.use_second_level_cache">true</beans:prop>
<beans:prop key="cache.use_query_cache">true</beans:prop>
<beans:prop key="hibernate.order_updates">true</beans:prop>
<beans:prop key="show_sql">false</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
<beans:bean id="LoginServiceImpl" class="com.tooltank.spring.service.LoginServiceImpl"/>
<task:annotation-driven executor="myExecutor"/>
<task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<beans:bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<beans:property name="sessionFactory" ref="hibernate4AnnotatedSessionFactory"/>
</beans:bean>
<cache:annotation-driven/>
<beans:bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<beans:property name="caches">
<beans:set>
<beans:bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="person"/>
</beans:set>
</beans:property>
</beans:bean>
<!-- AppConfiguration for Spring-Data-Redis -->
<beans:bean id="jedisConnFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:usePool="true"/>
<beans:bean id="redisSaveTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connectionFactory-ref="jedisConnFactory"/>
<beans:bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connectionFactory-ref="jedisConnFactory"/>
Thank you.
Upvotes: 0
Views: 972
Reputation: 4116
Your requirements may be conflicting if I understand them correctly:
method gets updated in sequence as triggered by each user, and none of the user has stale data while updating
Given N synchronized update in sequence on the same entity.
The first one will udpate the entity. Thus following update will update an already updated entity with stale data.
If you only implements synchronization using synchronized
:
If you only implements versioning
I think Optimistic locking and versioning does only make sense.
If you alleviate your requirements to just in sequence, synchronization will work. But on method it may too broad. For this type of use case you can use the guava striped Lock
Upvotes: 1
Reputation: 7044
There are several way to do this.
1. Quick way, just syncronize it!
By making the editNoteWithMap() syncronized method, you can ensure only one thread accessing the editNoteWithMap(). However syncronized might not be the magic help you want if the note can be updated somewhere else (other method /other system) , or if you worry it might cause performance issue.
2. Do it the long way, by implement locking/version mechanism.
You can create locking/version mechanism on the note record. There are several way to achieve it, one example is by adding a version column on the note record. Prior to the update, you should check if the version of the record is equal to the version you retrieved, and decide whether you want to retry or fail the update.
Upvotes: 2