Java for Beginner
672 subscribers
541 photos
155 videos
12 files
827 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Типизация и ограничения

Нельзя привести boolean к int или обратно. Даже явно: (int) true — ошибка.
Нельзя использовать boolean в качестве индекса массива или как аргумент методов, ожидающих число.
Нет Boolean.parseInt(), как у числовых типов — только Boolean.parseBoolean() с String.


Упаковка и объектный аналог

Для работы с boolean как объектом существует класс-обертка Boolean. Он используется при работе с коллекциями (List<Boolean>) или API, которые требуют объектов.

Как и другие обертки, Boolean поддерживает автоупаковку и автораспаковку:
Boolean obj = true;     // упаковка
boolean val = obj; // распаковка
Также есть кэширование значений: Boolean.TRUE и Boolean.FALSE — это единственные экземпляры класса Boolean, которые обычно и используются.


Подводные камни и особенности

Нет арифметики. Переход с языков вроде C, где true — это 1, а false — это 0, может вызвать ошибки.
Java не допускает смешивания логики и чисел.
Отсутствие побитовых операций. Нельзя использовать & или | на boolean как побитовые операторы. Да, есть &, | и ^, но они действуют как логические, без короткого замыкания — в отличие от && и ||.
Нет поддержки масок и битов. Если требуется управлять отдельными битами, используют int или byte, а не boolean[].
Выражения с boolean всегда проверяются как логические. Даже if (a & b) — это проверка boolean, а не побитовая операция над числами.
Интерфейсы и структуры не позволяют оптимизировать boolean до одного бита. Например, boolean[] всегда тратит 1 байт (или больше) на элемент, а не один бит, как хотелось бы при экономии памяти. Для работы с компактными флагами используют BitSet или битовые маски на базе int.


#Java #для_новичков #beginner #boolean
Откаты (Rollback) и теги в Liquibase

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

1. Типы откатов

1.1. Автоматический откат (на основе Liquibase)
Liquibase может автоматически генерировать команды отката для некоторых операций, если они указаны в changeSet.

Пример (YAML):
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: INT
constraints:
primaryKey: true
- rollback:
dropTable:
tableName: users


Как это работает?
При выполнении liquibase update применяется createTable.
При откате (rollback) выполнится dropTable.

Какие операции поддерживают автоматический откат?
createTable → dropTable
addColumn → dropColumn
createIndex → dropIndex


Ограничения:
Не все операции имеют автоматический откат (например, сложные ALTER TABLE).
Для них требуется ручной rollback.


1.2. Ручной откат (пользовательские SQL-скрипты)
Если Liquibase не может сгенерировать откат автоматически, его можно задать вручную.

Пример (YAML):
- changeSet:
id: 2
author: dev
changes:
- sql:
sql: "ALTER TABLE users ADD COLUMN last_login TIMESTAMP"
- rollback:
sql:
sql: "ALTER TABLE users DROP COLUMN last_login"


Когда использовать?
Для сложных SQL-запросов.
Если Liquibase не поддерживает автоматический откат для операции.


2. Теги (Tags)

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

2.1. Создание тега (tag)
Пометка текущего состояния БД.

Команда:
liquibase tag v1.0
или через changelog:

yaml
- changeSet:
id: 3
author: dev
changes:
- tagDatabase:
tag: v1.0


Где хранится информация о тегах?
В таблице DATABASECHANGELOG (поле TAG).

2.2. Откат к тегу (rollback <tag>)
Возврат базы данных к состоянию на момент создания тега.

Команда:
liquibase rollback v1.0


Как это работает?
Liquibase находит тег v1.0 в DATABASECHANGELOG.
Определяет, какие changeSet’ы были выполнены после этого тега.
Применяет откаты в обратном порядке.


Пример (YAML):
- changeSet:
id: 4
author: dev
changes:
- tagDatabase:
tag: v1.1


3. Практические сценарии отката

3.1. Отмена последнего changeSet’а
liquibase rollbackCount 1


Что произойдёт?
Liquibase откатит один последний применённый changeSet.


3.2. Восстановление до определённой версии

Вариант 1: По тегу
liquibase rollback v1.0


Вариант 2: По дате
liquibase rollbackToDate 2024-03-15


Вариант 3: По количеству changeSet’ов
liquibase rollbackCount 3  # Откатит 3 последних изменения


#Java #middle #Liquibase
4. Важные нюансы

