πŸ“œ ⬆️ ⬇️

Spring Boot. Background tasks and not only

Introduction


In this tutorial, I want to give an example of an application for sending emails to users based on the date of their birth (for example, with congratulations), using the Scheduled annotation. I decided to give this example, in my opinion, it includes quite a few things, such as working with a database (in our case it is PostgreSQL), Spring Data JPA, new java 8 time api, email service, creating background tasks and small business logic while remaining compact. Today, the Internet is replete with a huge variety of tutorials that usually come down to how to inherit from CrudRepository, JpaRepository, and so on. Tutorial is designed for the fact that you have already watched at least some of them and have an idea of ​​what Spring Boot is. I will try to show an example of an application that shows its capabilities more extensively and how to work with it.

Project creation


Go to the Spring Initializr .

Add dependencies:
')
1. PosgreSQL - as a database
2. JPA - access to the database
3. Lombok - for convenience and getting rid of the boilerplate code (you do not have to write getters, setters, etc. by yourself), read more here
4. Mail - actually for work and sending emails, of. documentation

Specify the group and the artifact, for example, com.application and task. Download and unpack the project, then open it in the development environment, I have this Intellij IDEA.

Database


Now set yourself PostgreSQL . Next, create a database with user and password. You can do this directly from IDEA, in the database tab, you can use the command line if you have Linux, with the following commands:

sudo -u postgres createuser <username> sudo -u postgres createdb <dbname> $ sudo -u postgres psql psql=# alter user <username> with encrypted password '<password>'; psql=# grant all privileges on database <dbname> to <username> ; 

Also on windows this can be done using pgAdmin or its alternatives.

Start


We open our project and we can start writing the code.

Now we have only one java-file in the project. It looks like this:

 @SpringBootApplication public class TaskApplication { public static void main(String[] args) { SpringApplication.run(TaskApplication.class, args); } } 

The class name may be different depending on the artifact name you gave when creating the project.

This class is the launch point of the application. The @SpringBootApplication annotation means that this is a Spring Boot application and is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan.

Creating a model


First we divide the directory in which our class lies to run the entire application and divide it into three directories: model, repository, service.

Next in the model folder we create the User class:

 @Getter @Setter @ToString @NoArgsConstructor @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", nullable = false) private String name; @Email private String email; private LocalDate birthday; } 

So, we have created a class with the minimum number of fields that we need: user id, his name, email and date of birth.

Lets go over the annotations: The first 4 above the class are the lombok annotations that generate getters, setters, the toString method, and the constructor with no arguments.

Entity indicates to Hibernate that this class is an entity.
Table - the name corresponds to the table in the database.
Id - points to the primary key of this class.
@GeneratedValue - used with Id and defines the strategy and generator pameters.
@Column - indicates the name of the column that is displayed in the property of the entity, also using nullable = false, we indicate that the field is required.
Email - the string must be a valid email address (javax.validation.constraints package is used instead of org.hibernate.validator.constraints, that annotation is outdated in the latter).

Repository


Next in the repository folder we create the UserRepository interface:

 public interface UserRepository extends JpaRepository<User, Integer> {} 

Inheritance from JpaRepository gives us the opportunity to use its methods for working with databases such as delete, save, findAll and many others. In addition, if you wish, we can create our own methods, according to the principle β€œwe write what we need.” T e if we need to find all users with the same name, then our method will look like this:

 List<User> findAllByName(String name); 

This method will eventually create a SQL query like this:

 SELECT * FROM users WHERE name = ?; 

Or for example:

 List<User> findByBirthdayAfter(LocalDate date); 

Allows you to select all users born after a certain date.

In general, this is quite an extensive topic, on which quite a lot of articles and videos. Like this one .
It is also possible to write your sql queries using the JPA annotation Query directly above the method body.
There is an opportunity to use two types of syntax: JPQL (JPA query language, similar to SQL using instead of tables and columns β€” entities, attributes, etc.) or SQL itself used by us (then the nativeQuery = true property is added). Example with JPQL:

 @Query(value = "SELECT u from User u where u.name = ?1") List<User> findAllByName(String name); 

