About Yuan Ji

Yuan is a passionate Java programmer and open source evangelist. He is eager to learn new technologies and loves clean and beautiful application design. He lives in Edmonton, Canada as an independent consultant and contractor.

MongoDB: Add A Counter With Spring Data

In my blog app, you can view any user’s profile, for example, my profile page will be http://www.jiwhiz.com/profile/user1, and ‘user1′ is my user Id in the system. In MongoDB, every document object will have a unique identifier, and often we store it as String, so I have a BaseEntity class for that:
 
 
 
 
 
 
 

@Document
@SuppressWarnings('serial')
public abstract class BaseEntity implements Serializable {
    @Id
    private String id;
…
}

But the system generated id usually is very long, and I want to generate my own userId in my UserAccount class:

@Document(collection = 'UserAccount')
public class UserAccount extends BaseEntity implements SocialUserDetails {
    @Indexed
    private String userId;

    private UserRoleType[] roles;

    private String email;

    private String displayName;

    private String imageUrl;

    private String webSite;
...
}

The generated userId is very simple, just ‘user’ with a sequence number, for example, I’m the first user, so my userId is ‘User1′, and the next signed up user will be ‘User2′, etc. I want a sequence number generator from MongoDB to give me unique sequence numbers. The operations are to return current sequence number and also increase the sequence number in the database. In MongoDB, command findAndModify automatically modifies and returns a single document. So we can use this command to query the sequence number and increase it by $inc function.

First we create a Counter class to store sequence numbers for different purposes, like userId:

@SuppressWarnings('serial')
@Document(collection = 'Counter')
public class Counter extends BaseEntity{

    private String name;

    private long sequence;

...
}

Since we will use counter in a special way, there is no need to have a repository. I just create a CounterService with the method to return the next user id:

public interface CounterService {
    long getNextUserIdSequence();
}

The implementation will use findAndModify to get next sequence:

public class CounterServiceImpl implements CounterService {
    public static final String USER_ID_SEQUENCE_NAME = 'user_id';

    private final MongoTemplate mongoTemplate;

    @Inject
    public CounterServiceImpl(MongoTemplate mongoTemplate){
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public long getNextUserIdSequence() {
        return increaseCounter(USER_ID_SEQUENCE_NAME);
    }

    private long increaseCounter(String counterName){
        Query query = new Query(Criteria.where('name').is(counterName));
        Update update = new Update().inc('sequence', 1);
        Counter counter = mongoTemplate.findAndModify(query, update, Counter.class); // return old Counter object
        return counter.getSequence();
    }
}

Using this approach, you can add as many sequence as you want, just create a name for it. For example, you can record visits to your web site, so add a method like logVisit(), which calls the private method increaseCounter() with a name like ‘visit_num’. In this example, we don’t use Spring Data Repository for Counter document, but instead use MongoTemplate directly. From my MongoConfig class, which extends AbstractMongoConfiguration, which exposes MongoTemplate bean, we can easily inject MongoTemplate into other config bean, like CounterService:

@Configuration
class MainAppConfig {
...
    @Bean
    public CounterService counterService(MongoTemplate mongoTemplate) {
        return new CounterServiceImpl(mongoTemplate);
    }
...
}

Before you start running your app in any environment, you have to set up a Counter document first. Just type the following script in MongoDB shell:

db.Counter.insert({ 'name' : 'user_id', sequence : 1})

OK, those are the steps to prepare a user id sequence generator. But how can we use it when we want to add a new user to our system? It becomes very easy now. We will have an AccountService, which has createUserAccount method, to create a new UserAccount when the user sign in for the first time.

public interface AccountService extends SocialUserDetailsService, UserDetailsService, UserIdExtractor {
    UserAccount findByUserId(String userId);

    List<UserAccount> getAllUsers();

    List<UserSocialConnection> getConnectionsByUserId(String userId);

    UserAccount createUserAccount(ConnectionData data);
}

In our implementation class AccountServiceImpl, we can use CounterService, see highlighted code below:

public class AccountServiceImpl implements AccountService {
    private final UserAccountRepository accountRepository;
    private final UserSocialConnectionRepository userSocialConnectionRepository;
    private final CounterService counterService;

    @Inject
    public AccountServiceImpl(UserAccountRepository accountRepository, UserSocialConnectionRepository userSocialConnectionRepository, CounterService counterService) {
        this.accountRepository = accountRepository;
        this.userSocialConnectionRepository = userSocialConnectionRepository;
        this.counterService = counterService;
    }

    @Override
    public UserAccount findByUserId(String userId) {
        return accountRepository.findByUserId(userId);
    }

    @Override
    public List<UserAccount> getAllUsers() {
        return accountRepository.findAll();
    }

    @Override
    public List<UserSocialConnection> getConnectionsByUserId(String userId){
        return this.userSocialConnectionRepository.findByUserId(userId);
    }

    @Override
    public UserAccount createUserAccount(ConnectionData data) {
        UserAccount account = new UserAccount();
        account.setUserId('user' + this.counterService.getNextUserIdSequence());
        account.setDisplayName(data.getDisplayName());
        account.setImageUrl(data.getImageUrl());
        account.setRoles(new UserRoleType[] { UserRoleType.ROLE_USER });
        this.accountRepository.save(account);
        return account;
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
        UserAccount account = findByUserId(userId);
        if (account == null) {
            throw new UsernameNotFoundException('Cannot find user by userId ' + userId);
        }
        return account;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return loadUserByUserId(username);
    }

    @Override
    public String extractUserId(Authentication authentication) {
        if (authentication instanceof SocialAuthenticationToken) {
            SocialAuthenticationToken token = (SocialAuthenticationToken) authentication;
            if (token.getPrincipal() instanceof SocialUserDetails) {
                return ((SocialUserDetails) token.getPrincipal()).getUserId();
            }
        }
        return null;
    }

}

The Java config code to glue them together for AccountService:

@Configuration
class MainAppConfig {
...
    @Bean
    public AccountService accountService(MongoTemplate mongoTemplate, UserAccountRepository accountRepository,
            UserSocialConnectionRepository userSocialConnectionRepository) {
        AccountServiceImpl service = new AccountServiceImpl(accountRepository, userSocialConnectionRepository,
                counterService(mongoTemplate));
        return service;
    }
...
}

When do we call AccountService.createUserAccount()? At the time when a first time user tries to sign in, and the system cannot find an existing UserAccount, so the ConnectionSignUp bean plugged into MongoUsersConnectionRepository will be called. (See my previous post for other spring social connection related code.) So ConnectionSignUp will pass ConnectionData to AccountService.createUserAccount():

public class AutoConnectionSignUp implements ConnectionSignUp{
    private final AccountService accountService;

    @Inject
    public AutoConnectionSignUp(AccountService accountService){
        this.accountService = accountService;
    }

    public String execute(Connection<?> connection) {
        ConnectionData data = connection.createData();

        UserAccount account = this.accountService.createUserAccount(data);

        return account.getUserId();
    }
}

My experience with Spring Data MongoDB is very positive. It is very powerful in providing basic CRUD functions as well as abundant query functions, and you don’t need to write any implementation code. If you have to use a special command of MongoDB, MongoTemplate is flexible enough to meet your requirements.
 

Reference: MongoDB: Add A CounterWithSpring Data from our JCG partner Yuan Ji at the Jiwhiz blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


− 8 = one



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close