Откат не всегда возможен
Если changeSet не содержит rollback, Liquibase не сможет его отменить автоматически.

(Пример: изменение данных (INSERT/UPDATE) требует ручного rollback.)

Теги vs. Версии
Теги — это произвольные метки (например, v1.0, prod-release).
Версии — это последовательные номера changeSet’ов.


Безопасность откатов
Всегда тестируйте rollback в dev-среде, прежде чем применять в production.
Используйте preConditions, чтобы избежать ошибок.


5. Пример полного сценария

1. Изначальный changelog:

databaseChangeLog:
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: INT
constraints:
primaryKey: true
- rollback:
dropTable:
tableName: users

- changeSet:
id: 2
author: dev
changes:
- addColumn:
tableName: users
column:
name: username
type: VARCHAR(50)
- rollback:
dropColumn:
tableName: users
columnName: username

- changeSet:
id: 3
author: dev
changes:
- tagDatabase:
tag: v1.0


2. Применяем изменения:
liquibase update


3. Откатываемся до тега v1.0:
liquibase rollback v1.0


Результат:
Будет отменён changeSet id=2 (удалён столбец username).
Таблица users останется (так как changeSet id=1 был до тега).


#Java #middle #Liquibase
Пагинация, которую начинаешь ненавидеть 😵

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

Давайте представим, что есть связанные сущности(код условный, никакого реального совпадения):
@Entity
@Data
public class Employee {
@Id
private Long id;
private String name;

@OneToOne
@JoinColumn(name = "workstation_id")
private Workstation workstation;

@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}

@Entity
@Data
public class Workstation {
@Id
private Long id;
private String number; // Поле для сортировки
}

@Entity
@Data
public class Department {
@Id
private Long id;
private String name; // Поле для сортировки
}


И есть бизнес-задача: Провести сортировку (SortBy в Pageable) по полям вложенных сущностей (т.е. по workstation.number и department.name).


🙅‍♂️ Как хочется сделать

Как минимум все по стандарту:
@Query("SELECT e FROM Employee e WHERE e.name = :name")
Page<Employee> findByName(@Param("name") String name, Pageable pageable)

Естественно поля могут передаваться таким образом:
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("workstation.number"));

или

Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("department.name"));


И выглядит вроде бы все пристойно.

Но! Как всегда в магии Spring'а есть НО ✌️

Для связи @OneToOne (Workstation.number) сортировка работает, потому что это прямое простое соединение (join), и JPA может легко построить корректный SQL-запрос:
SELECT e.* FROM employee e
JOIN workstation w ON e.workstation_id = w.id
WHERE e.name = 'John'
ORDER BY w.number ASC -- Сортировка применяется


❗️ Но, для связи @OneToMany или @ManyToOne (department.name) сортировка не работает❗️

Потому что:

Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
JPA/Hibernate не может автоматически разрешить, как именно нужно применять сортировку в таком случае и не делает НИЧЕГО
🙄


🆗 Как надо сделать

Явный JOIN + ORDER BY в JPQL
@Query("SELECT e FROM Employee e " +
"LEFT JOIN e.department d " + // Явное соединение
"WHERE e.name = :name " +
"ORDER BY d.name") // Сортировка в запросе(но не обязательно)
Page<Employee> findByNameWithDepartmentSort(
@Param("name") String name,
Pageable pageable);


Явное указание, как соединять таблицы и ORDER BY до пагинации как минимум решат все проблемы 🏝

Использование JOIN FETCH (если нужны данные сразу)
@Query("SELECT DISTINCT e FROM Employee e " +
"LEFT JOIN FETCH e.department d " + // Загружаем department сразу
"WHERE e.name = :name " +
"ORDER BY d.name")
Page<Employee> findByNameWithDepartmentFetch(
@Param("name") String name,
Pageable pageable);


Тот же вариант что и выше, но если не жалко памяти 🆘

Specification API
public interface EmployeeRepository extends 
JpaRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> { }

// В сервисе:
Specification<Employee> spec = (root, query, cb) -> {
Join<Employee, Department> department = root.join("department");
return cb.equal(root.get("name"), name);
};

Sort sort = Sort.by("department.name").ascending();
Pageable pageable = PageRequest.of(0, 10, sort);

Page<Employee> result = repository.findAll(spec, pageable);


Вариант для любителей пожесче
🧑‍💻


