We are Borg
We are Borg

Reputation: 5313

Spring-MVC : How to ensure multiple users will be able to call a method in sequence

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

Answers (2)

Nicolas Labrot
Nicolas Labrot

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:

  • concurrents update will be done in sequence but updates could be done using staled data.
  • all calls to this method will be synchronized, even the one which targets another entity.
  • synchronization will not work on multiple instance

If you only implements versioning

  • only one update for the current version of the entity will succeed, others will fail because they try to update the previous version of the entity.

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

Rudy
Rudy

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

Related Questions