Столбец базы данных продолжает возвращаться к нулю

#java #spring-boot #hibernate #jhipster

Вопрос:

неопровержимые факты:

 <spring-boot.version>2.4.4</spring-boot.version>
<jhipster-dependencies.version>7.0.1</jhipster-dependencies.version>
<hibernate.version>5.4.29.Final</hibernate.version>
<liquibase.version>4.3.2</liquibase.version>
<liquibase-hibernate5.version>4.3.2</liquibase-hibernate5.version>
 

База данных-postgresql 13.

Моя проблема: я разрабатываю платформу, на которой игры могут регистрироваться, чтобы получить доступ к другим сторонним API. Для этого у меня есть классная игра:

 **
 * A Game.
 */
@Entity
@Table(name = "game")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Game implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "description")
    private String description;

    @Column(name = "api_key")
    private UUID apiKey;

    @Column(name = "api_secret")
    private UUID apiSecret;

    ....
 

Значение api_secret должно храниться в секрете, поэтому оно отображается только при первом создании игры и в противном случае всегда возвращается как null.
Однако через нерегулярные промежутки времени все api_secrets в моей базе данных имеют значение null, что, конечно, приводит к ошибкам доступа в играх.
Следующие соответствующие фрагменты кода:

Игровой сервис:

 /**
     * Save a game.
     *
     * @param game the entity to save.
     * @return the persisted entity.
     */
    public Game save(Game game) {
        log.debug("Request to save Game : {}", game);
        game = createApiCredentials(game);
        game.setUserExtra(userExtraService.findOne(getCurrentLoggedInUserId()).get());
        return gameRepository.save(game);
    }

    /**
     * Get all the games.
     *
     * @return the list of entities.
     */
    @Transactional(readOnly = true)
    public List<Game> findAll() {
        log.debug("Request to get all Games");
        List<Game> returnList = gameRepository.findAll();
        for (Game game : returnList) {
            game.setApiSecret(null);
        }
        return returnList;
    }

    /**
     * Get one game by id.
     *
     * @param id the id of the entity.
     * @return the entity.
     */
    @Transactional(readOnly = true)
    public Optional<Game> findOne(Long id) {
        log.debug("Request to get Game : {}", id);
        Optional<Game> returnGame = gameRepository.findById(id);
        returnGame.get().setApiSecret(null);
        return returnGame;
    }

    /**
     * Get all the games associated to logged in user.
     *
     * @return the list of entities.
     */
    @Transactional(readOnly = true)
    public List<Game> findAllByAuthenticatedUser() {
        log.debug("Request to get all Games");
        List<Game> returnList = gameRepository.findAllByUserExtra(userExtraService.findOne(getCurrentLoggedInUserId()).get());
        for (Game game : returnList) {
            game.setApiSecret(null);
        }
        return returnList;
    }

    /**
     * Generate new Api-Secret for game.
     *
     * @param id the id of the entity.
     * @return the entity.
     */
    public Game findOneAndGenerateNewApiSecret(Long id) {
        log.debug("Request to get Game : {}", id);
        Game returnGame = gameRepository.findById(id).get();
        returnGame.setApiSecret(UUID.randomUUID());
        return gameRepository.save(returnGame);
    }

    /**
     * Delete the game by id.
     *
     * @param id the id of the entity.
     */
    public void delete(Long id) {
        log.debug("Request to delete Game : {}", id);
        gameRepository.deleteById(id);
    }

    public boolean isGameRegistered(UUID apiKey, UUID apiSecret) {
        if (gameRepository.findByApiKeyAndApiSecret(apiKey, apiSecret) != null) {
            return true;
        }
        return false;
    }

    public Game createApiCredentials(Game game) {
        UUID apiKey = UUID.randomUUID();
        UUID apiSecret = UUID.randomUUID();

        game.setApiKey(apiKey);
        game.setApiSecret(apiSecret);

        return game;
    }
 

Как вы можете видеть, есть много случаев, когда я устанавливаю значение apiSecret равным нулю, но, по моему мнению, только тогда, когда я возвращаю его пользователю. И я никогда не вызываю какой-либо метод сохранения.

Игровая аудитория:

 /**
 * Spring Data SQL repository for the Game entity.
 */
@SuppressWarnings("unused")
@Repository
public interface GameRepository extends JpaRepository<Game, Long> {
    public Game findByApiKeyAndApiSecret(UUID apiKey, UUID apiSecret);

    public Game findByApiKey(UUID apiKey);

    public List<Game> findAllByUserExtra(UserExtra userExtra);
}
 

В базе данных я настроил триггер, который записывает каждое изменение в таблице игр. Здесь я также вижу, что изменение внесено моим программным пользователем. Это означает, что это тоже не ручное вмешательство, а действительно происходит с помощью программы.

Регистрация триггеров:

 public      | game       | dbadmin | 2021-08-29 11:02:13.084959 02 | U      | (5251,"GameName",,,,,gameKey,,00ucvnehiOJ85kZ3N5d5) | (5251,"GameName",,,,,gameKey,,,00ucvnehiOJ85kZ3N5d5)                                     | update game set api_key=$1, api_secret=$2, description=$4, name=$6 where id=$10
 

Возможно, кто-то обнаружит мою ошибку или еще есть какие-то хитрости, как я могу еще больше сузить проблему?

Комментарии:

1. Когда вы задаете свойства для управляемой сущности, состояние будет автоматически сохранено. Так что никогда не делайте этого, если не хотите, чтобы это продолжалось. Вместо того, чтобы возвращать сущность, создайте специальный класс GameView GameDto или что угодно другое и сопоставьте поля с Game ним (либо с помощью пользовательского запроса JPA, либо с помощью картографа, такого как MapStruct, или вручную). Но не связывайтесь с сущностями и не манипулируйте ими. Другой вариант-использовать проекцию, поддерживаемую Spring Data JPA (что делает это довольно простым).,

2. Согласен с мсье Дейном, см. find-sec-bugs.github.io/bugs.htm#ENTITY_LEAK для получения более подробной информации

3. Вместо того, чтобы вмешиваться в сущность, лучше использовать @JsonIgnore для исключения этого свойства из сериализации.

4. Большое спасибо мсье Дейну за подсказку и Гийому за ссылку. Сегодня я снова кое-чему научился. Забавно, что я использую DTO почти для всех моих других сущностей. Я осуществил это изменение.