Как не сойти с ума при поиске подобных ошибок?
🤪
Всегда, на стадии разработки и тестирования включайте SQL в логах (spring.jpa.show-sql=true) и будет Вам счастие.
Помните - если сортировка не применяется – валите всё на JOIN.



Понравился стиль подачи материала?

Отправь другу и ставь -
🔥
Если бывало такое - ❤️

#Java #join #autor
Please open Telegram to view this post
VIEW IN TELEGRAM
Ссылочные типы данных в Java

Ссылочные типы данных (reference types) являются неотъемлемой частью объектно-ориентированной модели Java. В отличие от примитивов, они представляют объекты и хранят ссылки (адреса) на область памяти, где находится фактическое содержимое.

1. Концепция и философия


Java — строго объектно-ориентированный язык, и ссылки — основа его динамической природы.

Зачем нужны ссылочные типы:

Инкапсуляция данных и поведения. Объекты объединяют данные и методы, действующие над ними.
Гибкость. Ссылочные типы позволяют моделировать сложные структуры (например, графы, деревья, коллекции).
Полиморфизм и наследование. Через ссылки возможна работа с объектами по интерфейсам и абстрактным типам.


List<String> names = new ArrayList<>(); // ссылка на объект ArrayList


2. Представление и поведение ссылок

Ссылочная переменная не содержит сам объект, а только указатель на него.

String a = "Hello";
String b = a;
Обе переменные a и b ссылаются на один и тот же объект "Hello" в куче (heap). Изменение объекта (если он мутабелен) через одну ссылку отразится на другой.


Пример с мутабельным объектом:
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = list1;
list1.add(10);
System.out.println(list2); // [10]


3. Классы, интерфейсы и массивы

К ссылочным типам относятся:

Классы (например, String, Object, Scanner, ArrayList)
Интерфейсы (например, List, Runnable, Serializable)
Массивы (int[], String[], Object[][]) — в
Java массивы — это объекты!
Специальный тип null — означает отсутствие объекта


4. Работа JVM с ссылками

При работе со ссылочными типами:
Объекты размещаются в куче (heap).
Ссылки могут храниться в стеке вызовов, в полях объектов, в массиве и т.д.
JVM использует сборку мусора (GC) для очистки неиспользуемых объектов.


class User {
String name;
}

User user1 = new User();
User user2 = user1;
user1 = null; // объект всё ещё доступен через user2


5. Особенности ссылочных типов

a) Сравнение ссылок
String a = new String("Java");
String b = new String("Java");
System.out.println(a == b); // false — разные объекты
System.out.println(a.equals(b)); // true — сравнение содержимого
== сравнивает ссылки, а equals() — содержимое (если метод переопределён корректно).


b) NullPointerException
String s = null;
System.out.println(s.length()); // исключение
Поэтому рекомендуется использовать Objects.requireNonNull, Optional, или делать явные проверки на null.


6. Автоматическая работа с памятью


Одно из главных преимуществ Java — автоматическое управление памятью. Не нужно вручную освобождать объекты, как в C/C++.

Но важно помнить:
Объекты "висят" в памяти, пока есть активные ссылки.
Циклические ссылки не мешают сборке мусора (в отличие от простых reference counting-систем).


7. Массивы как ссылочные типы

Массивы в Java — полноценные объекты.

int[] nums = {1, 2, 3};
System.out.println(nums.length); // 3
Массивы имеют поля и методы (length — поле, а не метод), и их тип — int[], String[] и т.д.


8. Использование в обобщениях

Ссылочные типы активно применяются в дженериках (обобщениях).

List<Integer> list = new ArrayList<>();


Нельзя использовать примитивы в параметрах типа:
List<int> wrongList; // ошибка
Поэтому применяются классы-обёртки: Integer, Double, Boolean и т.д.


9. Влияние на производительность

Работа со ссылками может быть дороже, чем с примитивами:
Дополнительные обращения к памяти
Участие в GC
Наличие виртуального вызова методов (vtable)
Частые создания объектов (например, при упаковке/распаковке)


Оптимизации:
Избегать ненужных аллокаций
Использовать StringBuilder вместо + в циклах
Применять ObjectPool, если объекты часто переиспользуются


#Java #для_новичков #beginner #reference_types
Интеграция Liquibase с другими инструментами

Maven / Gradle

Maven

Добавьте зависимость в pom.xml:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.25.0</version>
</dependency>


