Статьи

MongoDB: добавить счетчик с данными Spring

В моем приложении блога вы можете просмотреть профиль любого пользователя, например, страница моего профиля будет http://www.jiwhiz.com/profile/user1, а user1 — это мой идентификатор пользователя в системе. В MongoDB каждый объект документа будет иметь уникальный идентификатор, и часто мы храним его как String, поэтому для этого у меня есть класс BaseEntity:

1
2
3
4
5
6
7
@Document
@SuppressWarnings('serial')
public abstract class BaseEntity implements Serializable {
    @Id
    private String id;
}

Но сгенерированный системой идентификатор обычно очень длинный, и я хочу создать свой собственный идентификатор пользователя в своем классе UserAccount :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@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;
...
}

Сгенерированный userId очень прост, просто ‘user’ с порядковым номером, например, я первый пользователь, поэтому мой userId — ‘User1’, а следующим зарегистрированным пользователем будет ‘User2’ и т. Д. Я хочу генератор порядковых номеров из MongoDB, чтобы дать мне уникальные порядковые номера. Операции должны вернуть текущий порядковый номер, а также увеличить порядковый номер в базе данных. В MongoDB команда findAndModify автоматически изменяет и возвращает один документ. Таким образом, мы можем использовать эту команду для запроса порядкового номера и увеличения его на функцию $ inc .

Сначала мы создаем класс Counter для хранения порядковых номеров для разных целей, например userId:

01
02
03
04
05
06
07
08
09
10
@SuppressWarnings('serial')
@Document(collection = 'Counter')
public class Counter extends BaseEntity{
 
    private String name;
 
    private long sequence;
 
...
}

Поскольку мы будем использовать счетчик особым образом, вам не нужно иметь репозиторий. Я просто создаю CounterService с методом для возврата следующего идентификатора пользователя:

1
2
3
public interface CounterService {
    long getNextUserIdSequence();
}

Реализация будет использовать findAndModify для получения следующей последовательности:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
    }
}

Используя этот подход, вы можете добавить столько последовательности, сколько захотите, просто создайте для нее имя. Например, вы можете записывать посещения своего веб-сайта, поэтому добавьте метод, подобный logVisit() , который вызывает закрытый метод increaseCounter() с именем, например «visit_num». В этом примере мы не используем Spring Data Repository для Counter документа, но вместо этого используем MongoTemplate напрямую. Из моего класса MongoConfig , который расширяет AbstractMongoConfiguration , который предоставляет MongoTemplate компонент MongoTemplate , мы можем легко внедрить MongoTemplate в другой bean-компонент конфигурации, например CounterService :

1
2
3
4
5
6
7
8
9
@Configuration
class MainAppConfig {
...
    @Bean
    public CounterService counterService(MongoTemplate mongoTemplate) {
        return new CounterServiceImpl(mongoTemplate);
    }
...
}

Прежде чем запускать приложение в любой среде, сначала необходимо настроить документ Counter . Просто введите следующий скрипт в оболочку MongoDB:

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

ОК, это шаги для подготовки генератора последовательности идентификаторов пользователей. Но как мы можем использовать это, когда мы хотим добавить нового пользователя в нашу систему? Это становится очень легко сейчас. У нас будет createUserAccount , у которого есть метод createUserAccount , чтобы создать новый UserAccount при UserAccount пользователя в систему.

1
2
3
4
5
6
7
8
9
public interface AccountService extends SocialUserDetailsService, UserDetailsService, UserIdExtractor {
    UserAccount findByUserId(String userId);
 
    List<UserAccount> getAllUsers();
 
    List<UserSocialConnection> getConnectionsByUserId(String userId);
 
    UserAccount createUserAccount(ConnectionData data);
}

В нашем классе реализации AccountServiceImpl мы можем использовать CounterService , см. Выделенный код ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;
    }
 
}

Конфигурационный код Java, чтобы склеить их вместе для AccountService:

01
02
03
04
05
06
07
08
09
10
11
12
@Configuration
class MainAppConfig {
...
    @Bean
    public AccountService accountService(MongoTemplate mongoTemplate, UserAccountRepository accountRepository,
            UserSocialConnectionRepository userSocialConnectionRepository) {
        AccountServiceImpl service = new AccountServiceImpl(accountRepository, userSocialConnectionRepository,
                counterService(mongoTemplate));
        return service;
    }
...
}

Когда мы вызываем AccountService.createUserAccount() ? В то время, когда пользователь впервые пытается войти в систему и система не может найти существующий UserAccount , будет вызван компонент ConnectionSignUp подключенный к MongoUsersConnectionRepository . (См. Мой предыдущий пост о другом весеннем коде, связанном с социальными связями.) Поэтому ConnectionSignUp передаст ConnectionData AccountService.createUserAccount()

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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();
    }
}

Мой опыт работы с Spring Data MongoDB очень положительный. Он очень эффективен в предоставлении базовых функций CRUD, а также обильных функций запросов, и вам не нужно писать код реализации. Если вам нужно использовать специальную команду MongoDB, MongoTemplate достаточно гибок, чтобы удовлетворить ваши требования.

Ссылка: MongoDB: Добавьте данные CounterWithSpring от нашего партнера по JCG Юаня Цзи в блог Jiwhiz .