To specify the name of the request parameter, you can use the JPA @Param annotation:

 @Query(value = "SELECT u from User u where u.name = :name") List<User> findAllByName(@Param("name") String name); 

If we want to use pure SQL, then:

 @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) List<User> findAllByName(@Param("name") String name); 

We will create a method that will take from the database of all users whose email is not null, and in which the month and birthday will correspond to those that we will send there. Now our repository will look like this:

 @Repository public interface UserRepository extends JpaRepository<User, Integer> { @Query(value = "SELECT * FROM users " + "WHERE email IS NOT NULL " + "AND extract(MONTH FROM birthday) = :m " + "AND extract(DAY FROM birthday) = :d", nativeQuery = true) List<User> findByMatchMonthAndMatchDay(@Param("m") int month, @Param("d") int day); } 

A couple of repository features

The first parameter of the generic should be the entity with which we will work, and the second corresponds to the type of its primary key.
Also, the types of methods must correspond to the first parameter (if you do not use your own mapping).
If suddenly you have a question, why this directory is called repository, and not dao, then this is a good tone rule in Spring Boot, you are not obliged to do the same, just so accepted.

Services


First, create the UserRepositoryService interface in the service directory:

 public interface UserRepositoryService { List<User> getAll(int month, int day); } 

Further, here we create another impl directory and in it an implementation class for our service:

 @Service public class UserRepositoryServiceImpl implements UserRepositoryService { private final UserRepository repository; @Autowired public UserRepositoryServiceImpl(UserRepository repository) { this.repository = repository; } @Override public List<User> getAll(int month, int day) { return repository.findByMatchMonthAndMatchDay(int month, int day); } } 

Now let's look at our class:
Annotation Service shows the spring that this is a service.
Next, we declare a variable of type UserRepository and initialize it in the constructor, having previously marked its annotations @Autowired.
(You can put annotations directly above the repository field, but it is preferable to create a constructor or setter)
@Autowired - the spring finds the desired bin and substitutes its value in the property marked with annotation.
It is possible to create an autowired constructor using the annotation of a chunk above the class:

 @RequiredArgsConstructor(onConstructor = @__(@Autowired)) 

After the constructor, we implement the method of our interface and in it return the method from the repository.
Go ahead: create the EmailService in the service directory:

 public interface EmailService { void send(String to, String title, String body); } 

And its implementation of EmailServiceImpl in impl:

 @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class EmailServiceImpl implements EmailService { private final JavaMailSender emailSender; @Override public void send(String to, String subject, String text) { MimeMessage message = this.emailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); try { helper.setTo(to); helper.setSubject(subject); helper.setText(text); this.emailSender.send(message); } catch (MessagingException messageException) { throw new RuntimeException(messageException); } } } 

I will not delve into the description, here OD .

Now in service we will create our last and main class with a scheduler and business logic, let's call it for example SchedulerService.

Immediately define the following fields in it:

 @Slf4j @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class SchedulerService { private final UserRepositoryService userService; private final EmailService emailService; } 

So, we initialized the logger (also the @ Slf4j breakup annotations), user and email services in the constructor (@RequiredArgsConstructor (onConstructor = @__ (@ Autowired))).

Next, create a void method sendMailToUsers and above it we specify an annotation:

 @Scheduled(cron = "*/10 * * * * *") 

This annotation allows you to specify when our method will work. We use the cron parameter, which allows you to specify a schedule for specific hours and dates. There are also parameters such as fixedRate (determines the interval between method calls), fixedDelay (determines the interval since the last method call ended and the next one started), initialDelay (the number of milliseconds to delay before the first execution of fixedRate or fixedDelay) and a couple more .

Each asterisk in the cron line means seconds, minutes, hours, days, months, and days of the week. Here are more details . Now the value means that the test will take place every 10 seconds, this is done for example, in the future we will change it.

For convenience, the cron value can be put into a constant:

 private static final String CRON = "*/10 * * * * *"; 

In the method, we will create a variable with the current date (java date and time api), variables for the month and day, which are taken from the date, a user list, which is initialized by a method from our service and checking if it is empty:

 @Scheduled(cron = CRON) public void sendMailToUsers() { LocalDate date = LocalDate.now(); int month = date.getMonthValue(); int day = date.getDayOfMonth(); List<User> list = userService.getUsersByBirthday(month, day); if (!list.isEmpty()) { } } 