Конфигурация через pom.xml:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>4.25.0</version>
<configuration>
<changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<username>user</username>
<password>pass</password>
</configuration>
</plugin>


Запуск миграций:
mvn liquibase:update


Gradle

Добавьте в build.gradle:
plugins {
id 'org.liquibase.gradle' version '2.2.0'
}

dependencies {
liquibaseRuntime 'org.liquibase:liquibase-core:4.25.0'
liquibaseRuntime 'org.postgresql:postgresql:42.6.0'
}

liquibase {
changeLogFile 'src/main/resources/db/changelog/db.changelog-master.yaml'
url 'jdbc:postgresql://localhost:5432/mydb'
username 'user'
password 'pass'
}


Запуск:
gradle liquibaseUpdate


Spring Boot (автоконфигурация)

Spring Boot автоматически настраивает Liquibase, если он обнаружен в classpath.

Настройки в application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: pass
liquibase:
change-log: classpath:db/changelog/db.changelog-master.yaml
enabled: true
contexts: dev # Фильтрация по контексту


Поведение:
Миграции запускаются при старте приложения.
Можно отключить через spring.liquibase.enabled=false.


Расширенные функции Liquibase

Динамические свойства (property)

Позволяют выносить повторяющиеся значения в переменные.

Пример (YAML):
databaseChangeLog:
- property:
name: default.schema
value: public
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: ${default.schema}.users
columns:
- column:
name: id
type: INT


В
liquibase.properties:
default.schema=public


Через командную строку:
liquibase update -Ddefault.schema=public


Контексты (contexts) и фильтрация changeSet’ов

Контексты позволяют выбирать, какие changeSet’ы применять в определённых окружениях.

Пример (YAML):
- changeSet:
id: 2
author: dev
context: "dev,test" # Будет выполнен только в dev и test
changes:
- insert:
tableName: users
columns:
- column:
name: username
value: "test_user"

- changeSet:
id: 3
author: dev
context: "prod" # Только в production
changes:
- createIndex:
tableName: users
indexName: idx_user_email
columns:
- column:
name: email


Как передать контекст?

Через командную строку:
liquibase update --contexts="dev"


В Spring Boot:
spring:
liquibase:
contexts: dev


#Java #middle #Liquibase
Best Practices

Организация changelog-файлов

Рекомендуемая структура:
src/main/resources/db/changelog/
├── db.changelog-master.yaml # Главный файл
├── changes/
│ ├── 001-create-tables.yaml
│ ├── 002-add-indexes.yaml
│ └── 003-insert-data.yaml
└── rollback/
├── 001-rollback.yaml # Ручные откаты


Главный файл (db.changelog-master.yaml):
databaseChangeLog:
- includeAll:
path: db/changelog/changes/
relativeToChangelogFile: true


Почему так?

Удобно управлять версиями.
Легко находить изменения.


Тестирование миграций

Стратегии:
Локальная проверка:
liquibase validate  # Проверка синтаксиса
liquibase update # Применение в test-DB


Интеграционные тесты (JUnit + Testcontainers):
@Testcontainers
@SpringBootTest
class LiquibaseMigrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

@Test
void testMigrations() {
// Spring Boot автоматически применит миграции
assertTrue(true); # Если не упало — значит, успешно
}
}


Работа в команде (избежание конфликтов)


Правила:

Именование changeSet’ов:
Используйте id: <дата>-<номер> (например, id: 20240315-1).
Указывайте автора (author: github_username).


Порядок изменений:
Не зависеть от порядка выполнения (например, не предполагать, что таблица A уже существует при создании B).

Использование preConditions:
- changeSet:
id: 20240315-1
author: dev
preConditions:
- tableExists:
tableName: users
changes:
- addColumn:
tableName: users
column:
name: phone
type: VARCHAR(20)


Регулярные обновления:
Перед началом работы выполняйте liquibase update, чтобы получить актуальную схему.

#Java #middle #Liquibase
Настройка OAuth2 для Gmail

Для Gmail предпочтительно использовать OAuth2.

Пример конфигурации:
@Configuration
public class MailConfig {

@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername("your.email@gmail.com");

Properties props = mailSender.getJavaMailProperties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");

// Настройка OAuth2 токена через Google API Client Library
// Подробности: https://developers.google.com/identity/protocols/oauth2
return mailSender;
}
}


