Как стать автором
Обновить
41
0.1
Сергей Цыпанов @tsypanov

Разработчик

Отправить сообщение
Не понял вашу мысль.
И вам спасибо!

Мне кажется, что выбор между Spring Data JDBC и Spring Data JPA сродни выбору между ложкой и вилкой. И тем, и тем мы едим, но щи удобнее есть ложкой, а пельмени — вилкой.

Иными словами, выбирать нужно исходя из создавшегося положение и моя заметка как раз о случаях, когда лучше выбрать Spring Data JPA и не наступить на грабли.

Не за что, наберу ещё материала — и будет вторая часть про кастомизацию репозиториев. Там тоже есть интересные моменты.

Здесь для упрощения всё показано в одном методе, однако возможна (и мне подобные встречались) ситуация, когда у нас есть что-то вроде


BankAccount acc = repository.findById(id).orElseThow(NPE::new);
check(id);

public void (check) {
  boolean available = repository.findIfAvailable(id);
  if (!available) throw new IllegalStateException();
}

при чём всё это выполняется в одной транзакции (т.е. используется одна и та же сессия). Для неискушенного разработчика неочевидно, что второй repository.findById(id) возьмёт сущность из наличия.


Кстати, если метод очень большой и туда в разное время влезло несколько разработчиков, то и описанный мной вариант возможен почти дословно.

Сам по себе Спринг вполне себе управляем. Вот Спринг бут — это действительно огромная и сложная махина.
Можно и так, правда, в этом случае часть методов, объявленных в интерфейсе и тело будет иметь только в интерфейсе, другая же часть будет реализована в классе. ИМХО, лучше уже всё делать единообразно.
Особенно про LDAP — хоть в музей относи

Да уж, впору начинать их коллекционировать и на собеседованиях просить соискателей рассказать о своих случаях.

Дело не в прослойке, а в ошибках компиляции.


@Service
@RequiredArgsConstructor
public class Service
  private final SomeRepository someRepository;

  @Transactional
  public void foo(long id) {
    SomeEntity something = someRepository.findOne(someId);
    if (something == null) {
      something = someRepository.save(new SomeEntity());
    }
    useSomething(something);
}

На СБ1 (для которого Спринг Дата 1.*) это работает, на СБ2 (для которого Спринг Дата 2.*) нужно:


  • вызывать другой метод
  • сменить тип переменной something на Optional<SomeEntity>
  • заменить проверку пустой ссылки на Optional::isPresent или цепочку Optional::orElse/Optional::orElseGet

И так в каждом сервисе.

Не за что, мне очень понравились ваш доклад и эта статья, по его следам думаю написать статью про Спринг Бут и некоторые его особенности.

Точно не скажу, разговор был формата курилки. Спросил про миграцию на СБ2, вылезло несколько проблем, а поскольку задача была не очень важной, то занимались ей в перерывах между основной работой.


Вот что запомнилось точно — проблемы со Спринг Датой. Начиная с версии 2 изменились названия ключевых методов в JpaRepository:


interface JpaRepository<T, ID> {
  //было
  <T> T findOne(ID id);
  <T> List<T> findAll(Iterable<ID> ids);

  //стало
  <T> Optional<T> findById(ID id);
  <T> List<T> findAllById(Iterable<ID> ids);
}

В проекте несколько десятков репозиториев и тысячи обращений к указанным методам. Миграция "в лоб" (использование новых методов) зацепила бы каждый второй файл. Поэтому воспользовались возможностью подгонки репозиториев под свои нужды:


//основа для всех репозиториев
@NoRepositoryBean
public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
  // определяем метод findOne, аналогичный тёзке, существовавшему в версиях 1.*
  @Deprecated
  T findOne(ID id);

  // тоже для findAll
  @Deprecated
  List<T> findAll(Iterable<ID> ids);
}

public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> {

  private JpaEntityInformation<T, ?> entityInfo;
  private EntityManager entityManager;

  public BaseJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInfo, EntityManager entityManager) {
    super(entityInfo, entityManager);
    this.entityInfo = entityInfo;
    this.entityManager = entityManager;
  }

  //обеспечиваем совместимость поведения
  @Override
  public T findOne(ID id) {
    return findById(id).orElse(null);
  }

  @Override
  public List<T> findAll(Iterable<ID> ids) {
    return findAllById(ids);
  }
}

//какой-нибудь репозиторий
interface SomeRepository extends BaseJpaRepository<SomeEntiy, Lond> {
}

И о чудо! Все ошибки компиляции исчезают, код работает как и прежде, а изменено всего 2 класса, а не 200. Теперь можно неспешно заменять устаревшее АПИ на новое, благо "Идея" заботливо подсветит все вызовы помеченные @Deprecated.

Ответ переехал выше.

Ещё много изменений произошло в Actuator.

Знаю один из проектов, который ещё не переехал на СБ2 именно из-за Актуатора.