Now let's go through it and for each user, create a variable for the message and call the send method from EmailService, and pass the user's email, header and our message to it. At the end we wrap everything in try / catch to avoid exceptions. Everything, our method is ready.

We look at the whole class:

 @Service @Slf4j @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class SchedulerService { private static final String CRON = "*/10 * * * * *"; private final UserRepositoryService userService; private final EmailService emailService; @Scheduled(cron = CRON) public void sendMailToUsers() { LocalDate date = LocalDate.now(); int month = date.getMonthValue(); int day = date.getDayOfMonth(); List<User> list = userService.getUsersByBirthday(month, day); if (!list.isEmpty()) { list.forEach(user -> { try { String message = "Happy Birthday dear " + user.getName() + "!"; emailService.send(user.getEmail(), "Happy Birthday!", message); log.info("Email have been sent. User id: {}, Date: {}", user.getId(), date); } catch (Exception e) { log.error("Email can't be sent.User's id: {}, Error: {}", user.getId(), e.getMessage()); log.error("Email can't be sent", e); } }); } } } 

Now, to be able to run the background tasks, add the @EnableScheduling annotation to our TaskApplication directly above the @SpringBootApplication so that it looks like this:

 @EnableScheduling @SpringBootApplication public class TaskApplication { public static void main(String[] args) { SpringApplication.run(TaskApplication.class, args); } } 

This completes the work with the java code, we only need to specify configs in the application.properties file in the resources directory.

Configuration


 #   .   ,      server.port=7373 #   spring.jpa.database=POSTGRESQL spring.jpa.show-sql=true spring.datasource.platform=postgres spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/your_database?stringtype=unspecified spring.datasource.username=your_database_username spring.datasource.password=your_database_password #  logging.level.org.hibernate=info logging.level.org.springframework.security=debug #      jpa  postgreSQL spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # email-a spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=your_email@gmail.com spring.mail.password=your_password spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true 

A couple of explanations:
 spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=update 

They are used to automatically create / update the table in the database, using our entity. (In production, it is better to change the values ​​to false and none)

 spring.datasource.url=jdbc:postgresql://localhost:5432/your_database?stringtype=unspecified spring.datasource.username=your_database_username spring.datasource.password=your_database_password 

Here you can see the name of your database, login and password.

 spring.mail.username=your_email@gmail.com spring.mail.password=your_password 

Your or test email and password from it. There may be errors in access to gmail, for this you just need to allow unreliable applications in the security and login tab in its settings.

Launch


We go to our TaskApplication and run the application. If everything is done correctly, then you should have similar logs every 10 seconds:

 Hibernate: select users0_.id as id1_0_, users0_.birthday as birthday2_0_, users0_.email as email3_0_, users0_.name as name4_0_ from users users0_ where (users0_.birthday is not null) and (users0_.email is not null) 

Meaning that our method at least takes a list of users from the database. Now, if we open our database (I do this directly in IDEA. In the database tab, usually in the upper right corner, we can connect to the database we need), we will see that there appeared a table users with the corresponding fields. Let's create a new record and, as a birthday, we will enter the current date, and as our own email-a. After committing changes, every 10 seconds our log should appear informing you that email has been successfully sent. We check email and if everything is done correctly, then there should be one or several birthday greetings (depending on how many times the method has been worked out). We stop our application and change the CRON value to β€œ0 0 10 * * *” which means that now the check will not take place every 10 seconds, but every day at 10 am, which guarantees us sending only one greeting.

Conclusion


Based on this example, you can create and solve a variety of tasks related, in particular, to background processes; the main thing is not to be afraid to experiment. I hope today I was able to help someone better understand how to work with Spring Boot, databases and java. If someone is interested, then I can write the second part of the article, with the addition of a controller (so that, for example, if you wish, you can turn off email sending) testing and security.

Constructive criticism and comments on the topic are welcome.
Special thanks for the comments: StanislavL , elegorod , APXEOLOG , Singaporian

Links


Github source code
Spring Boot Official Documentation

Source: https://habr.com/ru/post/352954/


All Articles