Примечание: Зарегистрируйте приложение в Google Cloud Console и используйте библиотеку google-auth-library-oauth2-http для получения accessToken.

Создание сервиса отправки почты

Создадим сервис EmailService для отправки писем.

@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private TemplateEngine templateEngine;

private static final Logger logger = LoggerFactory.getLogger(EmailService.class);

public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}

public void sendHtmlEmail(String to, String subject, String htmlBody) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}

public void sendEmailWithAttachment(String to, String subject, String text, File attachment) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(text);
FileSystemResource file = new FileSystemResource(attachment);
helper.addAttachment(attachment.getName(), file);
mailSender.send(message);
}

@Async
public CompletableFuture<Void> sendSimpleEmailAsync(String to, String subject, String text IMPORTANT: text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
message.setText(text);
mailSender.send(message);
return CompletableFuture.completedFuture(null);
}

public void sendTemplatedEmail(String to, String subject, String username) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
Context context = new Context();
context.setVariable("username", username);
String htmlBody = templateEngine.process("email-template", context);
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}
}


Конфигурация асинхронности:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
}


#Java #middle #on_request #JavaMailSender
Примеры кода

Простой текст:
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("user@example.com");
message.setSubject("Тестовое письмо");
message.setText("Привет из Spring Boot!");
mailSender.send(message);


HTML-письмо:
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("HTML письмо", "UTF-8", null));
helper.setText("<h1>Привет!</h1><p>Это HTML письмо.</p>", true);
mailSender.send(message);


Вложение:

File file = new File("/path/to/file.pdf");
FileSystemResource resource = new FileSystemResource(file);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("Письмо с вложением", "UTF-8", null));
helper.setText("Смотри вложение!");
helper.addAttachment("file.pdf", resource);
mailSender.send(message);


Обработка ошибок и логирование

JavaMailSender может выбрасывать исключения, которые нужно обрабатывать.

Основные исключения:
MailAuthenticationException
MailSendException
MessagingException


Пример отлова:
try {
mailSender.send(message);
} catch (MailException e) {
logger.error("Ошибка отправки письма", e);
throw e;
}


Повторные попытки
Используйте @Retryable из Spring Retry:
@Retryable(value = MailException.class, maxAttempts = 3)
public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}


Зависимость:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>


Мониторинг и метрики

Для продакшен-приложений полезно отслеживать метрики отправки писем с помощью Spring Actuator:
@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private MeterRegistry meterRegistry;

public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
try {
mailSender.send(message);
meterRegistry.counter("email.sent.success").increment();
} catch (MailException e) {
meterRegistry.counter("email.sent.failure").increment();
throw e;
}
}
}


Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>


Безопасность и защита данных

Никогда не храните пароли в открытом виде в git-репозиториях.

Рекомендации:
Используйте переменные окружения или секреты (например, Kubernetes Secrets).
Применяйте Spring Cloud Vault или HashiCorp Vault.
Используйте шифрование с Jasypt.
Настройте OAuth2 для Gmail.


Пример с переменными окружения:
spring.config.import=optional:configserver:http://config-server
spring.mail.password=${MAIL_PASSWORD}


#Java #middle #on_request #JavaMailSender
Тестирование отправки почты

Интеграционные тесты с Testcontainers:
@Testcontainers
@SpringBootTest
public class EmailServiceTest {

@Container
private static final GreenMailContainer greenMail = new GreenMailContainer();

@Autowired
private EmailService emailService;

@Test
void shouldSendEmail() {
emailService.sendSimpleEmail("test@example.com", "Test Subject", "Test Body");
assertEquals(1, greenMail.getReceivedMessages().length);
}
}


Зависимость:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>greenmail</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>


Unit-тестирование:
@MockBean
private JavaMailSender mailSender;

@Test
void shouldSendMail() {
SimpleMailMessage message = new SimpleMailMessage();
emailService.sendSimpleEmail("to", "subject", "body");
verify(mailSender, times(1)).send(any(SimpleMailMessage.class));
}


Интеграция с очередями

Для отложенной отправки используйте RabbitMQ(ну или другой брокер сообщений):

@Service
public class EmailService {

@Autowired
private JavaMailSender mailSender;

@Autowired
private RabbitTemplate rabbitTemplate;

public void sendEmailToQueue(String to, String subject, String text) {
EmailMessage email = new EmailMessage(to, subject, text);
rabbitTemplate.convertAndSend("emailQueue", email);
}

@RabbitListener(queues = "emailQueue")
public void processEmail(EmailMessage email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email.getTo());
message.setSubject(email.getSubject());
message.setText(email.getText());
mailSender.send(message);
}