Кстати, раз уж речь зашла от миграции: отдельная и интересная тема — переезд с голого Спринга на Спринг Бут. Так на одном из проектов столкнулись с тем, что перестал работать откат транзакций при выбрасывании проверяемых исключений. Когда-то давно возникла необходимость откатывать спринговую транзакцию при выбрасывании этого исключения. Это было сделано с помощью AnnotationTransactionAttributeSource примерно вот так:


public class CustomAttributeSource extends AnnotationTransactionAttributeSource {
  @Override
  protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
    return new DefaultTransactionAttribute() {
      @Override
      public boolean rollbackOn(Throwable ex) {
        return super.rollbackOn(ex) || ex instanceof CustomCheckedException;
      }
    };
  }
}

На чистом Спринге работало, после переезда на СБ1 транзакции откатываться перестали.

1. Количество развёрнутых носителей не исчисляется тысячами, в лучшем случае сотнями. Количество же развёрнутых боеголовок не превышает двух тысяч. Данные из Википедии.

2. Перехватив ракету до развода боевых блоков можно одной противоракетой уничтожить сразу несколько головных частей. А как мы помним, речь не идёт о перехвате тысяч, а всего лишь сотен носителей. Таким образом, масштабное развёртывание ПРО может сильно уменьшить возможность нанесения ответного удара. Не даром же этот договор был заключен.

3. Как я указал в первом ответе (и как справедливо заметил ув. grondek ) в установки может быть загружено всё что угодно. И именно эта возможность вызывает наибольшее беспокойство.
Доказано в Дрездене и Хиросиме.
США свою систему ПРО (из договора о которой они вышли в одностороннем порядке ещё в 2002 году) почему-то не сворачивают и не ликвидируют. Более того, ПРО размещается в непосредственной близости от границ России, в частности в Польше и Румынии. Официально заявляется, что это для защиты от Ирана (верим!). И вот Минобороны России вполне резонно подозревает, что пусковые установки ракет ПРО в этих странах могут использоваться в т. ч. для размещения наступательного вооружения и запрашивает проверку этих установок. На что получает отказ, хотя если наступательных вооружений там нет и установка их невозможна, то этот отказ выглядит подозрительным.

Получается, что Россия не может запросить даже проверку объектов, вызывающих у неё опасение (небезосновательно), при этом от самой России в ультимативной форме требуют уничтожить вооружение, которое, во-первых, не размещается непосредственно у границ США, и во-вторых не является доказанным нарушением Договора РСМД.

Вы правы, передача размера в StringBuilder даёт неплохой прирост в данном случае


@Benchmark
public String appendBoundsSized(Data data) {
  int beginIndex = data.beginIndex;
  int endIndex = data.endIndex;

  return new StringBuilder(endIndex - beginIndex + 2)
          .append('L')
          .append(data.str, beginIndex, endIndex)
          .append(';')
          .toString();
}

Вывод


Benchmark                             length nonLatin   Score   rror Units
appendBounds                              10     true    41,3 ±  0,9 ns/op
appendBounds                             100     true   143,6 ±  8,1 ns/op
appendBounds                            1000     true  1206,5 ± 48,7 ns/op

appendBoundsSized                         10     true    42,6 ±  0,7 ns/op
appendBoundsSized                        100     true   116,2 ± 17,1 ns/op
appendBoundsSized                       1000     true   880,9 ± 33,7 ns/op

appendBounds                              10    false    28,4 ±  0,2 ns/op
appendBounds                             100    false    99,0 ±  3,9 ns/op
appendBounds                            1000    false   663,3 ± 44,5 ns/op

appendBoundsSized                         10    false    29,5 ±  0,9 ns/op
appendBoundsSized                        100    false    68,7 ±  3,9 ns/op
appendBoundsSized                       1000    false   485,6 ± 11,2 ns/op

appendBounds:·gc.alloc.rate.norm          10     true   200,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm         100     true  1192,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm        1000     true 10200,0 ±  0,0  B/op

appendBoundsSized:·gc.alloc.rate.norm     10     true   192,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm    100     true   736,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm   1000     true  6144,0 ±  0,0  B/op

appendBounds:·gc.alloc.rate.norm          10    false   112,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm         100    false   544,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm        1000    false  4152,0 ±  0,0  B/op

appendBoundsSized:·gc.alloc.rate.norm     10    false   112,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm    100    false   288,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm   1000    false  2096,0 ±  0,0  B/op

Интересно, что в этом случае (малое количество вызовов StringBuilder.append) точное выделение памяти даёт очень хороший прирост, чего не скажешь о случае, когда вызовов StringBuilder.append значительно больше.

Спасибо! Уже не первый год работаю, но не устаю удивляться, как много тонкостей стоит вроде бы за простым кодом.

Вы имеете ввиду неэквивалентность байт-кода? Поведение обоих методов, насколько я понимаю, одинаковое:


String foo(String a1, String a2, String a3) {
  StringBuilder sb = new StringBuilder();
  sb.append(a1);
  sb.append(a2);
  sb.append(a3);
  return sb.toString();
}

String _foo(String a1, String a2, String a3) {
  return new StringBuilder()
          .append(a1)
          .append(a2)
          .append(a3)
          .toString();
}

Информация

В рейтинге
3 485-й
Зарегистрирован
Активность