Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!
Переопределение и дополнение свойств для Spring Boot тестов – собрали в одном месте 6 различных вариантов переопределения свойств
7 основных способов оптимизировать кеширование в Spring Boot – узнали, что, где и как кэшировать, а что лучше не стоит
Самая любимая фича в Java 22 – освежили в памяти все фичи, которые вошли в 22-й релиз Java
Declarative Gradle: рывок или прорыв? – выяснили, что из себя представляет новый проект от Gradle, и попробовали его в действии
Новые возможности конструкторов в Java (часть 2) – продолжили изучать нововведения, связанные с конструкторами объектов
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍9🔥7
По умолчанию Spring инициализирует все sigleton-бины во время запуска приложения. Аннотация
@Lazy
позволяет изменить это поведение, инициализируя бины только по мере необходимости. Используя аннотацию @Lazy
можно существенно снизить потребление памяти и уменьшить время запуска приложения, отметив бины, которые используются в приложении не так часто и занимают немало памяти.Способы применения
Чтобы сделать бин лениво инициализируемым, нужно отметить его аннотацией
@Lazy
в месте его объявления:
@Lazy
@Component
public class MyComponent {
//...
}
@Configuration
@Lazy
public class LazyConfig {
// Все объявленные в этом конфиге бины будут ленивыми
}
@Configuration
public class AppConfig {
//Этот конкретный бин будет ленивым
@Bean
@Lazy
public MyBean myBean() {
return new MyBean();
}
}
А также в месте его инжекции:
@Service
public class MyService {
private final MyBean myBean;
private final MyComponent myComponent;
public MyService(@Lazy MyBean myBean, @Lazy MyComponent myComponent) {
this.myBean = myBean;
this.myComponent = myComponent;
}
}
Если не воспользоваться аннотацией
@Lazy
в месте объявления бина или в месте его инжекции, то он НЕ БУДЕТ ленивым.Ленивая инициализация контекста по умолчанию
Чтобы сделать инициализацию ленивой для всего контекста, можно добавить следущее свойство в конфигурационный файл:
spring.main.lazy-initialization=true
Однако стоит быть осторожным с этим подходом. Ошибки инициализации могут возникнуть не во время запуска приложения, а позже – в рантайме, что усложнит их обнаружение и исправление
@Lazy(false)
:
@Service
@Lazy(false)
public class MyService {
// код
}
А много ли бинов лениво инициализируется на вашем проекте? Может быть вообще все? Поделитесь своим опытом в комментариях, будет интересно почитать!
#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31❤6🔥6
Судя по реакциям на предыдущих постах про JEP, они вам нравятся, так что продолжаем разбирать свежые JEP'ы.
Представим себе небольшой веб-фреймворк, который обрабатывает HTTP-запросы. Фреймворк создает контекст для каждого запроса и передает его через методы:
@Override
public void handle(Request request, Response response) {
var userInfo = readUserInfo();
}
private UserInfo readUserInfo() {
return (UserInfo) framework.readKey("userInfo", context);
}
Ранее для передачи контекста использовались переменные типа
ThreadLocal
:
private final static ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();
void serve(Request request, Response response) {
var context = createContext(request);
CONTEXT.set(context);
Application.handle(request, response);
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}
Scoped Values API впервые был представлен в JDK 20 и прошел несколько итераций и улучшений в последующих версиях JDK (JEP 429, JEP 446, JEP 464, JEP 481). Scoped Values представляют собой новый механизм для передачи неизменяемых данных между методами в одном потоке и между дочерними потоками. Этот механизм проще в использовании по сравнению с
ThreadLocal
и обладает меньшими затратами по времени и памяти.У подхода, использующего
ThreadLocal
, есть несколько недостатков:1. Неограниченная изменяемость — переменные могут изменяться в любое время любым кодом в потоке
2. Неограниченное время жизни — переменные могут существовать дольше, чем необходимо, что может приводить к утечкам памяти
3. Высокая стоимость наследования — при создании дочерних потоков переменные должны копироваться, что увеличивает затраты по памяти
Scoped Values позволяют избежать этих проблем, обеспечивая одноразовую запись и ограниченное время жизни значений:
final static ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();
void serve(Request request, Response response) {
var context = createContext(request);
ScopedValue.runWhere(CONTEXT, context, () -> Application.handle(request, response));
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}
В данном случае метод
ScopedValue.runWhere
связывает значение с текущим потоком на время выполнения лямбда-выражения, после чего связь уничтожается, что улучшает производительность и безопасность кода.В 23 версии Java фича будет пересмотрена повторно с одним изменением: тип параметра операции метода
ScopedValue.callWhere
будет являться новым функциональным интерфейсом, который позволит компилятору Java делать вывод о том, может ли быть выброшено проверяемое исключение. Подробнее про ScopedValue.callWhere
и улучшения, связанные с ним, поговорим во второй части.Ставьте 🔥 если хотите вторую часть!
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥59👍9❤2
Благодаря этой статье вы узнаете, как подключить и настроить Flyway в Spring Boot приложении, а также как сгенерировать скрипты инициализации и миграции схемы базы данных.
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥6❤2
Мы рады представить вам новую рубрику, в которой эксперты сообщества Spring АйО будут отвечать на актуальные и интересные вопросы. Для дебютного выпуска мы пригласили Михаила Поливаху, контрибьютора в Spring Data JDBC, который любезно согласился принять участие. Большое спасибо ему за это! Если вам понравится этот формат, поддержите нас лайками и репостами.
Итак, ниже ответ Михаила на заявленный в заголовке вопрос.
–––
Друзья, по поводу аналога Criteria API в Spring Data JDBC и механизма динамического построения запросов в целом.
TL;DR: Аналог, какой-никакой, имеется, он даже работает😅. Но он менее функциональный, чем тот же Spring Data Specification API.
Long answer:
Если говорить про динамическое построение запроса в целом, то мы можем разбить это на несколько частей:
1. Динамическое построение условий.
Например,
LIKE
, BETWEEN
, IS NOT NULL
и любые другие keyword-ы, которые предназначены для фильтрации набора данных, но которые никак не влияют на структуру запроса в целом. Вот это в Spring Data JDBC имеется. Основные классы, которыми вы будете оперировать в таком случае, - Criteria
и Query
. Использовать это API достаточно просто, единственное - вам придется использовать напрямую JdbcAggregateTemplate
. На данный момент распознавание методов с Query
как параметром метода в Repository/CrudRepository
не поддерживается (речь не про default методы, а именно про абстрактные методы в интерфейсе, которые воспринимаются как PartTreeJdbcQuery
запросы), и скорее всего не будет поддерживаться, так как мало кому это пока было нужно (то есть никакого аналога интерфейсу JpaSpecificationExecutor
в Spring Data JDBC нет). Вот пример использования:
Сущность:
@Data
@Table("users")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
@Id
@EqualsAndHashCode.Include
private Long id;
private String status;
private String name;
private OffsetDateTime createdAt;
}
И пример запроса:
@Autowired
private JdbcAggregateTemplate repository;
@Test
void testCriteriaApi() {
Criteria criteria = Criteria
.where("status").is("READY").ignoreCase(true)
.and("created_at").between(OffsetDateTime.now().minusDays(2), OffsetDateTime.now().plusDays(2))
.and("name").like("%J%");
Query query = Query.query(criteria);
Page<User> allByQuery = repository.findAll(query, User.class, Pageable.unpaged());
Assertions.assertThat(allByQuery.getTotalElements()).isEqualTo(3);
}
Надеюсь, здесь все понятно. Код довольно легко читаемый, вот какой SQL мы сгенерируем:
SELECT
"users"."id" AS "id",
"users"."name" AS "name",
"users"."status" AS "status",
"users"."created_at" AS "created_at"
FROM
"users"
WHERE
UPPER("users"."status") = UPPER(?)
AND "users".created_at BETWEEN ?
AND ?
AND "users"."name" LIKE ?
2. Динамическое построение структуры запроса
Теперь, есть другая часть той же Criteria API - это уже динамическое построение самой структуры запроса. Частично с этим помогает
CriteriaQuery
из Criteria API. Это все возможные группировки, формирование селект листа, формирование подзапросов и т.п. Вот этого в Spring Data JDBC нет и в ближайшее время не планируется. Это огромный пласт работы, который предстоит сделать, если, конечно, это кому-то нужно. Сейчас там конь не валялся по этому вопросу.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍10❤5
Audio
Оказывается, нейросеть Suno не только умеет сочинять классные песни, но и отлично разбирается в программировании! На запрос "The best programming language" она написала песню про Java 🥰
Как перестать подпевать? 😂
java-java is super cool
java-java not just a tool
java here and java there
java language is superb
Please open Telegram to view this post
VIEW IN TELEGRAM
❤17🔥13😁9
Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!
Spring Tips: Аннотация @Lazy – разобрались, как лениво инициализировать бины, и обсудили, стоит ли вообще это делать
Scoped Values в Java (Часть 1) – продолжили изучать нововведения Java, которые появятся в нашем любимом языке программирования в ближайшее время
Flyway + Spring Boot: настройка и написание миграций баз данных – узнали, как можно быстро и просто писать скрипты миграции баз данных
#ВопросЭксперту: Есть ли аналог Criteria API в Spring Data JDBC? – запустили новую рубрику, в первом выпуске которой дебютировал Михаил Поливаха
Java - лучший язык программирования!? – нашли еще один аргумент в пользу Java для спора о лучшем языке программирования
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤3👍1
Валидация входных данных — важная часть любой системы, обеспечивающая целостность данных и предотвращение ошибок. Иногда стандартные аннотации Jakarta Bean Validation (например,
@NotNull
, @Size
, @Pattern
) не покрывают все наши потребности, и нам приходится писать свои собственные валидаторы. В этом посте мы разберем два подхода к созданию кастомных валидаторов в Spring Boot, а также узнаем, как настроить отображение ошибки валидации.1. Кастомные валидации с мета-аннотациями
Если стандартных аннотаций не хватает, можно создать свою собственную аннотацию для валидации. Это удобно, если вам нужно сочетать несколько стандартных аннотаций в одной или переиспользовать уже имеющиеся аннотации. Например, давайте создадим аннотацию
@CardNumber
, которая проверяет, соответствует ли строка формату номера кредитной карты.
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Pattern(regexp = "([0-9]{4}-){3}[0-9]{4}$", message = "Invalid credit card number")
@Constraint(validatedBy = {})
public @interface CardNumber {
String message() default "Invalid credit card number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
В этом примере аннотация
@CardNumber
использует @Pattern
для проверки формата номера кредитной карты. Такой подход упрощает код, потому что вся логика валидации сконцентрирована в одной аннотации.
@PostMapping("/card")
void checkCardNumber(@RequestParam @Valid @CardNumber String cardNumber) {
}
@Test
public void validCardNumber() throws Exception {
mockMvc.perform(post("/card")
.param("cardNumber", "1111-1111-1111-1111"))
.andExpect(status().isOk())
.andDo(print());
}
@Test
public void invalidCardNumber() throws Exception {
mockMvc.perform(post("/card")
.param("cardNumber", "1111-1111-1111"))
.andExpect(status().is(400))
.andDo(print());
}
2. Реализация собственных валидаторов
Когда стандартные аннотации не подходят, можно создать собственные валидаторы. Для этого нужно реализовать интерфейс
ConstraintValidator
.Предположим, нам нужно валидировать пароли с определенными требованиями. Для этого создадим аннотацию
@ValidPassword
и валидатор PasswordValidator
.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface ValidPassword {
String message() default "Invalid password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int minLength() default 8;
String specialChars() default "!@#$%^&*()";
}
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private int minLength;
private String specialChars;
@Override
public void initialize(ValidPassword constraintAnnotation) {
this.minLength = constraintAnnotation.minLength();
this.specialChars = constraintAnnotation.specialChars();
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null) {
return false;
}
boolean hasUpperCase = !password.equals(password.toLowerCase());
boolean hasLowerCase = !password.equals(password.toUpperCase());
boolean hasDigit = password.chars().anyMatch(Character::isDigit);
boolean hasSpecialChar = password.chars().anyMatch(ch -> specialChars.indexOf(ch) >= 0);
boolean isLongEnough = password.length() >= minLength;
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar && isLongEnough;
}
}
В данном примере аннотация
@ValidPassword
проверяет, что пароль содержит и верхний, и нижний регистр, цифры, специальные символы и имеет достаточную длину. Логика валидации инкапсулирована в PasswordValidator
.Продолжение в комментариях 👇
#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31❤7🔥4
Бывало ли у вас такое, что тест падает на первом же assertion'e из десяти? Вы исправляете ошибку, запускаете тест снова, и он падает на втором assertion'e. И так десять раз. Выматывает, не так ли?
На самом деле, есть способ ускорить этот процесс — использовать soft assertions. С их помощью тест выполнится полностью, даже если один или несколько assertion'ов упадут, и вы сразу увидите все ошибки.
В новой статье от Михаила Поливахи, эксперта сообщества Spring АйО, вы узнаете, что такое soft assertions и как ими пользоваться.
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤9🔥7
Часто в файле
application.properties
(или application.yml)
объявляются свойства, значения которых содержат переменные вида ${ENV_PROPERTY_KEY:defaultValue}
:
spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:localhost}/${POSTGRES_DB_NAME:local_dev_db}
spring.datasource.username=${POSTGRES_USERNAME:root}
spring.datasource.password=${POSTGRES_PASSWORD:root}
spring.datasource.driver-class-name=org.postgresql.Driver
Такой подход позволяет переопределять свойства при запуске приложения в разных средах (local, test, prod и т.д.).
Например, если мы решили запустить наше приложение через Docker Compose, предварительно собрав его в Docker образ, то передача значений для объявленных нами переменных окружения будет выглядеть следующим образом:
spring_test_app:
image: spring_test_app:latest
build:
context: .
dockerfile: docker/Dockerfile
args:
DOCKER_BUILDKIT: 1
restart: "no"
# задаём конкретные значения тем самым переменным окружения
environment:
POSTGRES_HOST: postgres:5432
POSTGRES_DB_NAME: test_stand_db
POSTGRES_USERNAME: admin
POSTGRES_PASSWORD: admin
ports:
- "8080:8080"
В этом примере переменные из
application.properties
переопределяются через переменные окружения при запуске Docker контейнера.Переопределение свойств напрямую
Но на самом деле, можно обойтись без дополнительных переменных в
application.properties
, переопределив Spring-свойства напрямую.Теперь
application.properties
выглядит следующим образом:
spring.datasource.url=jdbc:postgresql://localhost:5432/local_dev_db
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.postgresql.Driver
А код сервиса нашего приложения в Docker Compose следующим образом:
spring_test_app:
image: spring_test_app:latest
build:
context: .
dockerfile: docker/Dockerfile
args:
DOCKER_BUILDKIT: 1
restart: "no"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/test_stand_db
SPRING_DATASOURCE_USERNAME: admin
SPRING_DATASOURCE_PASSWORD: admin
ports:
- "8080:8080"
Для
spring.datasource.url
, spring.datasource.username
и spring.datasource.password
будут использованы те значения, которые мы указали в Docker Compose файле. За счёт чего это становится возможным?
Spring Boot использует определенный порядок загрузки свойств приложения, чтобы обеспечить разумное переопределение значений. Переменные окружения загружаются позже и перезаписывают свойства, заданные в
application.properties
. Полный порядок загрузки можно найти в документации.Relaxed Binding в Spring Boot
Кстати, обратите внимание на стиль написания названий свойств. Spring Boot использует концепцию Relaxed Binding, которая позволяет указать название свойства без полного совпадения. Например:
@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}
Для этого класса можно использовать следующие стили именования свойств:
-
my.main-project.person.first-name
— Kebab стиль для .properties
/.yaml
файлов-
my.main-project.person.firstName
— CamelCase стиль-
my.main-project.person.first_name
— Underscore стиль-
MY_MAINPROJECT_PERSON_FIRSTNAME
— Uppercase + Underscore стиль для переменных окружения. При использовании Uppercase + Underscore стиля следует учитывать, что многие операционные системы ограничивают имена переменных окружения. Например, в Linux переменные могут содержать только буквы (a
-z
, A
-Z
), цифры (0
-9
) и символ подчеркивания (_
). Подробнее читайте в документации.В приведённом выше примере с Docker Compose файлом мы как раз воспользовались Uppercase + Underscore стилем, чтобы указать значения для нужных нам свойств. Именно такой вариант именования переменных окружения является негласным для Linux.
Ставь 🔥 если знал про это и 🤔 если слышишь впервые)
#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥37🤔24👍12❤1
В новом переводе от команды Spring АйО вы узнаете, как можно сгенерировать код HTTP клиентов для Spring Boot приложения по OpenAPI спецификации, используя плагин openapi-generator для Gradle.
В статье вы найдете:
💡 Пошаговую инструкцию по использованию openapi-generator для Gradle
⚙️ Настройки для генератора, которые помогут оставить только нужный код
🍃 Пример конфигурации сгенерированных Spring-бинов
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍10❤4🤔1
Нашли интересный репозиторий
– 18 статей по ключевым концепциям проектирования систем
– 26 статей по основным блокам проектирования систем
– Разбор 50 задач по проектированию на YouTube, разбитых по уровню сложности
– Ответы на вопросы по проектированию системы на интервью
– Ссылки на статьи, книги и каналы YouTube
🔗 https://github.com/ashishps1/awesome-system-design-resources
Сохраняйте, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥35👍14❤4
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Media is too big
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
🔥23😁16❤7👍3
Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!
– Кастомные валидаторы в Spring Boot: Как сделать валидацию под себя – вспомнили основные нюансы реализации собственных аннотаций и валидаторов
– Soft Assertions в AssertJ – Михаил Поливаха (эксперт сообщества Spring АйО) объяснил, как и в каких ситуации стоит использовать soft assertions
– Spring Boot Tips: Переопределение свойств через переменные окружения – узнали, как переопределять свойства через переменные окружения без их явного указания в application.properties
– Генерация HTTP клиентов для Spring Boot приложения по OpenAPI спецификации – выяснили, как можно сгенерировать код HTTP-клиентов и доработать его при необходимости
– Awesome System Design – нашли отличный репозиторий с материалами по системному дизайну на GitHub
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥5
Основное, на что стоит обратить внимание:
* Переход на Jakarta Persistence версии 3.2
* Более строгая проверка доменной модели
* Новая схема mapping.xsd c расширенными возможностями маппинга
* Переход на Hibernate Models с Hibernate Commons Annotations (HCANN)
#spring_news #hibernate
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍7🤯2