@Data
static class EmailMessage {
private String to;
private String subject;
private String text;
}
}


Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>


Распространенные ошибки и подводные камни

Неверная конфигурация SMTP (порт, шифрование).
Проблемы с двухфакторной авторизацией (особенно Gmail).
Ограничения на массовую рассылку (например, Gmail: ~500 писем/день для бесплатных аккаунтов).
Проблемы с кодировкой (например, кириллица в теме письма).
Отсутствие MIME-типа у вложений.
Блокировка сервером (например,
Mail.ru требует DKIM и SPF).

Решение для кодировки:
helper.setSubject(MimeUtility.encodeText("Тема на кириллице", "UTF-8", null));


#Java #middle #on_request #JavaMailSender
Объекты в Java

Объекты являются фундаментальным понятием в Java и лежат в основе всей объектно-ориентированной парадигмы языка. Каждый объект представляет собой экземпляр класса и объединяет в себе состояние (поля) и поведение (методы).

1. Введение

В Java всё, что не является примитивным типом, относится к ссылочным типам, а основные единицы этих типов — это объекты.

Объект-ориентированная модель основана на трёх ключевых принципах:
Инкапсуляция: объединение данных и методов для управления ими.
Наследование: возможность создавать новые классы на основе уже существующих, унаследовав их состояние и поведение.
Полиморфизм: возможность работать с объектами через их общий (абстрактный) тип, подставляя разные конкретные реализации.


Вся динамика и гибкость Java-приложений строится на том, что объекты создаются во время выполнения программы, передаются по ссылке, могут образовывать сложные графы связей и автоматически удаляться сборщиком мусора.

2. Создание объектов

2.1. Ключевое слово new

Самый распространённый способ создания объекта — использование оператора new, который выполняет два основных действия:
Выделяет память в куче (heap) под новый объект.
Вызывает конструктор соответствующего класса, чтобы инициализировать поля объекта.


Person person1 = new Person("John", 25);


Здесь:
new Person("John", 25) — порождает новый объект класса Person, вызывая конструктор Person(String name, int age).
Полученная ссылка на вновь созданный объект присваивается переменной person1 типа Person.
Переменная person1 не содержит непосредственно самого объекта, а лишь указывает на область памяти, где объект расположен.


2.2. Фабричные методы и другие способы

Кроме new, объекты могут создаваться через:
Фабричные методы (static factory methods), например, List.of(...) или Optional.of(...).
Клонирование (когда класс поддерживает интерфейс Cloneable и реализует метод clone()).
Десериализация (с помощью API сериализации или при работе с JSON/XML).
Рефлексия (пересоздание экземпляра через Class.newInstance() или Constructor.newInstance()).

Однако в подавляющем большинстве случаев для явного создания объекта используется именно new.


2.3. Размещение ссылок и объектов

Объекты всегда размещаются в куче (heap).

Ссылочные переменные (person1 в примере) могут храниться:
В стеке вызовов (если это локальная переменная метода).
В полях других объектов (если объект содержится в качестве поля другого объекта).
В элементах массива (если это массив ссылок).
В статических полях классов (в специальной области памяти, связанной с загрузчиком классов).


3. Существование и удаление объектов

3.1. Существование объектов

Объект продолжает «жить» в памяти до тех пор, пока на него существует хотя бы одна активная ссылка.


Пример:
Person person1 = new Person("John", 25);
Person person2 = person1; // person2 ссылается на тот же самый объект
person1 = null; // теперь единственная ссылка на объект — person2


В момент создания объекта через new в куче появляется новый экземпляр Person.
Переменные person1 и person2 указывают на одну и ту же область памяти.
Когда мы присвоили person1 = null;, объект всё ещё существует, поскольку на него ссылается person2.
Как только все ссылки будут убраны (например, person2 = null; или метод, в котором была локальная ссылка, завершится и стек «очистится»), объект становится недостижимым.

Java применяет алгоритм mark-and-sweep для сборки мусора, поэтому даже если два объекта ссылаются друг на друга, но на них никто извне не ссылается, они будут помечены как недостижимые и удалены.

#Java #для_новичков #beginner #reference_types #Object